addon-sdk/source/python-lib/cuddlefish/__init__.py

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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 sys
michael@0 6 import os
michael@0 7 import optparse
michael@0 8 import webbrowser
michael@0 9 import time
michael@0 10
michael@0 11 from copy import copy
michael@0 12 import simplejson as json
michael@0 13 from cuddlefish import packaging
michael@0 14 from cuddlefish._version import get_versions
michael@0 15
michael@0 16 MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary'
michael@0 17 MOZRUNNER_BIN_NOT_FOUND_HELP = """
michael@0 18 I can't find the application binary in any of its default locations
michael@0 19 on your system. Please specify one using the -b/--binary option.
michael@0 20 """
michael@0 21
michael@0 22 UPDATE_RDF_FILENAME = "%s.update.rdf"
michael@0 23 XPI_FILENAME = "%s.xpi"
michael@0 24
michael@0 25 usage = """
michael@0 26 %prog [options] command [command-specific options]
michael@0 27
michael@0 28 Supported Commands:
michael@0 29 init - create a sample addon in an empty directory
michael@0 30 test - run tests
michael@0 31 run - run program
michael@0 32 xpi - generate an xpi
michael@0 33
michael@0 34 Internal Commands:
michael@0 35 testcfx - test the cfx tool
michael@0 36 testex - test all example code
michael@0 37 testpkgs - test all installed packages
michael@0 38 testall - test whole environment
michael@0 39
michael@0 40 Experimental and internal commands and options are not supported and may be
michael@0 41 changed or removed in the future.
michael@0 42 """
michael@0 43
michael@0 44 global_options = [
michael@0 45 (("-v", "--verbose",), dict(dest="verbose",
michael@0 46 help="enable lots of output",
michael@0 47 action="store_true",
michael@0 48 default=False)),
michael@0 49 ]
michael@0 50
michael@0 51 parser_groups = (
michael@0 52 ("Supported Command-Specific Options", [
michael@0 53 (("", "--update-url",), dict(dest="update_url",
michael@0 54 help="update URL in install.rdf",
michael@0 55 metavar=None,
michael@0 56 default=None,
michael@0 57 cmds=['xpi'])),
michael@0 58 (("", "--update-link",), dict(dest="update_link",
michael@0 59 help="generate update.rdf",
michael@0 60 metavar=None,
michael@0 61 default=None,
michael@0 62 cmds=['xpi'])),
michael@0 63 (("-p", "--profiledir",), dict(dest="profiledir",
michael@0 64 help=("profile directory to pass to "
michael@0 65 "app"),
michael@0 66 metavar=None,
michael@0 67 default=None,
michael@0 68 cmds=['test', 'run', 'testex',
michael@0 69 'testpkgs', 'testall'])),
michael@0 70 (("-b", "--binary",), dict(dest="binary",
michael@0 71 help="path to app binary",
michael@0 72 metavar=None,
michael@0 73 default=None,
michael@0 74 cmds=['test', 'run', 'testex', 'testpkgs',
michael@0 75 'testall'])),
michael@0 76 (("", "--binary-args",), dict(dest="cmdargs",
michael@0 77 help=("additional arguments passed to the "
michael@0 78 "binary"),
michael@0 79 metavar=None,
michael@0 80 default=None,
michael@0 81 cmds=['run', 'test'])),
michael@0 82 (("", "--dependencies",), dict(dest="dep_tests",
michael@0 83 help="include tests for all deps",
michael@0 84 action="store_true",
michael@0 85 default=False,
michael@0 86 cmds=['test', 'testex', 'testpkgs',
michael@0 87 'testall'])),
michael@0 88 (("", "--times",), dict(dest="iterations",
michael@0 89 type="int",
michael@0 90 help="number of times to run tests",
michael@0 91 default=1,
michael@0 92 cmds=['test', 'testex', 'testpkgs',
michael@0 93 'testall'])),
michael@0 94 (("-f", "--filter",), dict(dest="filter",
michael@0 95 help=("only run tests whose filenames "
michael@0 96 "match FILENAME and optionally "
michael@0 97 "match TESTNAME, both regexps"),
michael@0 98 metavar="FILENAME[:TESTNAME]",
michael@0 99 default='',
michael@0 100 cmds=['test', 'testex', 'testaddons', 'testpkgs',
michael@0 101 'testall'])),
michael@0 102 (("-g", "--use-config",), dict(dest="config",
michael@0 103 help="use named config from local.json",
michael@0 104 metavar=None,
michael@0 105 default="default",
michael@0 106 cmds=['test', 'run', 'xpi', 'testex',
michael@0 107 'testpkgs', 'testall'])),
michael@0 108 (("", "--templatedir",), dict(dest="templatedir",
michael@0 109 help="XULRunner app/ext. template",
michael@0 110 metavar=None,
michael@0 111 default=None,
michael@0 112 cmds=['run', 'xpi'])),
michael@0 113 (("", "--package-path",), dict(dest="packagepath", action="append",
michael@0 114 help="extra directories for package search",
michael@0 115 metavar=None,
michael@0 116 default=[],
michael@0 117 cmds=['run', 'xpi', 'test'])),
michael@0 118 (("", "--extra-packages",), dict(dest="extra_packages",
michael@0 119 help=("extra packages to include, "
michael@0 120 "comma-separated. Default is "
michael@0 121 "'addon-sdk'."),
michael@0 122 metavar=None,
michael@0 123 default="addon-sdk",
michael@0 124 cmds=['run', 'xpi', 'test', 'testex',
michael@0 125 'testpkgs', 'testall',
michael@0 126 'testcfx'])),
michael@0 127 (("", "--pkgdir",), dict(dest="pkgdir",
michael@0 128 help=("package dir containing "
michael@0 129 "package.json; default is "
michael@0 130 "current directory"),
michael@0 131 metavar=None,
michael@0 132 default=None,
michael@0 133 cmds=['run', 'xpi', 'test'])),
michael@0 134 (("", "--static-args",), dict(dest="static_args",
michael@0 135 help="extra harness options as JSON",
michael@0 136 type="json",
michael@0 137 metavar=None,
michael@0 138 default="{}",
michael@0 139 cmds=['run', 'xpi'])),
michael@0 140 (("", "--parseable",), dict(dest="parseable",
michael@0 141 help="display test output in a parseable format",
michael@0 142 action="store_true",
michael@0 143 default=False,
michael@0 144 cmds=['run', 'test', 'testex', 'testpkgs',
michael@0 145 'testaddons', 'testall'])),
michael@0 146 ]
michael@0 147 ),
michael@0 148
michael@0 149 ("Experimental Command-Specific Options", [
michael@0 150 (("-a", "--app",), dict(dest="app",
michael@0 151 help=("app to run: firefox (default), fennec, "
michael@0 152 "fennec-on-device, xulrunner or "
michael@0 153 "thunderbird"),
michael@0 154 metavar=None,
michael@0 155 type="choice",
michael@0 156 choices=["firefox", "fennec",
michael@0 157 "fennec-on-device", "thunderbird",
michael@0 158 "xulrunner"],
michael@0 159 default="firefox",
michael@0 160 cmds=['test', 'run', 'testex', 'testpkgs',
michael@0 161 'testall'])),
michael@0 162 (("-o", "--overload-modules",), dict(dest="overload_modules",
michael@0 163 help=("Overload JS modules integrated into"
michael@0 164 " Firefox with the one from your SDK"
michael@0 165 " repository"),
michael@0 166 action="store_true",
michael@0 167 default=False,
michael@0 168 cmds=['run', 'test', 'testex', 'testpkgs',
michael@0 169 'testall'])),
michael@0 170 (("", "--strip-sdk",), dict(dest="bundle_sdk",
michael@0 171 help=("Do not ship SDK modules in the xpi"),
michael@0 172 action="store_false",
michael@0 173 default=False,
michael@0 174 cmds=['run', 'test', 'testex', 'testpkgs',
michael@0 175 'testall', 'xpi'])),
michael@0 176 (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk",
michael@0 177 help=("When --strip-sdk isn't passed, "
michael@0 178 "force using sdk modules shipped in "
michael@0 179 "the xpi instead of firefox ones"),
michael@0 180 action="store_true",
michael@0 181 default=False,
michael@0 182 cmds=['run', 'test', 'testex', 'testpkgs',
michael@0 183 'testall', 'xpi'])),
michael@0 184 (("", "--no-run",), dict(dest="no_run",
michael@0 185 help=("Instead of launching the "
michael@0 186 "application, just show the command "
michael@0 187 "for doing so. Use this to launch "
michael@0 188 "the application in a debugger like "
michael@0 189 "gdb."),
michael@0 190 action="store_true",
michael@0 191 default=False,
michael@0 192 cmds=['run', 'test'])),
michael@0 193 (("", "--no-strip-xpi",), dict(dest="no_strip_xpi",
michael@0 194 help="retain unused modules in XPI",
michael@0 195 action="store_true",
michael@0 196 default=False,
michael@0 197 cmds=['xpi'])),
michael@0 198 (("", "--force-mobile",), dict(dest="enable_mobile",
michael@0 199 help="Force compatibility with Firefox Mobile",
michael@0 200 action="store_true",
michael@0 201 default=False,
michael@0 202 cmds=['run', 'test', 'xpi', 'testall'])),
michael@0 203 (("", "--mobile-app",), dict(dest="mobile_app_name",
michael@0 204 help=("Name of your Android application to "
michael@0 205 "use. Possible values: 'firefox', "
michael@0 206 "'firefox_beta', 'fennec_aurora', "
michael@0 207 "'fennec' (for nightly)."),
michael@0 208 metavar=None,
michael@0 209 default=None,
michael@0 210 cmds=['run', 'test', 'testall'])),
michael@0 211 (("", "--harness-option",), dict(dest="extra_harness_option_args",
michael@0 212 help=("Extra properties added to "
michael@0 213 "harness-options.json"),
michael@0 214 action="append",
michael@0 215 metavar="KEY=VALUE",
michael@0 216 default=[],
michael@0 217 cmds=['xpi'])),
michael@0 218 (("", "--stop-on-error",), dict(dest="stopOnError",
michael@0 219 help="Stop running tests after the first failure",
michael@0 220 action="store_true",
michael@0 221 metavar=None,
michael@0 222 default=False,
michael@0 223 cmds=['test', 'testex', 'testpkgs'])),
michael@0 224 (("", "--check-memory",), dict(dest="check_memory",
michael@0 225 help="attempts to detect leaked compartments after a test run",
michael@0 226 action="store_true",
michael@0 227 default=False,
michael@0 228 cmds=['test', 'testpkgs', 'testaddons',
michael@0 229 'testall'])),
michael@0 230 (("", "--output-file",), dict(dest="output_file",
michael@0 231 help="Where to put the finished .xpi",
michael@0 232 default=None,
michael@0 233 cmds=['xpi'])),
michael@0 234 (("", "--manifest-overload",), dict(dest="manifest_overload",
michael@0 235 help="JSON file to overload package.json properties",
michael@0 236 default=None,
michael@0 237 cmds=['xpi'])),
michael@0 238 ]
michael@0 239 ),
michael@0 240
michael@0 241 ("Internal Command-Specific Options", [
michael@0 242 (("", "--addons",), dict(dest="addons",
michael@0 243 help=("paths of addons to install, "
michael@0 244 "comma-separated"),
michael@0 245 metavar=None,
michael@0 246 default=None,
michael@0 247 cmds=['test', 'run', 'testex', 'testpkgs',
michael@0 248 'testall'])),
michael@0 249 (("", "--test-runner-pkg",), dict(dest="test_runner_pkg",
michael@0 250 help=("name of package "
michael@0 251 "containing test runner "
michael@0 252 "program (default is "
michael@0 253 "test-harness)"),
michael@0 254 default="addon-sdk",
michael@0 255 cmds=['test', 'testex', 'testpkgs',
michael@0 256 'testall'])),
michael@0 257 # --keydir was removed in 1.0b5, but we keep it around in the options
michael@0 258 # parser to make life easier for frontends like FlightDeck which
michael@0 259 # might still pass it. It can go away once the frontends are updated.
michael@0 260 (("", "--keydir",), dict(dest="keydir",
michael@0 261 help=("obsolete, ignored"),
michael@0 262 metavar=None,
michael@0 263 default=None,
michael@0 264 cmds=['test', 'run', 'xpi', 'testex',
michael@0 265 'testpkgs', 'testall'])),
michael@0 266 (("", "--e10s",), dict(dest="enable_e10s",
michael@0 267 help="enable out-of-process Jetpacks",
michael@0 268 action="store_true",
michael@0 269 default=False,
michael@0 270 cmds=['test', 'run', 'testex', 'testpkgs'])),
michael@0 271 (("", "--logfile",), dict(dest="logfile",
michael@0 272 help="log console output to file",
michael@0 273 metavar=None,
michael@0 274 default=None,
michael@0 275 cmds=['run', 'test', 'testex', 'testpkgs'])),
michael@0 276 # TODO: This should default to true once our memory debugging
michael@0 277 # issues are resolved; see bug 592774.
michael@0 278 (("", "--profile-memory",), dict(dest="profileMemory",
michael@0 279 help=("profile memory usage "
michael@0 280 "(default is false)"),
michael@0 281 type="int",
michael@0 282 action="store",
michael@0 283 default=0,
michael@0 284 cmds=['test', 'testex', 'testpkgs',
michael@0 285 'testall'])),
michael@0 286 ]
michael@0 287 ),
michael@0 288 )
michael@0 289
michael@0 290 def find_parent_package(cur_dir):
michael@0 291 tail = True
michael@0 292 while tail:
michael@0 293 if os.path.exists(os.path.join(cur_dir, 'package.json')):
michael@0 294 return cur_dir
michael@0 295 cur_dir, tail = os.path.split(cur_dir)
michael@0 296 return None
michael@0 297
michael@0 298 def check_json(option, opt, value):
michael@0 299 # We return the parsed JSON here; see bug 610816 for background on why.
michael@0 300 try:
michael@0 301 return json.loads(value)
michael@0 302 except ValueError:
michael@0 303 raise optparse.OptionValueError("Option %s must be JSON." % opt)
michael@0 304
michael@0 305 class CfxOption(optparse.Option):
michael@0 306 TYPES = optparse.Option.TYPES + ('json',)
michael@0 307 TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
michael@0 308 TYPE_CHECKER['json'] = check_json
michael@0 309
michael@0 310 def parse_args(arguments, global_options, usage, version, parser_groups,
michael@0 311 defaults=None):
michael@0 312 parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption,
michael@0 313 version=version)
michael@0 314
michael@0 315 def name_cmp(a, b):
michael@0 316 # a[0] = name sequence
michael@0 317 # a[0][0] = short name (possibly empty string)
michael@0 318 # a[0][1] = long name
michael@0 319 names = []
michael@0 320 for seq in (a, b):
michael@0 321 names.append(seq[0][0][1:] if seq[0][0] else seq[0][1][2:])
michael@0 322 return cmp(*names)
michael@0 323
michael@0 324 global_options.sort(name_cmp)
michael@0 325 for names, opts in global_options:
michael@0 326 parser.add_option(*names, **opts)
michael@0 327
michael@0 328 for group_name, options in parser_groups:
michael@0 329 group = optparse.OptionGroup(parser, group_name)
michael@0 330 options.sort(name_cmp)
michael@0 331 for names, opts in options:
michael@0 332 if 'cmds' in opts:
michael@0 333 cmds = opts['cmds']
michael@0 334 del opts['cmds']
michael@0 335 cmds.sort()
michael@0 336 if not 'help' in opts:
michael@0 337 opts['help'] = ""
michael@0 338 opts['help'] += " (%s)" % ", ".join(cmds)
michael@0 339 group.add_option(*names, **opts)
michael@0 340 parser.add_option_group(group)
michael@0 341
michael@0 342 if defaults:
michael@0 343 parser.set_defaults(**defaults)
michael@0 344
michael@0 345 (options, args) = parser.parse_args(args=arguments)
michael@0 346
michael@0 347 if not args:
michael@0 348 parser.print_help()
michael@0 349 parser.exit()
michael@0 350
michael@0 351 return (options, args)
michael@0 352
michael@0 353 # all tests emit progress messages to stderr, not stdout. (the mozrunner
michael@0 354 # console output goes to stderr and is hard to change, and
michael@0 355 # unittest.TextTestRunner prefers stderr, so we send everything else there
michael@0 356 # too, to keep all the messages in order)
michael@0 357
michael@0 358 def test_all(env_root, defaults):
michael@0 359 fail = False
michael@0 360
michael@0 361 starttime = time.time()
michael@0 362
michael@0 363 if not defaults['filter']:
michael@0 364 print >>sys.stderr, "Testing cfx..."
michael@0 365 sys.stderr.flush()
michael@0 366 result = test_cfx(env_root, defaults['verbose'])
michael@0 367 if result.failures or result.errors:
michael@0 368 fail = True
michael@0 369
michael@0 370 if not fail or not defaults.get("stopOnError"):
michael@0 371 print >>sys.stderr, "Testing all examples..."
michael@0 372 sys.stderr.flush()
michael@0 373
michael@0 374 try:
michael@0 375 test_all_examples(env_root, defaults)
michael@0 376 except SystemExit, e:
michael@0 377 fail = (e.code != 0) or fail
michael@0 378
michael@0 379 if not fail or not defaults.get("stopOnError"):
michael@0 380 print >>sys.stderr, "Testing all unit-test addons..."
michael@0 381 sys.stderr.flush()
michael@0 382
michael@0 383 try:
michael@0 384 test_all_testaddons(env_root, defaults)
michael@0 385 except SystemExit, e:
michael@0 386 fail = (e.code != 0) or fail
michael@0 387
michael@0 388 if not fail or not defaults.get("stopOnError"):
michael@0 389 print >>sys.stderr, "Testing all packages..."
michael@0 390 sys.stderr.flush()
michael@0 391 try:
michael@0 392 test_all_packages(env_root, defaults)
michael@0 393 except SystemExit, e:
michael@0 394 fail = (e.code != 0) or fail
michael@0 395
michael@0 396 print >>sys.stderr, "Total time for all tests: %f seconds" % (time.time() - starttime)
michael@0 397
michael@0 398 if fail:
michael@0 399 print >>sys.stderr, "Some tests were unsuccessful."
michael@0 400 sys.exit(1)
michael@0 401 print >>sys.stderr, "All tests were successful. Ship it!"
michael@0 402 sys.exit(0)
michael@0 403
michael@0 404 def test_cfx(env_root, verbose):
michael@0 405 import cuddlefish.tests
michael@0 406
michael@0 407 # tests write to stderr. flush everything before and after to avoid
michael@0 408 # confusion later.
michael@0 409 sys.stdout.flush(); sys.stderr.flush()
michael@0 410 olddir = os.getcwd()
michael@0 411 os.chdir(env_root)
michael@0 412 retval = cuddlefish.tests.run(verbose)
michael@0 413 os.chdir(olddir)
michael@0 414 sys.stdout.flush(); sys.stderr.flush()
michael@0 415 return retval
michael@0 416
michael@0 417 def test_all_testaddons(env_root, defaults):
michael@0 418 addons_dir = os.path.join(env_root, "test", "addons")
michael@0 419 addons = [dirname for dirname in os.listdir(addons_dir)
michael@0 420 if os.path.isdir(os.path.join(addons_dir, dirname))]
michael@0 421 addons.sort()
michael@0 422 fail = False
michael@0 423 for dirname in addons:
michael@0 424 if (not defaults['filter'].split(":")[0] in dirname):
michael@0 425 continue
michael@0 426
michael@0 427 print >>sys.stderr, "Testing %s..." % dirname
michael@0 428 sys.stderr.flush()
michael@0 429 try:
michael@0 430 run(arguments=["run",
michael@0 431 "--pkgdir",
michael@0 432 os.path.join(addons_dir, dirname)],
michael@0 433 defaults=defaults,
michael@0 434 env_root=env_root)
michael@0 435 except SystemExit, e:
michael@0 436 fail = (e.code != 0) or fail
michael@0 437 if fail and defaults.get("stopOnError"):
michael@0 438 break
michael@0 439
michael@0 440 if fail:
michael@0 441 print >>sys.stderr, "Some test addons tests were unsuccessful."
michael@0 442 sys.exit(-1)
michael@0 443
michael@0 444 def test_all_examples(env_root, defaults):
michael@0 445 examples_dir = os.path.join(env_root, "examples")
michael@0 446 examples = [dirname for dirname in os.listdir(examples_dir)
michael@0 447 if os.path.isdir(os.path.join(examples_dir, dirname))]
michael@0 448 examples.sort()
michael@0 449 fail = False
michael@0 450 for dirname in examples:
michael@0 451 if (not defaults['filter'].split(":")[0] in dirname):
michael@0 452 continue
michael@0 453
michael@0 454 print >>sys.stderr, "Testing %s..." % dirname
michael@0 455 sys.stderr.flush()
michael@0 456 try:
michael@0 457 run(arguments=["test",
michael@0 458 "--pkgdir",
michael@0 459 os.path.join(examples_dir, dirname)],
michael@0 460 defaults=defaults,
michael@0 461 env_root=env_root)
michael@0 462 except SystemExit, e:
michael@0 463 fail = (e.code != 0) or fail
michael@0 464 if fail and defaults.get("stopOnError"):
michael@0 465 break
michael@0 466
michael@0 467 if fail:
michael@0 468 print >>sys.stderr, "Some examples tests were unsuccessful."
michael@0 469 sys.exit(-1)
michael@0 470
michael@0 471 def test_all_packages(env_root, defaults):
michael@0 472 packages_dir = os.path.join(env_root, "packages")
michael@0 473 if os.path.isdir(packages_dir):
michael@0 474 packages = [dirname for dirname in os.listdir(packages_dir)
michael@0 475 if os.path.isdir(os.path.join(packages_dir, dirname))]
michael@0 476 else:
michael@0 477 packages = []
michael@0 478 packages.append(env_root)
michael@0 479 packages.sort()
michael@0 480 print >>sys.stderr, "Testing all available packages: %s." % (", ".join(packages))
michael@0 481 sys.stderr.flush()
michael@0 482 fail = False
michael@0 483 for dirname in packages:
michael@0 484 print >>sys.stderr, "Testing %s..." % dirname
michael@0 485 sys.stderr.flush()
michael@0 486 try:
michael@0 487 run(arguments=["test",
michael@0 488 "--pkgdir",
michael@0 489 os.path.join(packages_dir, dirname)],
michael@0 490 defaults=defaults,
michael@0 491 env_root=env_root)
michael@0 492 except SystemExit, e:
michael@0 493 fail = (e.code != 0) or fail
michael@0 494 if fail and defaults.get('stopOnError'):
michael@0 495 break
michael@0 496 if fail:
michael@0 497 print >>sys.stderr, "Some package tests were unsuccessful."
michael@0 498 sys.exit(-1)
michael@0 499
michael@0 500 def get_config_args(name, env_root):
michael@0 501 local_json = os.path.join(env_root, "local.json")
michael@0 502 if not (os.path.exists(local_json) and
michael@0 503 os.path.isfile(local_json)):
michael@0 504 if name == "default":
michael@0 505 return []
michael@0 506 else:
michael@0 507 print >>sys.stderr, "File does not exist: %s" % local_json
michael@0 508 sys.exit(1)
michael@0 509 local_json = packaging.load_json_file(local_json)
michael@0 510 if 'configs' not in local_json:
michael@0 511 print >>sys.stderr, "'configs' key not found in local.json."
michael@0 512 sys.exit(1)
michael@0 513 if name not in local_json.configs:
michael@0 514 if name == "default":
michael@0 515 return []
michael@0 516 else:
michael@0 517 print >>sys.stderr, "No config found for '%s'." % name
michael@0 518 sys.exit(1)
michael@0 519 config = local_json.configs[name]
michael@0 520 if type(config) != list:
michael@0 521 print >>sys.stderr, "Config for '%s' must be a list of strings." % name
michael@0 522 sys.exit(1)
michael@0 523 return config
michael@0 524
michael@0 525 def initializer(env_root, args, out=sys.stdout, err=sys.stderr):
michael@0 526 from templates import PACKAGE_JSON, TEST_MAIN_JS
michael@0 527 from preflight import create_jid
michael@0 528 path = os.getcwd()
michael@0 529 addon = os.path.basename(path)
michael@0 530 # if more than two arguments
michael@0 531 if len(args) > 2:
michael@0 532 print >>err, 'Too many arguments.'
michael@0 533 return {"result":1}
michael@0 534 if len(args) == 2:
michael@0 535 path = os.path.join(path,args[1])
michael@0 536 try:
michael@0 537 os.mkdir(path)
michael@0 538 print >>out, '*', args[1], 'package directory created'
michael@0 539 except OSError:
michael@0 540 print >>out, '*', args[1], 'already exists, testing if directory is empty'
michael@0 541 # avoid clobbering existing files, but we tolerate things like .git
michael@0 542 existing = [fn for fn in os.listdir(path) if not fn.startswith(".")]
michael@0 543 if existing:
michael@0 544 print >>err, 'This command must be run in an empty directory.'
michael@0 545 return {"result":1}
michael@0 546 for d in ['lib','data','test']:
michael@0 547 os.mkdir(os.path.join(path,d))
michael@0 548 print >>out, '*', d, 'directory created'
michael@0 549 jid = create_jid()
michael@0 550 print >>out, '* generated jID automatically:', jid
michael@0 551 open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(),
michael@0 552 'title':addon,
michael@0 553 'id':jid })
michael@0 554 print >>out, '* package.json written'
michael@0 555 open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS)
michael@0 556 print >>out, '* test/test-main.js written'
michael@0 557 open(os.path.join(path,'lib','main.js'),'w').write('')
michael@0 558 print >>out, '* lib/main.js written'
michael@0 559 if len(args) == 1:
michael@0 560 print >>out, '\nYour sample add-on is now ready.'
michael@0 561 print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!'
michael@0 562 else:
michael@0 563 print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.'
michael@0 564 print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!'
michael@0 565 return {"result":0, "jid":jid}
michael@0 566
michael@0 567 def buildJID(target_cfg):
michael@0 568 if "id" in target_cfg:
michael@0 569 jid = target_cfg["id"]
michael@0 570 else:
michael@0 571 import uuid
michael@0 572 jid = str(uuid.uuid4())
michael@0 573 if not ("@" in jid or jid.startswith("{")):
michael@0 574 jid = jid + "@jetpack"
michael@0 575 return jid
michael@0 576
michael@0 577 def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
michael@0 578 defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'),
michael@0 579 stdout=sys.stdout):
michael@0 580 versions = get_versions()
michael@0 581 sdk_version = versions["version"]
michael@0 582 display_version = "Add-on SDK %s (%s)" % (sdk_version, versions["full"])
michael@0 583 parser_kwargs = dict(arguments=arguments,
michael@0 584 global_options=global_options,
michael@0 585 parser_groups=parser_groups,
michael@0 586 usage=usage,
michael@0 587 version=display_version,
michael@0 588 defaults=defaults)
michael@0 589
michael@0 590 (options, args) = parse_args(**parser_kwargs)
michael@0 591
michael@0 592 config_args = get_config_args(options.config, env_root);
michael@0 593
michael@0 594 # reparse configs with arguments from local.json
michael@0 595 if config_args:
michael@0 596 parser_kwargs['arguments'] += config_args
michael@0 597 (options, args) = parse_args(**parser_kwargs)
michael@0 598
michael@0 599 command = args[0]
michael@0 600
michael@0 601 if command == "init":
michael@0 602 initializer(env_root, args)
michael@0 603 return
michael@0 604 if command == "testpkgs":
michael@0 605 test_all_packages(env_root, defaults=options.__dict__)
michael@0 606 return
michael@0 607 elif command == "testaddons":
michael@0 608 test_all_testaddons(env_root, defaults=options.__dict__)
michael@0 609 return
michael@0 610 elif command == "testex":
michael@0 611 test_all_examples(env_root, defaults=options.__dict__)
michael@0 612 return
michael@0 613 elif command == "testall":
michael@0 614 test_all(env_root, defaults=options.__dict__)
michael@0 615 return
michael@0 616 elif command == "testcfx":
michael@0 617 if options.filter:
michael@0 618 print >>sys.stderr, "The filter option is not valid with the testcfx command"
michael@0 619 return
michael@0 620 test_cfx(env_root, options.verbose)
michael@0 621 return
michael@0 622 elif command not in ["xpi", "test", "run"]:
michael@0 623 print >>sys.stderr, "Unknown command: %s" % command
michael@0 624 print >>sys.stderr, "Try using '--help' for assistance."
michael@0 625 sys.exit(1)
michael@0 626
michael@0 627 target_cfg_json = None
michael@0 628 if not target_cfg:
michael@0 629 if not options.pkgdir:
michael@0 630 options.pkgdir = find_parent_package(os.getcwd())
michael@0 631 if not options.pkgdir:
michael@0 632 print >>sys.stderr, ("cannot find 'package.json' in the"
michael@0 633 " current directory or any parent.")
michael@0 634 sys.exit(1)
michael@0 635 else:
michael@0 636 options.pkgdir = os.path.abspath(options.pkgdir)
michael@0 637 if not os.path.exists(os.path.join(options.pkgdir, 'package.json')):
michael@0 638 print >>sys.stderr, ("cannot find 'package.json' in"
michael@0 639 " %s." % options.pkgdir)
michael@0 640 sys.exit(1)
michael@0 641
michael@0 642 target_cfg_json = os.path.join(options.pkgdir, 'package.json')
michael@0 643 target_cfg = packaging.get_config_in_dir(options.pkgdir)
michael@0 644
michael@0 645 if options.manifest_overload:
michael@0 646 for k, v in packaging.load_json_file(options.manifest_overload).items():
michael@0 647 target_cfg[k] = v
michael@0 648
michael@0 649 # At this point, we're either building an XPI or running Jetpack code in
michael@0 650 # a Mozilla application (which includes running tests).
michael@0 651
michael@0 652 use_main = False
michael@0 653 inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory']
michael@0 654 enforce_timeouts = False
michael@0 655
michael@0 656 if command == "xpi":
michael@0 657 use_main = True
michael@0 658 elif command == "test":
michael@0 659 if 'tests' not in target_cfg:
michael@0 660 target_cfg['tests'] = []
michael@0 661 inherited_options.extend(['iterations', 'filter', 'profileMemory',
michael@0 662 'stopOnError'])
michael@0 663 enforce_timeouts = True
michael@0 664 elif command == "run":
michael@0 665 use_main = True
michael@0 666 else:
michael@0 667 assert 0, "shouldn't get here"
michael@0 668
michael@0 669 if use_main and 'main' not in target_cfg:
michael@0 670 # If the user supplies a template dir, then the main
michael@0 671 # program may be contained in the template.
michael@0 672 if not options.templatedir:
michael@0 673 print >>sys.stderr, "package.json does not have a 'main' entry."
michael@0 674 sys.exit(1)
michael@0 675
michael@0 676 if not pkg_cfg:
michael@0 677 pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath)
michael@0 678
michael@0 679 target = target_cfg.name
michael@0 680
michael@0 681 # TODO: Consider keeping a cache of dynamic UUIDs, based
michael@0 682 # on absolute filesystem pathname, in the root directory
michael@0 683 # or something.
michael@0 684 if command in ('xpi', 'run'):
michael@0 685 from cuddlefish.preflight import preflight_config
michael@0 686 if target_cfg_json:
michael@0 687 config_was_ok, modified = preflight_config(target_cfg,
michael@0 688 target_cfg_json)
michael@0 689 if not config_was_ok:
michael@0 690 if modified:
michael@0 691 # we need to re-read package.json . The safest approach
michael@0 692 # is to re-run the "cfx xpi"/"cfx run" command.
michael@0 693 print >>sys.stderr, ("package.json modified: please re-run"
michael@0 694 " 'cfx %s'" % command)
michael@0 695 else:
michael@0 696 print >>sys.stderr, ("package.json needs modification:"
michael@0 697 " please update it and then re-run"
michael@0 698 " 'cfx %s'" % command)
michael@0 699 sys.exit(1)
michael@0 700 # if we make it this far, we have a JID
michael@0 701 else:
michael@0 702 assert command == "test"
michael@0 703
michael@0 704 jid = buildJID(target_cfg)
michael@0 705
michael@0 706 targets = [target]
michael@0 707 if command == "test":
michael@0 708 targets.append(options.test_runner_pkg)
michael@0 709
michael@0 710 extra_packages = []
michael@0 711 if options.extra_packages:
michael@0 712 extra_packages = options.extra_packages.split(",")
michael@0 713 if extra_packages:
michael@0 714 targets.extend(extra_packages)
michael@0 715 target_cfg.extra_dependencies = extra_packages
michael@0 716
michael@0 717 deps = packaging.get_deps_for_targets(pkg_cfg, targets)
michael@0 718
michael@0 719 from cuddlefish.manifest import build_manifest, ModuleNotFoundError, \
michael@0 720 BadChromeMarkerError
michael@0 721 # Figure out what loader files should be scanned. This is normally
michael@0 722 # computed inside packaging.generate_build_for_target(), by the first
michael@0 723 # dependent package that defines a "loader" property in its package.json.
michael@0 724 # This property is interpreted as a filename relative to the top of that
michael@0 725 # file, and stored as a path in build.loader . generate_build_for_target()
michael@0 726 # cannot be called yet (it needs the list of used_deps that
michael@0 727 # build_manifest() computes, but build_manifest() needs the list of
michael@0 728 # loader files that it computes). We could duplicate or factor out this
michael@0 729 # build.loader logic, but that would be messy, so instead we hard-code
michael@0 730 # the choice of loader for manifest-generation purposes. In practice,
michael@0 731 # this means that alternative loaders probably won't work with
michael@0 732 # --strip-xpi.
michael@0 733 assert packaging.DEFAULT_LOADER == "addon-sdk"
michael@0 734 assert pkg_cfg.packages["addon-sdk"].loader == "lib/sdk/loader/cuddlefish.js"
michael@0 735 cuddlefish_js_path = os.path.join(pkg_cfg.packages["addon-sdk"].root_dir,
michael@0 736 "lib", "sdk", "loader", "cuddlefish.js")
michael@0 737 loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)]
michael@0 738 scan_tests = command == "test"
michael@0 739 test_filter_re = None
michael@0 740 if scan_tests and options.filter:
michael@0 741 test_filter_re = options.filter
michael@0 742 if ":" in options.filter:
michael@0 743 test_filter_re = options.filter.split(":")[0]
michael@0 744 try:
michael@0 745 manifest = build_manifest(target_cfg, pkg_cfg, deps,
michael@0 746 scan_tests, test_filter_re,
michael@0 747 loader_modules)
michael@0 748 except ModuleNotFoundError, e:
michael@0 749 print str(e)
michael@0 750 sys.exit(1)
michael@0 751 except BadChromeMarkerError, e:
michael@0 752 # An error had already been displayed on stderr in manifest code
michael@0 753 sys.exit(1)
michael@0 754 used_deps = manifest.get_used_packages()
michael@0 755 if command == "test":
michael@0 756 # The test runner doesn't appear to link against any actual packages,
michael@0 757 # because it loads everything at runtime (invisible to the linker).
michael@0 758 # If we believe that, we won't set up URI mappings for anything, and
michael@0 759 # tests won't be able to run.
michael@0 760 used_deps = deps
michael@0 761 for xp in extra_packages:
michael@0 762 if xp not in used_deps:
michael@0 763 used_deps.append(xp)
michael@0 764
michael@0 765 build = packaging.generate_build_for_target(
michael@0 766 pkg_cfg, target, used_deps,
michael@0 767 include_dep_tests=options.dep_tests,
michael@0 768 is_running_tests=(command == "test")
michael@0 769 )
michael@0 770
michael@0 771 harness_options = {
michael@0 772 'jetpackID': jid,
michael@0 773 'staticArgs': options.static_args,
michael@0 774 'name': target,
michael@0 775 }
michael@0 776
michael@0 777 harness_options.update(build)
michael@0 778
michael@0 779 # When cfx is run from sdk root directory, we will strip sdk modules and
michael@0 780 # override them with local modules.
michael@0 781 # So that integration tools will continue to work and use local modules
michael@0 782 if os.getcwd() == env_root:
michael@0 783 options.bundle_sdk = True
michael@0 784 options.force_use_bundled_sdk = False
michael@0 785 options.overload_modules = True
michael@0 786
michael@0 787 extra_environment = {}
michael@0 788 if command == "test":
michael@0 789 # This should be contained in the test runner package.
michael@0 790 # maybe just do: target_cfg.main = 'test-harness/run-tests'
michael@0 791 harness_options['main'] = 'sdk/test/runner'
michael@0 792 harness_options['mainPath'] = 'sdk/test/runner'
michael@0 793 else:
michael@0 794 harness_options['main'] = target_cfg.get('main')
michael@0 795 harness_options['mainPath'] = manifest.top_path
michael@0 796 extra_environment["CFX_COMMAND"] = command
michael@0 797
michael@0 798 for option in inherited_options:
michael@0 799 harness_options[option] = getattr(options, option)
michael@0 800
michael@0 801 harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps)
michael@0 802
michael@0 803 harness_options['sdkVersion'] = sdk_version
michael@0 804
michael@0 805 packaging.call_plugins(pkg_cfg, used_deps)
michael@0 806
michael@0 807 retval = 0
michael@0 808
michael@0 809 if options.templatedir:
michael@0 810 app_extension_dir = os.path.abspath(options.templatedir)
michael@0 811 elif os.path.exists(os.path.join(options.pkgdir, "app-extension")):
michael@0 812 app_extension_dir = os.path.join(options.pkgdir, "app-extension")
michael@0 813 else:
michael@0 814 mydir = os.path.dirname(os.path.abspath(__file__))
michael@0 815 app_extension_dir = os.path.join(mydir, "../../app-extension")
michael@0 816
michael@0 817 if target_cfg.get('preferences'):
michael@0 818 harness_options['preferences'] = target_cfg.get('preferences')
michael@0 819
michael@0 820 # Do not add entries for SDK modules
michael@0 821 harness_options['manifest'] = manifest.get_harness_options_manifest(False)
michael@0 822
michael@0 823 # Gives an hint to tell if sdk modules are bundled or not
michael@0 824 harness_options['is-sdk-bundled'] = options.bundle_sdk or options.no_strip_xpi
michael@0 825
michael@0 826 if options.force_use_bundled_sdk:
michael@0 827 if not harness_options['is-sdk-bundled']:
michael@0 828 print >>sys.stderr, ("--force-use-bundled-sdk "
michael@0 829 "can't be used if sdk isn't bundled.")
michael@0 830 sys.exit(1)
michael@0 831 if options.overload_modules:
michael@0 832 print >>sys.stderr, ("--force-use-bundled-sdk and --overload-modules "
michael@0 833 "can't be used at the same time.")
michael@0 834 sys.exit(1)
michael@0 835 # Pass a flag in order to force using sdk modules shipped in the xpi
michael@0 836 harness_options['force-use-bundled-sdk'] = True
michael@0 837
michael@0 838 # Pass the list of absolute path for all test modules
michael@0 839 if command == "test":
michael@0 840 harness_options['allTestModules'] = manifest.get_all_test_modules()
michael@0 841 if len(harness_options['allTestModules']) == 0:
michael@0 842 sys.exit(0)
michael@0 843
michael@0 844 from cuddlefish.rdf import gen_manifest, RDFUpdate
michael@0 845
michael@0 846 manifest_rdf = gen_manifest(template_root_dir=app_extension_dir,
michael@0 847 target_cfg=target_cfg,
michael@0 848 jid=jid,
michael@0 849 update_url=options.update_url,
michael@0 850 bootstrap=True,
michael@0 851 enable_mobile=options.enable_mobile)
michael@0 852
michael@0 853 if command == "xpi" and options.update_link:
michael@0 854 if not options.update_link.startswith("https"):
michael@0 855 raise optparse.OptionValueError("--update-link must start with 'https': %s" % options.update_link)
michael@0 856 rdf_name = UPDATE_RDF_FILENAME % target_cfg.name
michael@0 857 print >>stdout, "Exporting update description to %s." % rdf_name
michael@0 858 update = RDFUpdate()
michael@0 859 update.add(manifest_rdf, options.update_link)
michael@0 860 open(rdf_name, "w").write(str(update))
michael@0 861
michael@0 862 # ask the manifest what files were used, so we can construct an XPI
michael@0 863 # without the rest. This will include the loader (and everything it
michael@0 864 # uses) because of the "loader_modules" starting points we passed to
michael@0 865 # build_manifest earlier
michael@0 866 used_files = None
michael@0 867 if command == "xpi":
michael@0 868 used_files = set(manifest.get_used_files(options.bundle_sdk))
michael@0 869
michael@0 870 if options.no_strip_xpi:
michael@0 871 used_files = None # disables the filter, includes all files
michael@0 872
michael@0 873 if command == 'xpi':
michael@0 874 from cuddlefish.xpi import build_xpi
michael@0 875 # Generate extra options
michael@0 876 extra_harness_options = {}
michael@0 877 for kv in options.extra_harness_option_args:
michael@0 878 key,value = kv.split("=", 1)
michael@0 879 extra_harness_options[key] = value
michael@0 880 # Generate xpi filepath
michael@0 881 if options.output_file:
michael@0 882 xpi_path = options.output_file
michael@0 883 else:
michael@0 884 xpi_path = XPI_FILENAME % target_cfg.name
michael@0 885
michael@0 886 print >>stdout, "Exporting extension to %s." % xpi_path
michael@0 887 build_xpi(template_root_dir=app_extension_dir,
michael@0 888 manifest=manifest_rdf,
michael@0 889 xpi_path=xpi_path,
michael@0 890 harness_options=harness_options,
michael@0 891 limit_to=used_files,
michael@0 892 extra_harness_options=extra_harness_options,
michael@0 893 bundle_sdk=True,
michael@0 894 pkgdir=options.pkgdir)
michael@0 895 else:
michael@0 896 from cuddlefish.runner import run_app
michael@0 897
michael@0 898 if options.profiledir:
michael@0 899 options.profiledir = os.path.expanduser(options.profiledir)
michael@0 900 options.profiledir = os.path.abspath(options.profiledir)
michael@0 901
michael@0 902 if options.addons is not None:
michael@0 903 options.addons = options.addons.split(",")
michael@0 904
michael@0 905 try:
michael@0 906 retval = run_app(harness_root_dir=app_extension_dir,
michael@0 907 manifest_rdf=manifest_rdf,
michael@0 908 harness_options=harness_options,
michael@0 909 app_type=options.app,
michael@0 910 binary=options.binary,
michael@0 911 profiledir=options.profiledir,
michael@0 912 verbose=options.verbose,
michael@0 913 parseable=options.parseable,
michael@0 914 enforce_timeouts=enforce_timeouts,
michael@0 915 logfile=options.logfile,
michael@0 916 addons=options.addons,
michael@0 917 args=options.cmdargs,
michael@0 918 extra_environment=extra_environment,
michael@0 919 norun=options.no_run,
michael@0 920 used_files=used_files,
michael@0 921 enable_mobile=options.enable_mobile,
michael@0 922 mobile_app_name=options.mobile_app_name,
michael@0 923 env_root=env_root,
michael@0 924 is_running_tests=(command == "test"),
michael@0 925 overload_modules=options.overload_modules,
michael@0 926 bundle_sdk=options.bundle_sdk,
michael@0 927 pkgdir=options.pkgdir)
michael@0 928 except ValueError, e:
michael@0 929 print ""
michael@0 930 print "A given cfx option has an inappropriate value:"
michael@0 931 print >>sys.stderr, " " + " \n ".join(str(e).split("\n"))
michael@0 932 retval = -1
michael@0 933 except Exception, e:
michael@0 934 if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND):
michael@0 935 print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip()
michael@0 936 retval = -1
michael@0 937 else:
michael@0 938 raise
michael@0 939 sys.exit(retval)

mercurial