#!/usr/bin/python # Copyright (c) 2009-2013, Benjamin Drung # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from __future__ import print_function import csv import optparse import os import stat import subprocess import sys import zipfile import RDF LICENSE_PATTERN_LIST = ( "copying", "gpl.txt", "licence", "license", "licence.txt", "license.txt" ) # error codes COMMAND_LINE_SYNTAX_ERROR = 1 XPI_FILE_DOES_NOT_EXISTS = 2 RDF_ERROR = 3 def get_debian_directory(script_name): """Return the path to the debian/ directory. Search for a debian/ directory in the current working directory and crawling up the parent directories until one is found. The script will fail if no debian/ directory is found or the debian/ directory does not contain a control file.""" base_directory = os.getcwd() while not os.path.isdir(os.path.join(base_directory, "debian")): parent_directory = os.path.split(base_directory)[0] if base_directory == parent_directory: sys.stderr.write(script_name + ": Error: Failed to find a debian/ " "directory. Please execute this script inside a " "Debian source package.\n") sys.exit(1) else: base_directory = parent_directory package_directory = os.path.join(base_directory, "debian") # Check if debian/control exists. if not os.path.isfile(os.path.join(package_directory, "control")): sys.stderr.write(script_name + ": Error: debian/control file is " "missing.\n") sys.exit(1) return package_directory def get_query_field_id_as_list(rdf_path, query_string): ret = [] model = RDF.Model() parser = RDF.Parser(name="rdfxml") parser.parse_into_model(model, "file:" + rdf_path) query = RDF.Query("PREFIX em: " + query_string, query_language="sparql") results = query.execute(model) for result in results: ret.append(result["id"].literal_value["string"]) return ret def get_target_applications(install_rdf): target_applications = get_query_field_id_as_list( install_rdf, "SELECT ?id WHERE { [] em:targetApplication ?x . ?x em:id ?id }" ) return target_applications def get_extension_id(install_rdf): extension_ids = set(get_query_field_id_as_list( install_rdf, "SELECT ?id WHERE {?x em:targetApplication [] . ?x em:id ?id }" )) return extension_ids.pop() def get_arch(package, debian_directory): lines = open(os.path.join(debian_directory, "control")).readlines() package_lines = filter(lambda x: x.find("Package:") >= 0, lines) packages = map(lambda x: x[x.find(":")+1:].strip(), package_lines) architecture_lines = filter(lambda x: x.find("Architecture:") >= 0, lines) architectures = map(lambda x: x[x.find(":")+1:].strip(), architecture_lines) (_, arch) = filter(lambda (x, y): x == package, zip(packages, architectures))[0] return arch def get_mode(filename): statinfo = os.stat(filename) mode = statinfo[stat.ST_MODE] return mode & 0777 def get_xul_apps(): csvfile = open("/usr/share/mozilla-devscripts/xul-app-data.csv") csv_reader = csv.DictReader(csvfile) rows = [] for row in csv_reader: rows.append(row) return rows def install_xpi(script_name, package, xpi_file, exclude, install_dir, links, correct_permissions, remove_licenses, system_prefs, debian_directory, verbose=False): # get xpi file content list if not os.path.isfile(xpi_file): print("%s: Error: xpi file %s does not exist." % (script_name, xpi_file), file=sys.stderr) sys.exit(XPI_FILE_DOES_NOT_EXISTS) zfobj = zipfile.ZipFile(xpi_file) xpi_content = sorted(zfobj.namelist()) # determine installation directory if get_arch(package, debian_directory) == "all": lib_share_dir = "share" else: lib_share_dir = "lib" if install_dir is None: install_dir = os.path.join("usr", lib_share_dir, "xul-ext", package.replace("xul-ext-", "")) copy_dir = os.path.join(debian_directory, package, install_dir.strip("/")) if verbose: print("%s: install directory: %s" % (script_name, install_dir)) # remove documented license files if remove_licenses: for name in filter(lambda x: not x.endswith('/'), xpi_content): basename = os.path.basename(name).lower() if basename in LICENSE_PATTERN_LIST: exclude.append(name) print("%s: exclude license file %s" % (script_name, name)) # create directory and extract xpi file if not os.path.isdir(copy_dir): os.makedirs(copy_dir) # With unzip, the mtime of created files will depend on the timezone, # which prevents reproducible builds. Let's make it UTC before unzipping. os.environ['TZ'] = 'UTC' command = ["unzip", "-o", "-d", copy_dir, xpi_file] if len(exclude) > 0: command.append("-x") command.extend(exclude) print(" ".join(command)) subprocess.call(command) # correct permissons of files to 644 and directories to 755 if correct_permissions: for name in xpi_content: filename = os.path.join(copy_dir, name) if os.path.exists(filename): mode = get_mode(filename) if os.path.isdir(filename) and mode != 0755: print("%s: correct permission from %s to %s of %s" % (script_name, oct(mode), oct(0755), name)) os.chmod(filename, 0755) elif os.path.isfile(filename): header = open(filename, "r").read(2) if header != "#!" and mode != 0644: # file without shebang print("%s: correct permission from %s to %s of %s" % (script_name, oct(mode), oct(0644), name)) os.chmod(filename, 0644) elif header == "#!" and mode != 0755: # file with shebang print("%s: correct permission from %s to %s of %s" % (script_name, oct(mode), oct(0755), name)) os.chmod(filename, 0755) # create a system preference file in /etc if system_prefs: # search for preference .js files in defaults/preferences/ pref_dir = os.path.join("defaults", "preferences") preferences = filter(lambda f: os.path.dirname(f) == pref_dir and f.endswith(".js"), xpi_content) if len(preferences) > 0: prefdir = os.path.join("etc", "xul-ext") full_prefdir = os.path.join(debian_directory, package, prefdir) if not os.path.exists(full_prefdir): os.makedirs(full_prefdir) prefname = package.replace("xul-ext-", "") + ".js" # create system preference file f = open(os.path.join(full_prefdir, prefname), "w") config_file = os.path.join(debian_directory, package + ".js") if os.path.isfile(config_file): # use debian/package.js as configuration file if it exists content = open(config_file).read() # replace @INSTALLDIR@ by the actual installation directory content = content.replace("@INSTALLDIR@", os.path.join("/", install_dir)) f.write(content) else: f.write("// Place your preferences for " + package + " in this file.\n") f.write("// You can override here the preferences specified " "in\n") map(lambda x: f.write("// " + os.path.join("/", install_dir, x) + "\n"), preferences) f.close() link_source = os.path.join(prefdir, prefname) link_target = os.path.join(install_dir, "defaults", "preferences", "000system.js") command = ["dh_link", "-p" + package, link_source, link_target] if verbose: print(" ".join(command)) subprocess.call(command, cwd=os.path.dirname(debian_directory)) # get symlinks list try: extension_id = get_extension_id(os.path.join(copy_dir, "install.rdf")) filename = os.path.join(copy_dir, "install.rdf") target_applications = get_target_applications(filename) except RDF.RedlandError as error: print(script_name + ": Error while parsing install.rdf: " + str(error), file=sys.stderr) sys.exit(RDF_ERROR) for target_application in target_applications: destination = os.path.join("/usr", lib_share_dir, "mozilla/extensions", target_application, extension_id) links.add(destination) # create symlinks for link in links: command = ["dh_link", "-p" + package, install_dir, link] print(" ".join(command)) subprocess.call(command, cwd=os.path.dirname(debian_directory)) def get_first_package(debian_directory): lines = open(os.path.join(debian_directory, "control")).readlines() package_lines = filter(lambda x: x.find("Package:") >= 0, lines) packages = map(lambda x: x[x.find(":")+1:].strip(), package_lines) return packages[0] def main(): script_name = os.path.basename(sys.argv[0]) usage = "%s [options] " % (script_name) epilog = "See %s(1) for more info." % (script_name) parser = optparse.OptionParser(usage=usage, epilog=epilog) parser.add_option("--disable-system-prefs", help="do not create a system preference file in /etc", dest="system_prefs", action="store_false", default=True) parser.add_option("-x", "--exclude", metavar="FILE", help="do not install specified FILE", dest="exclude", action="append", default=list()) parser.add_option("-i", "--install-dir", metavar="DIRECTORY", help="install extension into the specified DIRECTORY", dest="install_dir") parser.add_option("-l", "--link", metavar="DIRECTORY", help="link from DIRECTORY to extension directory", dest="links", action="append", default=list()) parser.add_option("-p", "--package", metavar="PACKAGE", help="install the extension into specified PACKAGE", dest="package", default=None) parser.add_option("--preserve-permissions", dest="correct_permissions", action="store_false", default=True, help="do not adjust the file permissions") parser.add_option("-r", "--remove-license-files", dest="remove_licenses", action="store_true", default=False, help="do not install license files") parser.add_option("-v", "--verbose", help="print more information", dest="verbose", action="store_true", default=False) (options, args) = parser.parse_args() if len(args) == 0: print("%s: Error: No xpi file specified." % (script_name), file=sys.stderr) sys.exit(COMMAND_LINE_SYNTAX_ERROR) elif len(args) > 1: print("%s: Error: Multiple xpi files specified: %s" % (script_name, ", ".join(args)), file=sys.stderr) sys.exit(COMMAND_LINE_SYNTAX_ERROR) debian_directory = get_debian_directory(script_name) if options.package is None: options.package = get_first_package(debian_directory) if options.verbose: print(script_name + ": Install %s into package %s." % (args[0], options.package)) install_xpi(script_name, options.package, args[0], options.exclude, options.install_dir, set(options.links), options.correct_permissions, options.remove_licenses, options.system_prefs, debian_directory, options.verbose) if __name__ == "__main__": main()