diff options
Diffstat (limited to 'addon-sdk/source/python-lib/cuddlefish/packaging.py')
-rw-r--r-- | addon-sdk/source/python-lib/cuddlefish/packaging.py | 463 |
1 files changed, 0 insertions, 463 deletions
diff --git a/addon-sdk/source/python-lib/cuddlefish/packaging.py b/addon-sdk/source/python-lib/cuddlefish/packaging.py deleted file mode 100644 index 0c5357e8e..000000000 --- a/addon-sdk/source/python-lib/cuddlefish/packaging.py +++ /dev/null @@ -1,463 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import os -import sys -import re -import copy - -import simplejson as json -from cuddlefish.bunch import Bunch - -MANIFEST_NAME = 'package.json' -DEFAULT_LOADER = 'addon-sdk' - -# Is different from root_dir when running tests -env_root = os.environ.get('CUDDLEFISH_ROOT') - -DEFAULT_PROGRAM_MODULE = 'main' - -DEFAULT_ICON = 'icon.png' -DEFAULT_ICON64 = 'icon64.png' - -METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version', - 'developers', 'translators', 'contributors', 'license', 'homepage', - 'icon', 'icon64', 'main', 'directories', 'permissions', 'preferences'] - -RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$') - -class Error(Exception): - pass - -class MalformedPackageError(Error): - pass - -class MalformedJsonFileError(Error): - pass - -class DuplicatePackageError(Error): - pass - -class PackageNotFoundError(Error): - def __init__(self, missing_package, reason): - self.missing_package = missing_package - self.reason = reason - def __str__(self): - return "%s (%s)" % (self.missing_package, self.reason) - -class BadChromeMarkerError(Error): - pass - -def validate_resource_hostname(name): - """ - Validates the given hostname for a resource: URI. - - For more information, see: - - https://bugzilla.mozilla.org/show_bug.cgi?id=566812#c13 - - Examples: - - >>> validate_resource_hostname('blarg') - - >>> validate_resource_hostname('bl arg') - Traceback (most recent call last): - ... - ValueError: Error: the name of your package contains an invalid character. - Package names can contain only lower-case letters, numbers, underscores, and dashes. - Current package name: bl arg - - >>> validate_resource_hostname('BLARG') - Traceback (most recent call last): - ... - ValueError: Error: the name of your package contains upper-case letters. - Package names can contain only lower-case letters, numbers, underscores, and dashes. - Current package name: BLARG - - >>> validate_resource_hostname('foo@bar') - Traceback (most recent call last): - ... - ValueError: Error: the name of your package contains an invalid character. - Package names can contain only lower-case letters, numbers, underscores, and dashes. - Current package name: foo@bar - """ - - # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details. - if not name.lower() == name: - raise ValueError("""Error: the name of your package contains upper-case letters. -Package names can contain only lower-case letters, numbers, underscores, and dashes. -Current package name: %s""" % name) - - if not RESOURCE_HOSTNAME_RE.match(name): - raise ValueError("""Error: the name of your package contains an invalid character. -Package names can contain only lower-case letters, numbers, underscores, and dashes. -Current package name: %s""" % name) - -def find_packages_with_module(pkg_cfg, name): - # TODO: Make this support more than just top-level modules. - filename = "%s.js" % name - packages = [] - for cfg in pkg_cfg.packages.itervalues(): - if 'lib' in cfg: - matches = [dirname for dirname in resolve_dirs(cfg, cfg.lib) - if os.path.exists(os.path.join(dirname, filename))] - if matches: - packages.append(cfg.name) - return packages - -def resolve_dirs(pkg_cfg, dirnames): - for dirname in dirnames: - yield resolve_dir(pkg_cfg, dirname) - -def resolve_dir(pkg_cfg, dirname): - return os.path.join(pkg_cfg.root_dir, dirname) - -def validate_permissions(perms): - if (perms.get('cross-domain-content') and - not isinstance(perms.get('cross-domain-content'), list)): - raise ValueError("Error: `cross-domain-content` permissions in \ - package.json file must be an array of strings:\n %s" % perms) - -def get_metadata(pkg_cfg, deps): - metadata = Bunch() - for pkg_name in deps: - cfg = pkg_cfg.packages[pkg_name] - metadata[pkg_name] = Bunch() - for prop in METADATA_PROPS: - if cfg.get(prop): - if prop == 'permissions': - validate_permissions(cfg[prop]) - metadata[pkg_name][prop] = cfg[prop] - return metadata - -def set_section_dir(base_json, name, base_path, dirnames, allow_root=False): - resolved = compute_section_dir(base_json, base_path, dirnames, allow_root) - if resolved: - base_json[name] = os.path.abspath(resolved) - -def compute_section_dir(base_json, base_path, dirnames, allow_root): - # PACKAGE_JSON.lib is highest priority - # then PACKAGE_JSON.directories.lib - # then lib/ (if it exists) - # then . (but only if allow_root=True) - for dirname in dirnames: - if base_json.get(dirname): - return os.path.join(base_path, base_json[dirname]) - if "directories" in base_json: - for dirname in dirnames: - if dirname in base_json.directories: - return os.path.join(base_path, base_json.directories[dirname]) - for dirname in dirnames: - if os.path.isdir(os.path.join(base_path, dirname)): - return os.path.join(base_path, dirname) - if allow_root: - return os.path.abspath(base_path) - return None - -def normalize_string_or_array(base_json, key): - if base_json.get(key): - if isinstance(base_json[key], basestring): - base_json[key] = [base_json[key]] - -def load_json_file(path): - data = open(path, 'r').read() - try: - return Bunch(json.loads(data)) - except ValueError, e: - raise MalformedJsonFileError('%s when reading "%s"' % (str(e), - path)) - -def get_config_in_dir(path): - package_json = os.path.join(path, MANIFEST_NAME) - if not (os.path.exists(package_json) and - os.path.isfile(package_json)): - raise MalformedPackageError('%s not found in "%s"' % (MANIFEST_NAME, - path)) - base_json = load_json_file(package_json) - - if 'name' not in base_json: - base_json.name = os.path.basename(path) - - # later processing steps will expect to see the following keys in the - # base_json that we return: - # - # name: name of the package - # lib: list of directories with .js files - # test: list of directories with test-*.js files - # doc: list of directories with documentation .md files - # data: list of directories with bundled arbitrary data files - # packages: ? - - if (not base_json.get('tests') and - os.path.isdir(os.path.join(path, 'test'))): - base_json['tests'] = 'test' - - set_section_dir(base_json, 'lib', path, ['lib'], True) - set_section_dir(base_json, 'tests', path, ['test', 'tests'], False) - set_section_dir(base_json, 'doc', path, ['doc', 'docs']) - set_section_dir(base_json, 'data', path, ['data']) - set_section_dir(base_json, 'packages', path, ['packages']) - set_section_dir(base_json, 'locale', path, ['locale']) - - if (not base_json.get('icon') and - os.path.isfile(os.path.join(path, DEFAULT_ICON))): - base_json['icon'] = DEFAULT_ICON - - if (not base_json.get('icon64') and - os.path.isfile(os.path.join(path, DEFAULT_ICON64))): - base_json['icon64'] = DEFAULT_ICON64 - - for key in ['lib', 'tests', 'dependencies', 'packages']: - # TODO: lib/tests can be an array?? consider interaction with - # compute_section_dir above - normalize_string_or_array(base_json, key) - - if 'main' not in base_json and 'lib' in base_json: - for dirname in base_json['lib']: - program = os.path.join(path, dirname, - '%s.js' % DEFAULT_PROGRAM_MODULE) - if os.path.exists(program): - base_json['main'] = DEFAULT_PROGRAM_MODULE - break - - base_json.root_dir = path - - if "dependencies" in base_json: - deps = base_json["dependencies"] - deps = [x for x in deps if x not in ["addon-kit", "api-utils"]] - deps.append("addon-sdk") - base_json["dependencies"] = deps - - return base_json - -def _is_same_file(a, b): - if hasattr(os.path, 'samefile'): - return os.path.samefile(a, b) - return a == b - -def build_config(root_dir, target_cfg, packagepath=[]): - dirs_to_scan = [env_root] # root is addon-sdk dir, diff from root_dir in tests - - def add_packages_from_config(pkgconfig): - if 'packages' in pkgconfig: - for package_dir in resolve_dirs(pkgconfig, pkgconfig.packages): - dirs_to_scan.append(package_dir) - - add_packages_from_config(target_cfg) - - packages_dir = os.path.join(root_dir, 'packages') - if os.path.exists(packages_dir) and os.path.isdir(packages_dir): - dirs_to_scan.append(packages_dir) - dirs_to_scan.extend(packagepath) - - packages = Bunch({target_cfg.name: target_cfg}) - - while dirs_to_scan: - packages_dir = dirs_to_scan.pop() - if os.path.exists(os.path.join(packages_dir, "package.json")): - package_paths = [packages_dir] - else: - package_paths = [os.path.join(packages_dir, dirname) - for dirname in os.listdir(packages_dir) - if not dirname.startswith('.')] - package_paths = [dirname for dirname in package_paths - if os.path.isdir(dirname)] - - for path in package_paths: - pkgconfig = get_config_in_dir(path) - if pkgconfig.name in packages: - otherpkg = packages[pkgconfig.name] - if not _is_same_file(otherpkg.root_dir, path): - raise DuplicatePackageError(path, otherpkg.root_dir) - else: - packages[pkgconfig.name] = pkgconfig - add_packages_from_config(pkgconfig) - - return Bunch(packages=packages) - -def get_deps_for_targets(pkg_cfg, targets): - visited = [] - deps_left = [[dep, None] for dep in list(targets)] - - while deps_left: - [dep, required_by] = deps_left.pop() - if dep not in visited: - visited.append(dep) - if dep not in pkg_cfg.packages: - required_reason = ("required by '%s'" % (required_by)) \ - if required_by is not None \ - else "specified as target" - raise PackageNotFoundError(dep, required_reason) - dep_cfg = pkg_cfg.packages[dep] - deps_left.extend([[i, dep] for i in dep_cfg.get('dependencies', [])]) - deps_left.extend([[i, dep] for i in dep_cfg.get('extra_dependencies', [])]) - - return visited - -def generate_build_for_target(pkg_cfg, target, deps, - include_tests=True, - include_dep_tests=False, - is_running_tests=False, - default_loader=DEFAULT_LOADER): - - build = Bunch(# Contains section directories for all packages: - packages=Bunch(), - locale=Bunch() - ) - - def add_section_to_build(cfg, section, is_code=False, - is_data=False): - if section in cfg: - dirnames = cfg[section] - if isinstance(dirnames, basestring): - # This is just for internal consistency within this - # function, it has nothing to do w/ a non-canonical - # configuration dict. - dirnames = [dirnames] - for dirname in resolve_dirs(cfg, dirnames): - # ensure that package name is valid - try: - validate_resource_hostname(cfg.name) - except ValueError, err: - print err - sys.exit(1) - # ensure that this package has an entry - if not cfg.name in build.packages: - build.packages[cfg.name] = Bunch() - # detect duplicated sections - if section in build.packages[cfg.name]: - raise KeyError("package's section already defined", - cfg.name, section) - # Register this section (lib, data, tests) - build.packages[cfg.name][section] = dirname - - def add_locale_to_build(cfg): - # Bug 730776: Ignore locales for addon-kit, that are only for unit tests - if not is_running_tests and cfg.name == "addon-sdk": - return - - path = resolve_dir(cfg, cfg['locale']) - files = os.listdir(path) - for filename in files: - fullpath = os.path.join(path, filename) - if os.path.isfile(fullpath) and filename.endswith('.properties'): - language = filename[:-len('.properties')] - - from property_parser import parse_file, MalformedLocaleFileError - try: - content = parse_file(fullpath) - except MalformedLocaleFileError, msg: - print msg[0] - sys.exit(1) - - # Merge current locales into global locale hashtable. - # Locale files only contains one big JSON object - # that act as an hastable of: - # "keys to translate" => "translated keys" - if language in build.locale: - merge = (build.locale[language].items() + - content.items()) - build.locale[language] = Bunch(merge) - else: - build.locale[language] = content - - def add_dep_to_build(dep): - dep_cfg = pkg_cfg.packages[dep] - add_section_to_build(dep_cfg, "lib", is_code=True) - add_section_to_build(dep_cfg, "data", is_data=True) - if include_tests and include_dep_tests: - add_section_to_build(dep_cfg, "tests", is_code=True) - if 'locale' in dep_cfg: - add_locale_to_build(dep_cfg) - if ("loader" in dep_cfg) and ("loader" not in build): - build.loader = "%s/%s" % (dep, - dep_cfg.loader) - - target_cfg = pkg_cfg.packages[target] - - if include_tests and not include_dep_tests: - add_section_to_build(target_cfg, "tests", is_code=True) - - for dep in deps: - add_dep_to_build(dep) - - if 'loader' not in build: - add_dep_to_build(DEFAULT_LOADER) - - if 'icon' in target_cfg: - build['icon'] = os.path.join(target_cfg.root_dir, target_cfg.icon) - del target_cfg['icon'] - - if 'icon64' in target_cfg: - build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64) - del target_cfg['icon64'] - - if 'id' in target_cfg: - # NOTE: logic duplicated from buildJID() - jid = target_cfg['id'] - if not ('@' in jid or jid.startswith('{')): - jid += '@jetpack' - build['preferencesBranch'] = jid - - if 'preferences-branch' in target_cfg: - build['preferencesBranch'] = target_cfg['preferences-branch'] - - return build - -def _get_files_in_dir(path): - data = {} - files = os.listdir(path) - for filename in files: - fullpath = os.path.join(path, filename) - if os.path.isdir(fullpath): - data[filename] = _get_files_in_dir(fullpath) - else: - try: - info = os.stat(fullpath) - data[filename] = ("file", dict(size=info.st_size)) - except OSError: - pass - return ("directory", data) - -def build_pkg_index(pkg_cfg): - pkg_cfg = copy.deepcopy(pkg_cfg) - for pkg in pkg_cfg.packages: - root_dir = pkg_cfg.packages[pkg].root_dir - files = _get_files_in_dir(root_dir) - pkg_cfg.packages[pkg].files = files - try: - readme = open(root_dir + '/README.md').read() - pkg_cfg.packages[pkg].readme = readme - except IOError: - pass - del pkg_cfg.packages[pkg].root_dir - return pkg_cfg.packages - -def build_pkg_cfg(root): - pkg_cfg = build_config(root, Bunch(name='dummy')) - del pkg_cfg.packages['dummy'] - return pkg_cfg - -def call_plugins(pkg_cfg, deps): - for dep in deps: - dep_cfg = pkg_cfg.packages[dep] - dirnames = dep_cfg.get('python-lib', []) - for dirname in resolve_dirs(dep_cfg, dirnames): - sys.path.append(dirname) - module_names = dep_cfg.get('python-plugins', []) - for module_name in module_names: - module = __import__(module_name) - module.init(root_dir=dep_cfg.root_dir) - -def call_cmdline_tool(env_root, pkg_name): - pkg_cfg = build_config(env_root, Bunch(name='dummy')) - if pkg_name not in pkg_cfg.packages: - print "This tool requires the '%s' package." % pkg_name - sys.exit(1) - cfg = pkg_cfg.packages[pkg_name] - for dirname in resolve_dirs(cfg, cfg['python-lib']): - sys.path.append(dirname) - module_name = cfg.get('python-cmdline-tool') - module = __import__(module_name) - module.run() |