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