__init__.py 40 KB

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