Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | import os |
michael@0 | 6 | import sys |
michael@0 | 7 | import re |
michael@0 | 8 | import copy |
michael@0 | 9 | |
michael@0 | 10 | import simplejson as json |
michael@0 | 11 | from cuddlefish.bunch import Bunch |
michael@0 | 12 | |
michael@0 | 13 | MANIFEST_NAME = 'package.json' |
michael@0 | 14 | DEFAULT_LOADER = 'addon-sdk' |
michael@0 | 15 | |
michael@0 | 16 | # Is different from root_dir when running tests |
michael@0 | 17 | env_root = os.environ.get('CUDDLEFISH_ROOT') |
michael@0 | 18 | |
michael@0 | 19 | DEFAULT_PROGRAM_MODULE = 'main' |
michael@0 | 20 | |
michael@0 | 21 | DEFAULT_ICON = 'icon.png' |
michael@0 | 22 | DEFAULT_ICON64 = 'icon64.png' |
michael@0 | 23 | |
michael@0 | 24 | METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version', |
michael@0 | 25 | 'translators', 'contributors', 'license', 'homepage', 'icon', |
michael@0 | 26 | 'icon64', 'main', 'directories', 'permissions'] |
michael@0 | 27 | |
michael@0 | 28 | RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$') |
michael@0 | 29 | |
michael@0 | 30 | class Error(Exception): |
michael@0 | 31 | pass |
michael@0 | 32 | |
michael@0 | 33 | class MalformedPackageError(Error): |
michael@0 | 34 | pass |
michael@0 | 35 | |
michael@0 | 36 | class MalformedJsonFileError(Error): |
michael@0 | 37 | pass |
michael@0 | 38 | |
michael@0 | 39 | class DuplicatePackageError(Error): |
michael@0 | 40 | pass |
michael@0 | 41 | |
michael@0 | 42 | class PackageNotFoundError(Error): |
michael@0 | 43 | def __init__(self, missing_package, reason): |
michael@0 | 44 | self.missing_package = missing_package |
michael@0 | 45 | self.reason = reason |
michael@0 | 46 | def __str__(self): |
michael@0 | 47 | return "%s (%s)" % (self.missing_package, self.reason) |
michael@0 | 48 | |
michael@0 | 49 | class BadChromeMarkerError(Error): |
michael@0 | 50 | pass |
michael@0 | 51 | |
michael@0 | 52 | def validate_resource_hostname(name): |
michael@0 | 53 | """ |
michael@0 | 54 | Validates the given hostname for a resource: URI. |
michael@0 | 55 | |
michael@0 | 56 | For more information, see: |
michael@0 | 57 | |
michael@0 | 58 | https://bugzilla.mozilla.org/show_bug.cgi?id=566812#c13 |
michael@0 | 59 | |
michael@0 | 60 | Examples: |
michael@0 | 61 | |
michael@0 | 62 | >>> validate_resource_hostname('blarg') |
michael@0 | 63 | |
michael@0 | 64 | >>> validate_resource_hostname('bl arg') |
michael@0 | 65 | Traceback (most recent call last): |
michael@0 | 66 | ... |
michael@0 | 67 | ValueError: Error: the name of your package contains an invalid character. |
michael@0 | 68 | Package names can contain only lower-case letters, numbers, underscores, and dashes. |
michael@0 | 69 | Current package name: bl arg |
michael@0 | 70 | |
michael@0 | 71 | >>> validate_resource_hostname('BLARG') |
michael@0 | 72 | Traceback (most recent call last): |
michael@0 | 73 | ... |
michael@0 | 74 | ValueError: Error: the name of your package contains upper-case letters. |
michael@0 | 75 | Package names can contain only lower-case letters, numbers, underscores, and dashes. |
michael@0 | 76 | Current package name: BLARG |
michael@0 | 77 | |
michael@0 | 78 | >>> validate_resource_hostname('foo@bar') |
michael@0 | 79 | Traceback (most recent call last): |
michael@0 | 80 | ... |
michael@0 | 81 | ValueError: Error: the name of your package contains an invalid character. |
michael@0 | 82 | Package names can contain only lower-case letters, numbers, underscores, and dashes. |
michael@0 | 83 | Current package name: foo@bar |
michael@0 | 84 | """ |
michael@0 | 85 | |
michael@0 | 86 | # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details. |
michael@0 | 87 | if not name.islower(): |
michael@0 | 88 | raise ValueError("""Error: the name of your package contains upper-case letters. |
michael@0 | 89 | Package names can contain only lower-case letters, numbers, underscores, and dashes. |
michael@0 | 90 | Current package name: %s""" % name) |
michael@0 | 91 | |
michael@0 | 92 | if not RESOURCE_HOSTNAME_RE.match(name): |
michael@0 | 93 | raise ValueError("""Error: the name of your package contains an invalid character. |
michael@0 | 94 | Package names can contain only lower-case letters, numbers, underscores, and dashes. |
michael@0 | 95 | Current package name: %s""" % name) |
michael@0 | 96 | |
michael@0 | 97 | def find_packages_with_module(pkg_cfg, name): |
michael@0 | 98 | # TODO: Make this support more than just top-level modules. |
michael@0 | 99 | filename = "%s.js" % name |
michael@0 | 100 | packages = [] |
michael@0 | 101 | for cfg in pkg_cfg.packages.itervalues(): |
michael@0 | 102 | if 'lib' in cfg: |
michael@0 | 103 | matches = [dirname for dirname in resolve_dirs(cfg, cfg.lib) |
michael@0 | 104 | if os.path.exists(os.path.join(dirname, filename))] |
michael@0 | 105 | if matches: |
michael@0 | 106 | packages.append(cfg.name) |
michael@0 | 107 | return packages |
michael@0 | 108 | |
michael@0 | 109 | def resolve_dirs(pkg_cfg, dirnames): |
michael@0 | 110 | for dirname in dirnames: |
michael@0 | 111 | yield resolve_dir(pkg_cfg, dirname) |
michael@0 | 112 | |
michael@0 | 113 | def resolve_dir(pkg_cfg, dirname): |
michael@0 | 114 | return os.path.join(pkg_cfg.root_dir, dirname) |
michael@0 | 115 | |
michael@0 | 116 | def validate_permissions(perms): |
michael@0 | 117 | if (perms.get('cross-domain-content') and |
michael@0 | 118 | not isinstance(perms.get('cross-domain-content'), list)): |
michael@0 | 119 | raise ValueError("Error: `cross-domain-content` permissions in \ |
michael@0 | 120 | package.json file must be an array of strings:\n %s" % perms) |
michael@0 | 121 | |
michael@0 | 122 | def get_metadata(pkg_cfg, deps): |
michael@0 | 123 | metadata = Bunch() |
michael@0 | 124 | for pkg_name in deps: |
michael@0 | 125 | cfg = pkg_cfg.packages[pkg_name] |
michael@0 | 126 | metadata[pkg_name] = Bunch() |
michael@0 | 127 | for prop in METADATA_PROPS: |
michael@0 | 128 | if cfg.get(prop): |
michael@0 | 129 | if prop == 'permissions': |
michael@0 | 130 | validate_permissions(cfg[prop]) |
michael@0 | 131 | metadata[pkg_name][prop] = cfg[prop] |
michael@0 | 132 | return metadata |
michael@0 | 133 | |
michael@0 | 134 | def set_section_dir(base_json, name, base_path, dirnames, allow_root=False): |
michael@0 | 135 | resolved = compute_section_dir(base_json, base_path, dirnames, allow_root) |
michael@0 | 136 | if resolved: |
michael@0 | 137 | base_json[name] = os.path.abspath(resolved) |
michael@0 | 138 | |
michael@0 | 139 | def compute_section_dir(base_json, base_path, dirnames, allow_root): |
michael@0 | 140 | # PACKAGE_JSON.lib is highest priority |
michael@0 | 141 | # then PACKAGE_JSON.directories.lib |
michael@0 | 142 | # then lib/ (if it exists) |
michael@0 | 143 | # then . (but only if allow_root=True) |
michael@0 | 144 | for dirname in dirnames: |
michael@0 | 145 | if base_json.get(dirname): |
michael@0 | 146 | return os.path.join(base_path, base_json[dirname]) |
michael@0 | 147 | if "directories" in base_json: |
michael@0 | 148 | for dirname in dirnames: |
michael@0 | 149 | if dirname in base_json.directories: |
michael@0 | 150 | return os.path.join(base_path, base_json.directories[dirname]) |
michael@0 | 151 | for dirname in dirnames: |
michael@0 | 152 | if os.path.isdir(os.path.join(base_path, dirname)): |
michael@0 | 153 | return os.path.join(base_path, dirname) |
michael@0 | 154 | if allow_root: |
michael@0 | 155 | return os.path.abspath(base_path) |
michael@0 | 156 | return None |
michael@0 | 157 | |
michael@0 | 158 | def normalize_string_or_array(base_json, key): |
michael@0 | 159 | if base_json.get(key): |
michael@0 | 160 | if isinstance(base_json[key], basestring): |
michael@0 | 161 | base_json[key] = [base_json[key]] |
michael@0 | 162 | |
michael@0 | 163 | def load_json_file(path): |
michael@0 | 164 | data = open(path, 'r').read() |
michael@0 | 165 | try: |
michael@0 | 166 | return Bunch(json.loads(data)) |
michael@0 | 167 | except ValueError, e: |
michael@0 | 168 | raise MalformedJsonFileError('%s when reading "%s"' % (str(e), |
michael@0 | 169 | path)) |
michael@0 | 170 | |
michael@0 | 171 | def get_config_in_dir(path): |
michael@0 | 172 | package_json = os.path.join(path, MANIFEST_NAME) |
michael@0 | 173 | if not (os.path.exists(package_json) and |
michael@0 | 174 | os.path.isfile(package_json)): |
michael@0 | 175 | raise MalformedPackageError('%s not found in "%s"' % (MANIFEST_NAME, |
michael@0 | 176 | path)) |
michael@0 | 177 | base_json = load_json_file(package_json) |
michael@0 | 178 | |
michael@0 | 179 | if 'name' not in base_json: |
michael@0 | 180 | base_json.name = os.path.basename(path) |
michael@0 | 181 | |
michael@0 | 182 | # later processing steps will expect to see the following keys in the |
michael@0 | 183 | # base_json that we return: |
michael@0 | 184 | # |
michael@0 | 185 | # name: name of the package |
michael@0 | 186 | # lib: list of directories with .js files |
michael@0 | 187 | # test: list of directories with test-*.js files |
michael@0 | 188 | # doc: list of directories with documentation .md files |
michael@0 | 189 | # data: list of directories with bundled arbitrary data files |
michael@0 | 190 | # packages: ? |
michael@0 | 191 | |
michael@0 | 192 | if (not base_json.get('tests') and |
michael@0 | 193 | os.path.isdir(os.path.join(path, 'test'))): |
michael@0 | 194 | base_json['tests'] = 'test' |
michael@0 | 195 | |
michael@0 | 196 | set_section_dir(base_json, 'lib', path, ['lib'], True) |
michael@0 | 197 | set_section_dir(base_json, 'tests', path, ['test', 'tests'], False) |
michael@0 | 198 | set_section_dir(base_json, 'doc', path, ['doc', 'docs']) |
michael@0 | 199 | set_section_dir(base_json, 'data', path, ['data']) |
michael@0 | 200 | set_section_dir(base_json, 'packages', path, ['packages']) |
michael@0 | 201 | set_section_dir(base_json, 'locale', path, ['locale']) |
michael@0 | 202 | |
michael@0 | 203 | if (not base_json.get('icon') and |
michael@0 | 204 | os.path.isfile(os.path.join(path, DEFAULT_ICON))): |
michael@0 | 205 | base_json['icon'] = DEFAULT_ICON |
michael@0 | 206 | |
michael@0 | 207 | if (not base_json.get('icon64') and |
michael@0 | 208 | os.path.isfile(os.path.join(path, DEFAULT_ICON64))): |
michael@0 | 209 | base_json['icon64'] = DEFAULT_ICON64 |
michael@0 | 210 | |
michael@0 | 211 | for key in ['lib', 'tests', 'dependencies', 'packages']: |
michael@0 | 212 | # TODO: lib/tests can be an array?? consider interaction with |
michael@0 | 213 | # compute_section_dir above |
michael@0 | 214 | normalize_string_or_array(base_json, key) |
michael@0 | 215 | |
michael@0 | 216 | if 'main' not in base_json and 'lib' in base_json: |
michael@0 | 217 | for dirname in base_json['lib']: |
michael@0 | 218 | program = os.path.join(path, dirname, |
michael@0 | 219 | '%s.js' % DEFAULT_PROGRAM_MODULE) |
michael@0 | 220 | if os.path.exists(program): |
michael@0 | 221 | base_json['main'] = DEFAULT_PROGRAM_MODULE |
michael@0 | 222 | break |
michael@0 | 223 | |
michael@0 | 224 | base_json.root_dir = path |
michael@0 | 225 | |
michael@0 | 226 | if "dependencies" in base_json: |
michael@0 | 227 | deps = base_json["dependencies"] |
michael@0 | 228 | deps = [x for x in deps if x not in ["addon-kit", "api-utils"]] |
michael@0 | 229 | deps.append("addon-sdk") |
michael@0 | 230 | base_json["dependencies"] = deps |
michael@0 | 231 | |
michael@0 | 232 | return base_json |
michael@0 | 233 | |
michael@0 | 234 | def _is_same_file(a, b): |
michael@0 | 235 | if hasattr(os.path, 'samefile'): |
michael@0 | 236 | return os.path.samefile(a, b) |
michael@0 | 237 | return a == b |
michael@0 | 238 | |
michael@0 | 239 | def build_config(root_dir, target_cfg, packagepath=[]): |
michael@0 | 240 | dirs_to_scan = [env_root] # root is addon-sdk dir, diff from root_dir in tests |
michael@0 | 241 | |
michael@0 | 242 | def add_packages_from_config(pkgconfig): |
michael@0 | 243 | if 'packages' in pkgconfig: |
michael@0 | 244 | for package_dir in resolve_dirs(pkgconfig, pkgconfig.packages): |
michael@0 | 245 | dirs_to_scan.append(package_dir) |
michael@0 | 246 | |
michael@0 | 247 | add_packages_from_config(target_cfg) |
michael@0 | 248 | |
michael@0 | 249 | packages_dir = os.path.join(root_dir, 'packages') |
michael@0 | 250 | if os.path.exists(packages_dir) and os.path.isdir(packages_dir): |
michael@0 | 251 | dirs_to_scan.append(packages_dir) |
michael@0 | 252 | dirs_to_scan.extend(packagepath) |
michael@0 | 253 | |
michael@0 | 254 | packages = Bunch({target_cfg.name: target_cfg}) |
michael@0 | 255 | |
michael@0 | 256 | while dirs_to_scan: |
michael@0 | 257 | packages_dir = dirs_to_scan.pop() |
michael@0 | 258 | if os.path.exists(os.path.join(packages_dir, "package.json")): |
michael@0 | 259 | package_paths = [packages_dir] |
michael@0 | 260 | else: |
michael@0 | 261 | package_paths = [os.path.join(packages_dir, dirname) |
michael@0 | 262 | for dirname in os.listdir(packages_dir) |
michael@0 | 263 | if not dirname.startswith('.')] |
michael@0 | 264 | package_paths = [dirname for dirname in package_paths |
michael@0 | 265 | if os.path.isdir(dirname)] |
michael@0 | 266 | |
michael@0 | 267 | for path in package_paths: |
michael@0 | 268 | pkgconfig = get_config_in_dir(path) |
michael@0 | 269 | if pkgconfig.name in packages: |
michael@0 | 270 | otherpkg = packages[pkgconfig.name] |
michael@0 | 271 | if not _is_same_file(otherpkg.root_dir, path): |
michael@0 | 272 | raise DuplicatePackageError(path, otherpkg.root_dir) |
michael@0 | 273 | else: |
michael@0 | 274 | packages[pkgconfig.name] = pkgconfig |
michael@0 | 275 | add_packages_from_config(pkgconfig) |
michael@0 | 276 | |
michael@0 | 277 | return Bunch(packages=packages) |
michael@0 | 278 | |
michael@0 | 279 | def get_deps_for_targets(pkg_cfg, targets): |
michael@0 | 280 | visited = [] |
michael@0 | 281 | deps_left = [[dep, None] for dep in list(targets)] |
michael@0 | 282 | |
michael@0 | 283 | while deps_left: |
michael@0 | 284 | [dep, required_by] = deps_left.pop() |
michael@0 | 285 | if dep not in visited: |
michael@0 | 286 | visited.append(dep) |
michael@0 | 287 | if dep not in pkg_cfg.packages: |
michael@0 | 288 | required_reason = ("required by '%s'" % (required_by)) \ |
michael@0 | 289 | if required_by is not None \ |
michael@0 | 290 | else "specified as target" |
michael@0 | 291 | raise PackageNotFoundError(dep, required_reason) |
michael@0 | 292 | dep_cfg = pkg_cfg.packages[dep] |
michael@0 | 293 | deps_left.extend([[i, dep] for i in dep_cfg.get('dependencies', [])]) |
michael@0 | 294 | deps_left.extend([[i, dep] for i in dep_cfg.get('extra_dependencies', [])]) |
michael@0 | 295 | |
michael@0 | 296 | return visited |
michael@0 | 297 | |
michael@0 | 298 | def generate_build_for_target(pkg_cfg, target, deps, |
michael@0 | 299 | include_tests=True, |
michael@0 | 300 | include_dep_tests=False, |
michael@0 | 301 | is_running_tests=False, |
michael@0 | 302 | default_loader=DEFAULT_LOADER): |
michael@0 | 303 | |
michael@0 | 304 | build = Bunch(# Contains section directories for all packages: |
michael@0 | 305 | packages=Bunch(), |
michael@0 | 306 | locale=Bunch() |
michael@0 | 307 | ) |
michael@0 | 308 | |
michael@0 | 309 | def add_section_to_build(cfg, section, is_code=False, |
michael@0 | 310 | is_data=False): |
michael@0 | 311 | if section in cfg: |
michael@0 | 312 | dirnames = cfg[section] |
michael@0 | 313 | if isinstance(dirnames, basestring): |
michael@0 | 314 | # This is just for internal consistency within this |
michael@0 | 315 | # function, it has nothing to do w/ a non-canonical |
michael@0 | 316 | # configuration dict. |
michael@0 | 317 | dirnames = [dirnames] |
michael@0 | 318 | for dirname in resolve_dirs(cfg, dirnames): |
michael@0 | 319 | # ensure that package name is valid |
michael@0 | 320 | try: |
michael@0 | 321 | validate_resource_hostname(cfg.name) |
michael@0 | 322 | except ValueError, err: |
michael@0 | 323 | print err |
michael@0 | 324 | sys.exit(1) |
michael@0 | 325 | # ensure that this package has an entry |
michael@0 | 326 | if not cfg.name in build.packages: |
michael@0 | 327 | build.packages[cfg.name] = Bunch() |
michael@0 | 328 | # detect duplicated sections |
michael@0 | 329 | if section in build.packages[cfg.name]: |
michael@0 | 330 | raise KeyError("package's section already defined", |
michael@0 | 331 | cfg.name, section) |
michael@0 | 332 | # Register this section (lib, data, tests) |
michael@0 | 333 | build.packages[cfg.name][section] = dirname |
michael@0 | 334 | |
michael@0 | 335 | def add_locale_to_build(cfg): |
michael@0 | 336 | # Bug 730776: Ignore locales for addon-kit, that are only for unit tests |
michael@0 | 337 | if not is_running_tests and cfg.name == "addon-sdk": |
michael@0 | 338 | return |
michael@0 | 339 | |
michael@0 | 340 | path = resolve_dir(cfg, cfg['locale']) |
michael@0 | 341 | files = os.listdir(path) |
michael@0 | 342 | for filename in files: |
michael@0 | 343 | fullpath = os.path.join(path, filename) |
michael@0 | 344 | if os.path.isfile(fullpath) and filename.endswith('.properties'): |
michael@0 | 345 | language = filename[:-len('.properties')] |
michael@0 | 346 | |
michael@0 | 347 | from property_parser import parse_file, MalformedLocaleFileError |
michael@0 | 348 | try: |
michael@0 | 349 | content = parse_file(fullpath) |
michael@0 | 350 | except MalformedLocaleFileError, msg: |
michael@0 | 351 | print msg[0] |
michael@0 | 352 | sys.exit(1) |
michael@0 | 353 | |
michael@0 | 354 | # Merge current locales into global locale hashtable. |
michael@0 | 355 | # Locale files only contains one big JSON object |
michael@0 | 356 | # that act as an hastable of: |
michael@0 | 357 | # "keys to translate" => "translated keys" |
michael@0 | 358 | if language in build.locale: |
michael@0 | 359 | merge = (build.locale[language].items() + |
michael@0 | 360 | content.items()) |
michael@0 | 361 | build.locale[language] = Bunch(merge) |
michael@0 | 362 | else: |
michael@0 | 363 | build.locale[language] = content |
michael@0 | 364 | |
michael@0 | 365 | def add_dep_to_build(dep): |
michael@0 | 366 | dep_cfg = pkg_cfg.packages[dep] |
michael@0 | 367 | add_section_to_build(dep_cfg, "lib", is_code=True) |
michael@0 | 368 | add_section_to_build(dep_cfg, "data", is_data=True) |
michael@0 | 369 | if include_tests and include_dep_tests: |
michael@0 | 370 | add_section_to_build(dep_cfg, "tests", is_code=True) |
michael@0 | 371 | if 'locale' in dep_cfg: |
michael@0 | 372 | add_locale_to_build(dep_cfg) |
michael@0 | 373 | if ("loader" in dep_cfg) and ("loader" not in build): |
michael@0 | 374 | build.loader = "%s/%s" % (dep, |
michael@0 | 375 | dep_cfg.loader) |
michael@0 | 376 | |
michael@0 | 377 | target_cfg = pkg_cfg.packages[target] |
michael@0 | 378 | |
michael@0 | 379 | if include_tests and not include_dep_tests: |
michael@0 | 380 | add_section_to_build(target_cfg, "tests", is_code=True) |
michael@0 | 381 | |
michael@0 | 382 | for dep in deps: |
michael@0 | 383 | add_dep_to_build(dep) |
michael@0 | 384 | |
michael@0 | 385 | if 'loader' not in build: |
michael@0 | 386 | add_dep_to_build(DEFAULT_LOADER) |
michael@0 | 387 | |
michael@0 | 388 | if 'icon' in target_cfg: |
michael@0 | 389 | build['icon'] = os.path.join(target_cfg.root_dir, target_cfg.icon) |
michael@0 | 390 | del target_cfg['icon'] |
michael@0 | 391 | |
michael@0 | 392 | if 'icon64' in target_cfg: |
michael@0 | 393 | build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64) |
michael@0 | 394 | del target_cfg['icon64'] |
michael@0 | 395 | |
michael@0 | 396 | if ('preferences' in target_cfg): |
michael@0 | 397 | build['preferences'] = target_cfg.preferences |
michael@0 | 398 | |
michael@0 | 399 | if 'id' in target_cfg: |
michael@0 | 400 | # NOTE: logic duplicated from buildJID() |
michael@0 | 401 | jid = target_cfg['id'] |
michael@0 | 402 | if not ('@' in jid or jid.startswith('{')): |
michael@0 | 403 | jid += '@jetpack' |
michael@0 | 404 | build['preferencesBranch'] = jid |
michael@0 | 405 | |
michael@0 | 406 | if 'preferences-branch' in target_cfg: |
michael@0 | 407 | # check it's a non-empty, valid branch name |
michael@0 | 408 | preferencesBranch = target_cfg['preferences-branch'] |
michael@0 | 409 | if re.match('^[\w{@}-]+$', preferencesBranch): |
michael@0 | 410 | build['preferencesBranch'] = preferencesBranch |
michael@0 | 411 | elif not is_running_tests: |
michael@0 | 412 | print >>sys.stderr, "IGNORING preferences-branch (not a valid branch name)" |
michael@0 | 413 | |
michael@0 | 414 | return build |
michael@0 | 415 | |
michael@0 | 416 | def _get_files_in_dir(path): |
michael@0 | 417 | data = {} |
michael@0 | 418 | files = os.listdir(path) |
michael@0 | 419 | for filename in files: |
michael@0 | 420 | fullpath = os.path.join(path, filename) |
michael@0 | 421 | if os.path.isdir(fullpath): |
michael@0 | 422 | data[filename] = _get_files_in_dir(fullpath) |
michael@0 | 423 | else: |
michael@0 | 424 | try: |
michael@0 | 425 | info = os.stat(fullpath) |
michael@0 | 426 | data[filename] = ("file", dict(size=info.st_size)) |
michael@0 | 427 | except OSError: |
michael@0 | 428 | pass |
michael@0 | 429 | return ("directory", data) |
michael@0 | 430 | |
michael@0 | 431 | def build_pkg_index(pkg_cfg): |
michael@0 | 432 | pkg_cfg = copy.deepcopy(pkg_cfg) |
michael@0 | 433 | for pkg in pkg_cfg.packages: |
michael@0 | 434 | root_dir = pkg_cfg.packages[pkg].root_dir |
michael@0 | 435 | files = _get_files_in_dir(root_dir) |
michael@0 | 436 | pkg_cfg.packages[pkg].files = files |
michael@0 | 437 | try: |
michael@0 | 438 | readme = open(root_dir + '/README.md').read() |
michael@0 | 439 | pkg_cfg.packages[pkg].readme = readme |
michael@0 | 440 | except IOError: |
michael@0 | 441 | pass |
michael@0 | 442 | del pkg_cfg.packages[pkg].root_dir |
michael@0 | 443 | return pkg_cfg.packages |
michael@0 | 444 | |
michael@0 | 445 | def build_pkg_cfg(root): |
michael@0 | 446 | pkg_cfg = build_config(root, Bunch(name='dummy')) |
michael@0 | 447 | del pkg_cfg.packages['dummy'] |
michael@0 | 448 | return pkg_cfg |
michael@0 | 449 | |
michael@0 | 450 | def call_plugins(pkg_cfg, deps): |
michael@0 | 451 | for dep in deps: |
michael@0 | 452 | dep_cfg = pkg_cfg.packages[dep] |
michael@0 | 453 | dirnames = dep_cfg.get('python-lib', []) |
michael@0 | 454 | for dirname in resolve_dirs(dep_cfg, dirnames): |
michael@0 | 455 | sys.path.append(dirname) |
michael@0 | 456 | module_names = dep_cfg.get('python-plugins', []) |
michael@0 | 457 | for module_name in module_names: |
michael@0 | 458 | module = __import__(module_name) |
michael@0 | 459 | module.init(root_dir=dep_cfg.root_dir) |
michael@0 | 460 | |
michael@0 | 461 | def call_cmdline_tool(env_root, pkg_name): |
michael@0 | 462 | pkg_cfg = build_config(env_root, Bunch(name='dummy')) |
michael@0 | 463 | if pkg_name not in pkg_cfg.packages: |
michael@0 | 464 | print "This tool requires the '%s' package." % pkg_name |
michael@0 | 465 | sys.exit(1) |
michael@0 | 466 | cfg = pkg_cfg.packages[pkg_name] |
michael@0 | 467 | for dirname in resolve_dirs(cfg, cfg['python-lib']): |
michael@0 | 468 | sys.path.append(dirname) |
michael@0 | 469 | module_name = cfg.get('python-cmdline-tool') |
michael@0 | 470 | module = __import__(module_name) |
michael@0 | 471 | module.run() |