#!/usr/bin/python # Copyright (c) 2009 Benjamin Drung # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import csv import getopt import os import stat import subprocess import sys import zipfile from rdflib import Namespace from rdflib.Graph import Graph # error codes COMMAND_LINE_SYNTAX_ERROR = 1 XPI_FILE_DOES_NOT_EXISTS = 2 def get_target_applications(script_name, install_rdf, verbose=False): target_applications = [] rdf_graph = Graph() rdf_graph.parse(install_rdf) query = "SELECT ?id WHERE { ?x1 em:targetApplication ?x2 . ?x2 em:id ?id }" results = rdf_graph.query(query, initNs=dict(em=Namespace("http://www.mozilla.org/2004/em-rdf#"))) for target in results: target_applications.append(str(target[0])) return target_applications def get_extension_id(install_rdf): extension_ids = set() rdf_graph = Graph() rdf_graph.parse(install_rdf) query = "SELECT ?id WHERE {?x1 em:targetApplication ?x2 . ?x1 em:id ?id }" results = rdf_graph.query(query, initNs=dict(em=Namespace("http://www.mozilla.org/2004/em-rdf#"))) for result in results: extension_ids.add(str(result[0])) return extension_ids.pop() def get_arch(package): lines = open("debian/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) (p, arch) = filter(lambda (x, y): x == package, zip(packages, architectures))[0] return arch def get_mode(filename): st = os.stat(filename) mode = st[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 incompatible_apps(target_applications): """There are still some application that do not scan "/usr/{lib,share}/mozilla/extensions/$target_application" for extensions.""" xul_apps = filter(lambda x: x["id"] in target_applications, get_xul_apps()) app_list = reduce(lambda l, x: l + [x["package"]], xul_apps, list()) incompatible_apps = ["icedove", "thunderbird"] incompatible_apps = filter(lambda x: x in app_list, incompatible_apps) return incompatible_apps def install_xpi(script_name, package, xpi_file, exclude, install_dir, links, correct_permissions, remove_licenses, verbose=False): # get xpi file content list if not os.path.isfile(xpi_file): print >> sys.stderr, "%s: Error: xpi file %s does not exist." % (script_name, xpi_file) sys.exit(XPI_FILE_DOES_NOT_EXISTS) zfobj = zipfile.ZipFile(xpi_file) xpi_content = zfobj.namelist() # determine installation directory if get_arch(package) == "all": lib_share_dir = "share" else: lib_share_dir = "lib" if install_dir is None: install_dir = os.path.join("usr", lib_share_dir, package) copy_dir = os.path.join("debian", package, install_dir.strip("/")) if verbose: print "%s: install directory: %s" % (script_name, install_dir) # remove documented license files if remove_licenses: pattern_list = ("copying", "gpl.txt", "licence", "license", "licence.txt", "license.txt") for name in filter(lambda x: not x.endswith('/'), xpi_content): basename = os.path.basename(name).lower() if basename in 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) 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) # get symlinks list extension_id = get_extension_id(os.path.join(copy_dir, "install.rdf")) target_applications = get_target_applications(script_name, os.path.join(copy_dir, "install.rdf")) for target_application in target_applications: destination = os.path.join("/usr", lib_share_dir, "mozilla/extensions", target_application, extension_id) links.add(destination) # backwards compatibility: create symlinks for applications that do not # scan /usr/{lib,share}/mozilla/extensions/target_application for extensions for app in incompatible_apps(target_applications): links.add(os.path.join("/usr/lib", app, "extensions", extension_id)) # create symlinks for link in links: command = ["dh_link", "-p" + package, install_dir, link] print " ".join(command) subprocess.call(command) def get_first_package(): lines = open("debian/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 usage(output): print >> output, """Usage: %s [options] Options: -x, --exclude= do not install specified file -i, --install-dir= install extension into the specified directory -l, --link= link from directory to extension directory -p, --package= install the extension into specified package --preserve-permissions do not adjust the file permissions -r, --remove-license-files do not install license files General options: -h, --help display this help and exit -v, --verbose print more information See %s(1) for more info.""" % (os.path.basename(sys.argv[0]), os.path.basename(sys.argv[0])) if __name__ == "__main__": try: long_opts = ["exclude", "help", "install-dir", "link", "package", "preserve-permissions", "remove-license-files", "verbose"] opts, args = getopt.gnu_getopt(sys.argv[1:], "hi:l:p:rvx:", long_opts) except getopt.GetoptError, e: # print help information and exit: print >> sys.stderr, str(e) # will print something like "option -a not recognized" usage(sys.stderr) sys.exit(COMMAND_LINE_SYNTAX_ERROR) correct_permissions = True install_dir = None links = set() package = None remove_licenses = False verbose = False exclude = list() for o, a in opts: if o in ("-x", "--exclude"): exclude.append(a) elif o in ("-h", "--help"): usage(sys.stdout) sys.exit() elif o in ("-i", "--install-dir"): install_dir = a elif o in ("-l", "--link"): links.add(a) elif o in ("-p", "--package"): package = a elif o in ("preserve-permissions"): correct_permissions = False elif o in ("-r", "--remove-license-files"): remove_licenses = True elif o in ("-v", "--verbose"): verbose = True else: assert False, "unhandled option" if package is None: package = get_first_package() script_name = os.path.basename(sys.argv[0]) if len(args) == 0: print >> sys.stderr, "%s: Error: No xpi file specified." % (script_name) sys.exit(COMMAND_LINE_SYNTAX_ERROR) elif len(args) > 1: print >> sys.stderr, script_name + ": Error: Multiple xpi files specified: " + ", ".join(args) sys.exit(COMMAND_LINE_SYNTAX_ERROR) if verbose: print script_name + ": Install %s into package %s." % (args[0], package) install_xpi(script_name, package, args[0], exclude, install_dir, links, correct_permissions, remove_licenses, verbose)