summaryrefslogtreecommitdiffstats
path: root/addon-sdk/source/python-lib/cuddlefish/packaging.py
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-02-09 06:46:43 -0500
committerMatt A. Tobin <email@mattatobin.com>2018-02-09 06:46:43 -0500
commitac46df8daea09899ce30dc8fd70986e258c746bf (patch)
tree2750d3125fc253fd5b0671e4bd268eff1fd97296 /addon-sdk/source/python-lib/cuddlefish/packaging.py
parent8cecf8d5208f3945b35f879bba3015bb1a11bec6 (diff)
downloadUXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.gz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.lz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.tar.xz
UXP-ac46df8daea09899ce30dc8fd70986e258c746bf.zip
Move Add-on SDK source to toolkit/jetpack
Diffstat (limited to 'addon-sdk/source/python-lib/cuddlefish/packaging.py')
-rw-r--r--addon-sdk/source/python-lib/cuddlefish/packaging.py463
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()