1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,939 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +import sys 1.9 +import os 1.10 +import optparse 1.11 +import webbrowser 1.12 +import time 1.13 + 1.14 +from copy import copy 1.15 +import simplejson as json 1.16 +from cuddlefish import packaging 1.17 +from cuddlefish._version import get_versions 1.18 + 1.19 +MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary' 1.20 +MOZRUNNER_BIN_NOT_FOUND_HELP = """ 1.21 +I can't find the application binary in any of its default locations 1.22 +on your system. Please specify one using the -b/--binary option. 1.23 +""" 1.24 + 1.25 +UPDATE_RDF_FILENAME = "%s.update.rdf" 1.26 +XPI_FILENAME = "%s.xpi" 1.27 + 1.28 +usage = """ 1.29 +%prog [options] command [command-specific options] 1.30 + 1.31 +Supported Commands: 1.32 + init - create a sample addon in an empty directory 1.33 + test - run tests 1.34 + run - run program 1.35 + xpi - generate an xpi 1.36 + 1.37 +Internal Commands: 1.38 + testcfx - test the cfx tool 1.39 + testex - test all example code 1.40 + testpkgs - test all installed packages 1.41 + testall - test whole environment 1.42 + 1.43 +Experimental and internal commands and options are not supported and may be 1.44 +changed or removed in the future. 1.45 +""" 1.46 + 1.47 +global_options = [ 1.48 + (("-v", "--verbose",), dict(dest="verbose", 1.49 + help="enable lots of output", 1.50 + action="store_true", 1.51 + default=False)), 1.52 + ] 1.53 + 1.54 +parser_groups = ( 1.55 + ("Supported Command-Specific Options", [ 1.56 + (("", "--update-url",), dict(dest="update_url", 1.57 + help="update URL in install.rdf", 1.58 + metavar=None, 1.59 + default=None, 1.60 + cmds=['xpi'])), 1.61 + (("", "--update-link",), dict(dest="update_link", 1.62 + help="generate update.rdf", 1.63 + metavar=None, 1.64 + default=None, 1.65 + cmds=['xpi'])), 1.66 + (("-p", "--profiledir",), dict(dest="profiledir", 1.67 + help=("profile directory to pass to " 1.68 + "app"), 1.69 + metavar=None, 1.70 + default=None, 1.71 + cmds=['test', 'run', 'testex', 1.72 + 'testpkgs', 'testall'])), 1.73 + (("-b", "--binary",), dict(dest="binary", 1.74 + help="path to app binary", 1.75 + metavar=None, 1.76 + default=None, 1.77 + cmds=['test', 'run', 'testex', 'testpkgs', 1.78 + 'testall'])), 1.79 + (("", "--binary-args",), dict(dest="cmdargs", 1.80 + help=("additional arguments passed to the " 1.81 + "binary"), 1.82 + metavar=None, 1.83 + default=None, 1.84 + cmds=['run', 'test'])), 1.85 + (("", "--dependencies",), dict(dest="dep_tests", 1.86 + help="include tests for all deps", 1.87 + action="store_true", 1.88 + default=False, 1.89 + cmds=['test', 'testex', 'testpkgs', 1.90 + 'testall'])), 1.91 + (("", "--times",), dict(dest="iterations", 1.92 + type="int", 1.93 + help="number of times to run tests", 1.94 + default=1, 1.95 + cmds=['test', 'testex', 'testpkgs', 1.96 + 'testall'])), 1.97 + (("-f", "--filter",), dict(dest="filter", 1.98 + help=("only run tests whose filenames " 1.99 + "match FILENAME and optionally " 1.100 + "match TESTNAME, both regexps"), 1.101 + metavar="FILENAME[:TESTNAME]", 1.102 + default='', 1.103 + cmds=['test', 'testex', 'testaddons', 'testpkgs', 1.104 + 'testall'])), 1.105 + (("-g", "--use-config",), dict(dest="config", 1.106 + help="use named config from local.json", 1.107 + metavar=None, 1.108 + default="default", 1.109 + cmds=['test', 'run', 'xpi', 'testex', 1.110 + 'testpkgs', 'testall'])), 1.111 + (("", "--templatedir",), dict(dest="templatedir", 1.112 + help="XULRunner app/ext. template", 1.113 + metavar=None, 1.114 + default=None, 1.115 + cmds=['run', 'xpi'])), 1.116 + (("", "--package-path",), dict(dest="packagepath", action="append", 1.117 + help="extra directories for package search", 1.118 + metavar=None, 1.119 + default=[], 1.120 + cmds=['run', 'xpi', 'test'])), 1.121 + (("", "--extra-packages",), dict(dest="extra_packages", 1.122 + help=("extra packages to include, " 1.123 + "comma-separated. Default is " 1.124 + "'addon-sdk'."), 1.125 + metavar=None, 1.126 + default="addon-sdk", 1.127 + cmds=['run', 'xpi', 'test', 'testex', 1.128 + 'testpkgs', 'testall', 1.129 + 'testcfx'])), 1.130 + (("", "--pkgdir",), dict(dest="pkgdir", 1.131 + help=("package dir containing " 1.132 + "package.json; default is " 1.133 + "current directory"), 1.134 + metavar=None, 1.135 + default=None, 1.136 + cmds=['run', 'xpi', 'test'])), 1.137 + (("", "--static-args",), dict(dest="static_args", 1.138 + help="extra harness options as JSON", 1.139 + type="json", 1.140 + metavar=None, 1.141 + default="{}", 1.142 + cmds=['run', 'xpi'])), 1.143 + (("", "--parseable",), dict(dest="parseable", 1.144 + help="display test output in a parseable format", 1.145 + action="store_true", 1.146 + default=False, 1.147 + cmds=['run', 'test', 'testex', 'testpkgs', 1.148 + 'testaddons', 'testall'])), 1.149 + ] 1.150 + ), 1.151 + 1.152 + ("Experimental Command-Specific Options", [ 1.153 + (("-a", "--app",), dict(dest="app", 1.154 + help=("app to run: firefox (default), fennec, " 1.155 + "fennec-on-device, xulrunner or " 1.156 + "thunderbird"), 1.157 + metavar=None, 1.158 + type="choice", 1.159 + choices=["firefox", "fennec", 1.160 + "fennec-on-device", "thunderbird", 1.161 + "xulrunner"], 1.162 + default="firefox", 1.163 + cmds=['test', 'run', 'testex', 'testpkgs', 1.164 + 'testall'])), 1.165 + (("-o", "--overload-modules",), dict(dest="overload_modules", 1.166 + help=("Overload JS modules integrated into" 1.167 + " Firefox with the one from your SDK" 1.168 + " repository"), 1.169 + action="store_true", 1.170 + default=False, 1.171 + cmds=['run', 'test', 'testex', 'testpkgs', 1.172 + 'testall'])), 1.173 + (("", "--strip-sdk",), dict(dest="bundle_sdk", 1.174 + help=("Do not ship SDK modules in the xpi"), 1.175 + action="store_false", 1.176 + default=False, 1.177 + cmds=['run', 'test', 'testex', 'testpkgs', 1.178 + 'testall', 'xpi'])), 1.179 + (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk", 1.180 + help=("When --strip-sdk isn't passed, " 1.181 + "force using sdk modules shipped in " 1.182 + "the xpi instead of firefox ones"), 1.183 + action="store_true", 1.184 + default=False, 1.185 + cmds=['run', 'test', 'testex', 'testpkgs', 1.186 + 'testall', 'xpi'])), 1.187 + (("", "--no-run",), dict(dest="no_run", 1.188 + help=("Instead of launching the " 1.189 + "application, just show the command " 1.190 + "for doing so. Use this to launch " 1.191 + "the application in a debugger like " 1.192 + "gdb."), 1.193 + action="store_true", 1.194 + default=False, 1.195 + cmds=['run', 'test'])), 1.196 + (("", "--no-strip-xpi",), dict(dest="no_strip_xpi", 1.197 + help="retain unused modules in XPI", 1.198 + action="store_true", 1.199 + default=False, 1.200 + cmds=['xpi'])), 1.201 + (("", "--force-mobile",), dict(dest="enable_mobile", 1.202 + help="Force compatibility with Firefox Mobile", 1.203 + action="store_true", 1.204 + default=False, 1.205 + cmds=['run', 'test', 'xpi', 'testall'])), 1.206 + (("", "--mobile-app",), dict(dest="mobile_app_name", 1.207 + help=("Name of your Android application to " 1.208 + "use. Possible values: 'firefox', " 1.209 + "'firefox_beta', 'fennec_aurora', " 1.210 + "'fennec' (for nightly)."), 1.211 + metavar=None, 1.212 + default=None, 1.213 + cmds=['run', 'test', 'testall'])), 1.214 + (("", "--harness-option",), dict(dest="extra_harness_option_args", 1.215 + help=("Extra properties added to " 1.216 + "harness-options.json"), 1.217 + action="append", 1.218 + metavar="KEY=VALUE", 1.219 + default=[], 1.220 + cmds=['xpi'])), 1.221 + (("", "--stop-on-error",), dict(dest="stopOnError", 1.222 + help="Stop running tests after the first failure", 1.223 + action="store_true", 1.224 + metavar=None, 1.225 + default=False, 1.226 + cmds=['test', 'testex', 'testpkgs'])), 1.227 + (("", "--check-memory",), dict(dest="check_memory", 1.228 + help="attempts to detect leaked compartments after a test run", 1.229 + action="store_true", 1.230 + default=False, 1.231 + cmds=['test', 'testpkgs', 'testaddons', 1.232 + 'testall'])), 1.233 + (("", "--output-file",), dict(dest="output_file", 1.234 + help="Where to put the finished .xpi", 1.235 + default=None, 1.236 + cmds=['xpi'])), 1.237 + (("", "--manifest-overload",), dict(dest="manifest_overload", 1.238 + help="JSON file to overload package.json properties", 1.239 + default=None, 1.240 + cmds=['xpi'])), 1.241 + ] 1.242 + ), 1.243 + 1.244 + ("Internal Command-Specific Options", [ 1.245 + (("", "--addons",), dict(dest="addons", 1.246 + help=("paths of addons to install, " 1.247 + "comma-separated"), 1.248 + metavar=None, 1.249 + default=None, 1.250 + cmds=['test', 'run', 'testex', 'testpkgs', 1.251 + 'testall'])), 1.252 + (("", "--test-runner-pkg",), dict(dest="test_runner_pkg", 1.253 + help=("name of package " 1.254 + "containing test runner " 1.255 + "program (default is " 1.256 + "test-harness)"), 1.257 + default="addon-sdk", 1.258 + cmds=['test', 'testex', 'testpkgs', 1.259 + 'testall'])), 1.260 + # --keydir was removed in 1.0b5, but we keep it around in the options 1.261 + # parser to make life easier for frontends like FlightDeck which 1.262 + # might still pass it. It can go away once the frontends are updated. 1.263 + (("", "--keydir",), dict(dest="keydir", 1.264 + help=("obsolete, ignored"), 1.265 + metavar=None, 1.266 + default=None, 1.267 + cmds=['test', 'run', 'xpi', 'testex', 1.268 + 'testpkgs', 'testall'])), 1.269 + (("", "--e10s",), dict(dest="enable_e10s", 1.270 + help="enable out-of-process Jetpacks", 1.271 + action="store_true", 1.272 + default=False, 1.273 + cmds=['test', 'run', 'testex', 'testpkgs'])), 1.274 + (("", "--logfile",), dict(dest="logfile", 1.275 + help="log console output to file", 1.276 + metavar=None, 1.277 + default=None, 1.278 + cmds=['run', 'test', 'testex', 'testpkgs'])), 1.279 + # TODO: This should default to true once our memory debugging 1.280 + # issues are resolved; see bug 592774. 1.281 + (("", "--profile-memory",), dict(dest="profileMemory", 1.282 + help=("profile memory usage " 1.283 + "(default is false)"), 1.284 + type="int", 1.285 + action="store", 1.286 + default=0, 1.287 + cmds=['test', 'testex', 'testpkgs', 1.288 + 'testall'])), 1.289 + ] 1.290 + ), 1.291 + ) 1.292 + 1.293 +def find_parent_package(cur_dir): 1.294 + tail = True 1.295 + while tail: 1.296 + if os.path.exists(os.path.join(cur_dir, 'package.json')): 1.297 + return cur_dir 1.298 + cur_dir, tail = os.path.split(cur_dir) 1.299 + return None 1.300 + 1.301 +def check_json(option, opt, value): 1.302 + # We return the parsed JSON here; see bug 610816 for background on why. 1.303 + try: 1.304 + return json.loads(value) 1.305 + except ValueError: 1.306 + raise optparse.OptionValueError("Option %s must be JSON." % opt) 1.307 + 1.308 +class CfxOption(optparse.Option): 1.309 + TYPES = optparse.Option.TYPES + ('json',) 1.310 + TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) 1.311 + TYPE_CHECKER['json'] = check_json 1.312 + 1.313 +def parse_args(arguments, global_options, usage, version, parser_groups, 1.314 + defaults=None): 1.315 + parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption, 1.316 + version=version) 1.317 + 1.318 + def name_cmp(a, b): 1.319 + # a[0] = name sequence 1.320 + # a[0][0] = short name (possibly empty string) 1.321 + # a[0][1] = long name 1.322 + names = [] 1.323 + for seq in (a, b): 1.324 + names.append(seq[0][0][1:] if seq[0][0] else seq[0][1][2:]) 1.325 + return cmp(*names) 1.326 + 1.327 + global_options.sort(name_cmp) 1.328 + for names, opts in global_options: 1.329 + parser.add_option(*names, **opts) 1.330 + 1.331 + for group_name, options in parser_groups: 1.332 + group = optparse.OptionGroup(parser, group_name) 1.333 + options.sort(name_cmp) 1.334 + for names, opts in options: 1.335 + if 'cmds' in opts: 1.336 + cmds = opts['cmds'] 1.337 + del opts['cmds'] 1.338 + cmds.sort() 1.339 + if not 'help' in opts: 1.340 + opts['help'] = "" 1.341 + opts['help'] += " (%s)" % ", ".join(cmds) 1.342 + group.add_option(*names, **opts) 1.343 + parser.add_option_group(group) 1.344 + 1.345 + if defaults: 1.346 + parser.set_defaults(**defaults) 1.347 + 1.348 + (options, args) = parser.parse_args(args=arguments) 1.349 + 1.350 + if not args: 1.351 + parser.print_help() 1.352 + parser.exit() 1.353 + 1.354 + return (options, args) 1.355 + 1.356 +# all tests emit progress messages to stderr, not stdout. (the mozrunner 1.357 +# console output goes to stderr and is hard to change, and 1.358 +# unittest.TextTestRunner prefers stderr, so we send everything else there 1.359 +# too, to keep all the messages in order) 1.360 + 1.361 +def test_all(env_root, defaults): 1.362 + fail = False 1.363 + 1.364 + starttime = time.time() 1.365 + 1.366 + if not defaults['filter']: 1.367 + print >>sys.stderr, "Testing cfx..." 1.368 + sys.stderr.flush() 1.369 + result = test_cfx(env_root, defaults['verbose']) 1.370 + if result.failures or result.errors: 1.371 + fail = True 1.372 + 1.373 + if not fail or not defaults.get("stopOnError"): 1.374 + print >>sys.stderr, "Testing all examples..." 1.375 + sys.stderr.flush() 1.376 + 1.377 + try: 1.378 + test_all_examples(env_root, defaults) 1.379 + except SystemExit, e: 1.380 + fail = (e.code != 0) or fail 1.381 + 1.382 + if not fail or not defaults.get("stopOnError"): 1.383 + print >>sys.stderr, "Testing all unit-test addons..." 1.384 + sys.stderr.flush() 1.385 + 1.386 + try: 1.387 + test_all_testaddons(env_root, defaults) 1.388 + except SystemExit, e: 1.389 + fail = (e.code != 0) or fail 1.390 + 1.391 + if not fail or not defaults.get("stopOnError"): 1.392 + print >>sys.stderr, "Testing all packages..." 1.393 + sys.stderr.flush() 1.394 + try: 1.395 + test_all_packages(env_root, defaults) 1.396 + except SystemExit, e: 1.397 + fail = (e.code != 0) or fail 1.398 + 1.399 + print >>sys.stderr, "Total time for all tests: %f seconds" % (time.time() - starttime) 1.400 + 1.401 + if fail: 1.402 + print >>sys.stderr, "Some tests were unsuccessful." 1.403 + sys.exit(1) 1.404 + print >>sys.stderr, "All tests were successful. Ship it!" 1.405 + sys.exit(0) 1.406 + 1.407 +def test_cfx(env_root, verbose): 1.408 + import cuddlefish.tests 1.409 + 1.410 + # tests write to stderr. flush everything before and after to avoid 1.411 + # confusion later. 1.412 + sys.stdout.flush(); sys.stderr.flush() 1.413 + olddir = os.getcwd() 1.414 + os.chdir(env_root) 1.415 + retval = cuddlefish.tests.run(verbose) 1.416 + os.chdir(olddir) 1.417 + sys.stdout.flush(); sys.stderr.flush() 1.418 + return retval 1.419 + 1.420 +def test_all_testaddons(env_root, defaults): 1.421 + addons_dir = os.path.join(env_root, "test", "addons") 1.422 + addons = [dirname for dirname in os.listdir(addons_dir) 1.423 + if os.path.isdir(os.path.join(addons_dir, dirname))] 1.424 + addons.sort() 1.425 + fail = False 1.426 + for dirname in addons: 1.427 + if (not defaults['filter'].split(":")[0] in dirname): 1.428 + continue 1.429 + 1.430 + print >>sys.stderr, "Testing %s..." % dirname 1.431 + sys.stderr.flush() 1.432 + try: 1.433 + run(arguments=["run", 1.434 + "--pkgdir", 1.435 + os.path.join(addons_dir, dirname)], 1.436 + defaults=defaults, 1.437 + env_root=env_root) 1.438 + except SystemExit, e: 1.439 + fail = (e.code != 0) or fail 1.440 + if fail and defaults.get("stopOnError"): 1.441 + break 1.442 + 1.443 + if fail: 1.444 + print >>sys.stderr, "Some test addons tests were unsuccessful." 1.445 + sys.exit(-1) 1.446 + 1.447 +def test_all_examples(env_root, defaults): 1.448 + examples_dir = os.path.join(env_root, "examples") 1.449 + examples = [dirname for dirname in os.listdir(examples_dir) 1.450 + if os.path.isdir(os.path.join(examples_dir, dirname))] 1.451 + examples.sort() 1.452 + fail = False 1.453 + for dirname in examples: 1.454 + if (not defaults['filter'].split(":")[0] in dirname): 1.455 + continue 1.456 + 1.457 + print >>sys.stderr, "Testing %s..." % dirname 1.458 + sys.stderr.flush() 1.459 + try: 1.460 + run(arguments=["test", 1.461 + "--pkgdir", 1.462 + os.path.join(examples_dir, dirname)], 1.463 + defaults=defaults, 1.464 + env_root=env_root) 1.465 + except SystemExit, e: 1.466 + fail = (e.code != 0) or fail 1.467 + if fail and defaults.get("stopOnError"): 1.468 + break 1.469 + 1.470 + if fail: 1.471 + print >>sys.stderr, "Some examples tests were unsuccessful." 1.472 + sys.exit(-1) 1.473 + 1.474 +def test_all_packages(env_root, defaults): 1.475 + packages_dir = os.path.join(env_root, "packages") 1.476 + if os.path.isdir(packages_dir): 1.477 + packages = [dirname for dirname in os.listdir(packages_dir) 1.478 + if os.path.isdir(os.path.join(packages_dir, dirname))] 1.479 + else: 1.480 + packages = [] 1.481 + packages.append(env_root) 1.482 + packages.sort() 1.483 + print >>sys.stderr, "Testing all available packages: %s." % (", ".join(packages)) 1.484 + sys.stderr.flush() 1.485 + fail = False 1.486 + for dirname in packages: 1.487 + print >>sys.stderr, "Testing %s..." % dirname 1.488 + sys.stderr.flush() 1.489 + try: 1.490 + run(arguments=["test", 1.491 + "--pkgdir", 1.492 + os.path.join(packages_dir, dirname)], 1.493 + defaults=defaults, 1.494 + env_root=env_root) 1.495 + except SystemExit, e: 1.496 + fail = (e.code != 0) or fail 1.497 + if fail and defaults.get('stopOnError'): 1.498 + break 1.499 + if fail: 1.500 + print >>sys.stderr, "Some package tests were unsuccessful." 1.501 + sys.exit(-1) 1.502 + 1.503 +def get_config_args(name, env_root): 1.504 + local_json = os.path.join(env_root, "local.json") 1.505 + if not (os.path.exists(local_json) and 1.506 + os.path.isfile(local_json)): 1.507 + if name == "default": 1.508 + return [] 1.509 + else: 1.510 + print >>sys.stderr, "File does not exist: %s" % local_json 1.511 + sys.exit(1) 1.512 + local_json = packaging.load_json_file(local_json) 1.513 + if 'configs' not in local_json: 1.514 + print >>sys.stderr, "'configs' key not found in local.json." 1.515 + sys.exit(1) 1.516 + if name not in local_json.configs: 1.517 + if name == "default": 1.518 + return [] 1.519 + else: 1.520 + print >>sys.stderr, "No config found for '%s'." % name 1.521 + sys.exit(1) 1.522 + config = local_json.configs[name] 1.523 + if type(config) != list: 1.524 + print >>sys.stderr, "Config for '%s' must be a list of strings." % name 1.525 + sys.exit(1) 1.526 + return config 1.527 + 1.528 +def initializer(env_root, args, out=sys.stdout, err=sys.stderr): 1.529 + from templates import PACKAGE_JSON, TEST_MAIN_JS 1.530 + from preflight import create_jid 1.531 + path = os.getcwd() 1.532 + addon = os.path.basename(path) 1.533 + # if more than two arguments 1.534 + if len(args) > 2: 1.535 + print >>err, 'Too many arguments.' 1.536 + return {"result":1} 1.537 + if len(args) == 2: 1.538 + path = os.path.join(path,args[1]) 1.539 + try: 1.540 + os.mkdir(path) 1.541 + print >>out, '*', args[1], 'package directory created' 1.542 + except OSError: 1.543 + print >>out, '*', args[1], 'already exists, testing if directory is empty' 1.544 + # avoid clobbering existing files, but we tolerate things like .git 1.545 + existing = [fn for fn in os.listdir(path) if not fn.startswith(".")] 1.546 + if existing: 1.547 + print >>err, 'This command must be run in an empty directory.' 1.548 + return {"result":1} 1.549 + for d in ['lib','data','test']: 1.550 + os.mkdir(os.path.join(path,d)) 1.551 + print >>out, '*', d, 'directory created' 1.552 + jid = create_jid() 1.553 + print >>out, '* generated jID automatically:', jid 1.554 + open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(), 1.555 + 'title':addon, 1.556 + 'id':jid }) 1.557 + print >>out, '* package.json written' 1.558 + open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS) 1.559 + print >>out, '* test/test-main.js written' 1.560 + open(os.path.join(path,'lib','main.js'),'w').write('') 1.561 + print >>out, '* lib/main.js written' 1.562 + if len(args) == 1: 1.563 + print >>out, '\nYour sample add-on is now ready.' 1.564 + print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' 1.565 + else: 1.566 + print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' 1.567 + print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' 1.568 + return {"result":0, "jid":jid} 1.569 + 1.570 +def buildJID(target_cfg): 1.571 + if "id" in target_cfg: 1.572 + jid = target_cfg["id"] 1.573 + else: 1.574 + import uuid 1.575 + jid = str(uuid.uuid4()) 1.576 + if not ("@" in jid or jid.startswith("{")): 1.577 + jid = jid + "@jetpack" 1.578 + return jid 1.579 + 1.580 +def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, 1.581 + defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'), 1.582 + stdout=sys.stdout): 1.583 + versions = get_versions() 1.584 + sdk_version = versions["version"] 1.585 + display_version = "Add-on SDK %s (%s)" % (sdk_version, versions["full"]) 1.586 + parser_kwargs = dict(arguments=arguments, 1.587 + global_options=global_options, 1.588 + parser_groups=parser_groups, 1.589 + usage=usage, 1.590 + version=display_version, 1.591 + defaults=defaults) 1.592 + 1.593 + (options, args) = parse_args(**parser_kwargs) 1.594 + 1.595 + config_args = get_config_args(options.config, env_root); 1.596 + 1.597 + # reparse configs with arguments from local.json 1.598 + if config_args: 1.599 + parser_kwargs['arguments'] += config_args 1.600 + (options, args) = parse_args(**parser_kwargs) 1.601 + 1.602 + command = args[0] 1.603 + 1.604 + if command == "init": 1.605 + initializer(env_root, args) 1.606 + return 1.607 + if command == "testpkgs": 1.608 + test_all_packages(env_root, defaults=options.__dict__) 1.609 + return 1.610 + elif command == "testaddons": 1.611 + test_all_testaddons(env_root, defaults=options.__dict__) 1.612 + return 1.613 + elif command == "testex": 1.614 + test_all_examples(env_root, defaults=options.__dict__) 1.615 + return 1.616 + elif command == "testall": 1.617 + test_all(env_root, defaults=options.__dict__) 1.618 + return 1.619 + elif command == "testcfx": 1.620 + if options.filter: 1.621 + print >>sys.stderr, "The filter option is not valid with the testcfx command" 1.622 + return 1.623 + test_cfx(env_root, options.verbose) 1.624 + return 1.625 + elif command not in ["xpi", "test", "run"]: 1.626 + print >>sys.stderr, "Unknown command: %s" % command 1.627 + print >>sys.stderr, "Try using '--help' for assistance." 1.628 + sys.exit(1) 1.629 + 1.630 + target_cfg_json = None 1.631 + if not target_cfg: 1.632 + if not options.pkgdir: 1.633 + options.pkgdir = find_parent_package(os.getcwd()) 1.634 + if not options.pkgdir: 1.635 + print >>sys.stderr, ("cannot find 'package.json' in the" 1.636 + " current directory or any parent.") 1.637 + sys.exit(1) 1.638 + else: 1.639 + options.pkgdir = os.path.abspath(options.pkgdir) 1.640 + if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): 1.641 + print >>sys.stderr, ("cannot find 'package.json' in" 1.642 + " %s." % options.pkgdir) 1.643 + sys.exit(1) 1.644 + 1.645 + target_cfg_json = os.path.join(options.pkgdir, 'package.json') 1.646 + target_cfg = packaging.get_config_in_dir(options.pkgdir) 1.647 + 1.648 + if options.manifest_overload: 1.649 + for k, v in packaging.load_json_file(options.manifest_overload).items(): 1.650 + target_cfg[k] = v 1.651 + 1.652 + # At this point, we're either building an XPI or running Jetpack code in 1.653 + # a Mozilla application (which includes running tests). 1.654 + 1.655 + use_main = False 1.656 + inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory'] 1.657 + enforce_timeouts = False 1.658 + 1.659 + if command == "xpi": 1.660 + use_main = True 1.661 + elif command == "test": 1.662 + if 'tests' not in target_cfg: 1.663 + target_cfg['tests'] = [] 1.664 + inherited_options.extend(['iterations', 'filter', 'profileMemory', 1.665 + 'stopOnError']) 1.666 + enforce_timeouts = True 1.667 + elif command == "run": 1.668 + use_main = True 1.669 + else: 1.670 + assert 0, "shouldn't get here" 1.671 + 1.672 + if use_main and 'main' not in target_cfg: 1.673 + # If the user supplies a template dir, then the main 1.674 + # program may be contained in the template. 1.675 + if not options.templatedir: 1.676 + print >>sys.stderr, "package.json does not have a 'main' entry." 1.677 + sys.exit(1) 1.678 + 1.679 + if not pkg_cfg: 1.680 + pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath) 1.681 + 1.682 + target = target_cfg.name 1.683 + 1.684 + # TODO: Consider keeping a cache of dynamic UUIDs, based 1.685 + # on absolute filesystem pathname, in the root directory 1.686 + # or something. 1.687 + if command in ('xpi', 'run'): 1.688 + from cuddlefish.preflight import preflight_config 1.689 + if target_cfg_json: 1.690 + config_was_ok, modified = preflight_config(target_cfg, 1.691 + target_cfg_json) 1.692 + if not config_was_ok: 1.693 + if modified: 1.694 + # we need to re-read package.json . The safest approach 1.695 + # is to re-run the "cfx xpi"/"cfx run" command. 1.696 + print >>sys.stderr, ("package.json modified: please re-run" 1.697 + " 'cfx %s'" % command) 1.698 + else: 1.699 + print >>sys.stderr, ("package.json needs modification:" 1.700 + " please update it and then re-run" 1.701 + " 'cfx %s'" % command) 1.702 + sys.exit(1) 1.703 + # if we make it this far, we have a JID 1.704 + else: 1.705 + assert command == "test" 1.706 + 1.707 + jid = buildJID(target_cfg) 1.708 + 1.709 + targets = [target] 1.710 + if command == "test": 1.711 + targets.append(options.test_runner_pkg) 1.712 + 1.713 + extra_packages = [] 1.714 + if options.extra_packages: 1.715 + extra_packages = options.extra_packages.split(",") 1.716 + if extra_packages: 1.717 + targets.extend(extra_packages) 1.718 + target_cfg.extra_dependencies = extra_packages 1.719 + 1.720 + deps = packaging.get_deps_for_targets(pkg_cfg, targets) 1.721 + 1.722 + from cuddlefish.manifest import build_manifest, ModuleNotFoundError, \ 1.723 + BadChromeMarkerError 1.724 + # Figure out what loader files should be scanned. This is normally 1.725 + # computed inside packaging.generate_build_for_target(), by the first 1.726 + # dependent package that defines a "loader" property in its package.json. 1.727 + # This property is interpreted as a filename relative to the top of that 1.728 + # file, and stored as a path in build.loader . generate_build_for_target() 1.729 + # cannot be called yet (it needs the list of used_deps that 1.730 + # build_manifest() computes, but build_manifest() needs the list of 1.731 + # loader files that it computes). We could duplicate or factor out this 1.732 + # build.loader logic, but that would be messy, so instead we hard-code 1.733 + # the choice of loader for manifest-generation purposes. In practice, 1.734 + # this means that alternative loaders probably won't work with 1.735 + # --strip-xpi. 1.736 + assert packaging.DEFAULT_LOADER == "addon-sdk" 1.737 + assert pkg_cfg.packages["addon-sdk"].loader == "lib/sdk/loader/cuddlefish.js" 1.738 + cuddlefish_js_path = os.path.join(pkg_cfg.packages["addon-sdk"].root_dir, 1.739 + "lib", "sdk", "loader", "cuddlefish.js") 1.740 + loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)] 1.741 + scan_tests = command == "test" 1.742 + test_filter_re = None 1.743 + if scan_tests and options.filter: 1.744 + test_filter_re = options.filter 1.745 + if ":" in options.filter: 1.746 + test_filter_re = options.filter.split(":")[0] 1.747 + try: 1.748 + manifest = build_manifest(target_cfg, pkg_cfg, deps, 1.749 + scan_tests, test_filter_re, 1.750 + loader_modules) 1.751 + except ModuleNotFoundError, e: 1.752 + print str(e) 1.753 + sys.exit(1) 1.754 + except BadChromeMarkerError, e: 1.755 + # An error had already been displayed on stderr in manifest code 1.756 + sys.exit(1) 1.757 + used_deps = manifest.get_used_packages() 1.758 + if command == "test": 1.759 + # The test runner doesn't appear to link against any actual packages, 1.760 + # because it loads everything at runtime (invisible to the linker). 1.761 + # If we believe that, we won't set up URI mappings for anything, and 1.762 + # tests won't be able to run. 1.763 + used_deps = deps 1.764 + for xp in extra_packages: 1.765 + if xp not in used_deps: 1.766 + used_deps.append(xp) 1.767 + 1.768 + build = packaging.generate_build_for_target( 1.769 + pkg_cfg, target, used_deps, 1.770 + include_dep_tests=options.dep_tests, 1.771 + is_running_tests=(command == "test") 1.772 + ) 1.773 + 1.774 + harness_options = { 1.775 + 'jetpackID': jid, 1.776 + 'staticArgs': options.static_args, 1.777 + 'name': target, 1.778 + } 1.779 + 1.780 + harness_options.update(build) 1.781 + 1.782 + # When cfx is run from sdk root directory, we will strip sdk modules and 1.783 + # override them with local modules. 1.784 + # So that integration tools will continue to work and use local modules 1.785 + if os.getcwd() == env_root: 1.786 + options.bundle_sdk = True 1.787 + options.force_use_bundled_sdk = False 1.788 + options.overload_modules = True 1.789 + 1.790 + extra_environment = {} 1.791 + if command == "test": 1.792 + # This should be contained in the test runner package. 1.793 + # maybe just do: target_cfg.main = 'test-harness/run-tests' 1.794 + harness_options['main'] = 'sdk/test/runner' 1.795 + harness_options['mainPath'] = 'sdk/test/runner' 1.796 + else: 1.797 + harness_options['main'] = target_cfg.get('main') 1.798 + harness_options['mainPath'] = manifest.top_path 1.799 + extra_environment["CFX_COMMAND"] = command 1.800 + 1.801 + for option in inherited_options: 1.802 + harness_options[option] = getattr(options, option) 1.803 + 1.804 + harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps) 1.805 + 1.806 + harness_options['sdkVersion'] = sdk_version 1.807 + 1.808 + packaging.call_plugins(pkg_cfg, used_deps) 1.809 + 1.810 + retval = 0 1.811 + 1.812 + if options.templatedir: 1.813 + app_extension_dir = os.path.abspath(options.templatedir) 1.814 + elif os.path.exists(os.path.join(options.pkgdir, "app-extension")): 1.815 + app_extension_dir = os.path.join(options.pkgdir, "app-extension") 1.816 + else: 1.817 + mydir = os.path.dirname(os.path.abspath(__file__)) 1.818 + app_extension_dir = os.path.join(mydir, "../../app-extension") 1.819 + 1.820 + if target_cfg.get('preferences'): 1.821 + harness_options['preferences'] = target_cfg.get('preferences') 1.822 + 1.823 + # Do not add entries for SDK modules 1.824 + harness_options['manifest'] = manifest.get_harness_options_manifest(False) 1.825 + 1.826 + # Gives an hint to tell if sdk modules are bundled or not 1.827 + harness_options['is-sdk-bundled'] = options.bundle_sdk or options.no_strip_xpi 1.828 + 1.829 + if options.force_use_bundled_sdk: 1.830 + if not harness_options['is-sdk-bundled']: 1.831 + print >>sys.stderr, ("--force-use-bundled-sdk " 1.832 + "can't be used if sdk isn't bundled.") 1.833 + sys.exit(1) 1.834 + if options.overload_modules: 1.835 + print >>sys.stderr, ("--force-use-bundled-sdk and --overload-modules " 1.836 + "can't be used at the same time.") 1.837 + sys.exit(1) 1.838 + # Pass a flag in order to force using sdk modules shipped in the xpi 1.839 + harness_options['force-use-bundled-sdk'] = True 1.840 + 1.841 + # Pass the list of absolute path for all test modules 1.842 + if command == "test": 1.843 + harness_options['allTestModules'] = manifest.get_all_test_modules() 1.844 + if len(harness_options['allTestModules']) == 0: 1.845 + sys.exit(0) 1.846 + 1.847 + from cuddlefish.rdf import gen_manifest, RDFUpdate 1.848 + 1.849 + manifest_rdf = gen_manifest(template_root_dir=app_extension_dir, 1.850 + target_cfg=target_cfg, 1.851 + jid=jid, 1.852 + update_url=options.update_url, 1.853 + bootstrap=True, 1.854 + enable_mobile=options.enable_mobile) 1.855 + 1.856 + if command == "xpi" and options.update_link: 1.857 + if not options.update_link.startswith("https"): 1.858 + raise optparse.OptionValueError("--update-link must start with 'https': %s" % options.update_link) 1.859 + rdf_name = UPDATE_RDF_FILENAME % target_cfg.name 1.860 + print >>stdout, "Exporting update description to %s." % rdf_name 1.861 + update = RDFUpdate() 1.862 + update.add(manifest_rdf, options.update_link) 1.863 + open(rdf_name, "w").write(str(update)) 1.864 + 1.865 + # ask the manifest what files were used, so we can construct an XPI 1.866 + # without the rest. This will include the loader (and everything it 1.867 + # uses) because of the "loader_modules" starting points we passed to 1.868 + # build_manifest earlier 1.869 + used_files = None 1.870 + if command == "xpi": 1.871 + used_files = set(manifest.get_used_files(options.bundle_sdk)) 1.872 + 1.873 + if options.no_strip_xpi: 1.874 + used_files = None # disables the filter, includes all files 1.875 + 1.876 + if command == 'xpi': 1.877 + from cuddlefish.xpi import build_xpi 1.878 + # Generate extra options 1.879 + extra_harness_options = {} 1.880 + for kv in options.extra_harness_option_args: 1.881 + key,value = kv.split("=", 1) 1.882 + extra_harness_options[key] = value 1.883 + # Generate xpi filepath 1.884 + if options.output_file: 1.885 + xpi_path = options.output_file 1.886 + else: 1.887 + xpi_path = XPI_FILENAME % target_cfg.name 1.888 + 1.889 + print >>stdout, "Exporting extension to %s." % xpi_path 1.890 + build_xpi(template_root_dir=app_extension_dir, 1.891 + manifest=manifest_rdf, 1.892 + xpi_path=xpi_path, 1.893 + harness_options=harness_options, 1.894 + limit_to=used_files, 1.895 + extra_harness_options=extra_harness_options, 1.896 + bundle_sdk=True, 1.897 + pkgdir=options.pkgdir) 1.898 + else: 1.899 + from cuddlefish.runner import run_app 1.900 + 1.901 + if options.profiledir: 1.902 + options.profiledir = os.path.expanduser(options.profiledir) 1.903 + options.profiledir = os.path.abspath(options.profiledir) 1.904 + 1.905 + if options.addons is not None: 1.906 + options.addons = options.addons.split(",") 1.907 + 1.908 + try: 1.909 + retval = run_app(harness_root_dir=app_extension_dir, 1.910 + manifest_rdf=manifest_rdf, 1.911 + harness_options=harness_options, 1.912 + app_type=options.app, 1.913 + binary=options.binary, 1.914 + profiledir=options.profiledir, 1.915 + verbose=options.verbose, 1.916 + parseable=options.parseable, 1.917 + enforce_timeouts=enforce_timeouts, 1.918 + logfile=options.logfile, 1.919 + addons=options.addons, 1.920 + args=options.cmdargs, 1.921 + extra_environment=extra_environment, 1.922 + norun=options.no_run, 1.923 + used_files=used_files, 1.924 + enable_mobile=options.enable_mobile, 1.925 + mobile_app_name=options.mobile_app_name, 1.926 + env_root=env_root, 1.927 + is_running_tests=(command == "test"), 1.928 + overload_modules=options.overload_modules, 1.929 + bundle_sdk=options.bundle_sdk, 1.930 + pkgdir=options.pkgdir) 1.931 + except ValueError, e: 1.932 + print "" 1.933 + print "A given cfx option has an inappropriate value:" 1.934 + print >>sys.stderr, " " + " \n ".join(str(e).split("\n")) 1.935 + retval = -1 1.936 + except Exception, e: 1.937 + if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): 1.938 + print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() 1.939 + retval = -1 1.940 + else: 1.941 + raise 1.942 + sys.exit(retval)