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

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

mercurial