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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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)

mercurial