123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 |
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- import os, sys, re, hashlib
- import simplejson as json
- SEP = os.path.sep
- from cuddlefish.util import filter_filenames, filter_dirnames
- # Load new layout mapping hashtable
- path = os.path.join(os.environ.get('CUDDLEFISH_ROOT'), "mapping.json")
- data = open(path, 'r').read()
- NEW_LAYOUT_MAPPING = json.loads(data)
- def js_zipname(packagename, modulename):
- return "%s-lib/%s.js" % (packagename, modulename)
- def docs_zipname(packagename, modulename):
- return "%s-docs/%s.md" % (packagename, modulename)
- def datamap_zipname(packagename):
- return "%s-data.json" % packagename
- def datafile_zipname(packagename, datapath):
- return "%s-data/%s" % (packagename, datapath)
- def to_json(o):
- return json.dumps(o, indent=1).encode("utf-8")+"\n"
- class ModuleNotFoundError(Exception):
- def __init__(self, requirement_type, requirement_name,
- used_by, line_number, looked_in):
- Exception.__init__(self)
- self.requirement_type = requirement_type # "require" or "define"
- self.requirement_name = requirement_name # string, what they require()d
- self.used_by = used_by # string, full path to module which did require()
- self.line_number = line_number # int, 1-indexed line number of first require()
- self.looked_in = looked_in # list of full paths to potential .js files
- def __str__(self):
- what = "%s(%s)" % (self.requirement_type, self.requirement_name)
- where = self.used_by
- if self.line_number is not None:
- where = "%s:%d" % (self.used_by, self.line_number)
- searched = "Looked for it in:\n %s\n" % "\n ".join(self.looked_in)
- return ("ModuleNotFoundError: unable to satisfy: %s from\n"
- " %s:\n" % (what, where)) + searched
- class BadModuleIdentifier(Exception):
- pass
- class BadSection(Exception):
- pass
- class UnreachablePrefixError(Exception):
- pass
- class ManifestEntry:
- def __init__(self):
- self.docs_filename = None
- self.docs_hash = None
- self.requirements = {}
- self.datamap = None
- def get_path(self):
- name = self.moduleName
- if name.endswith(".js"):
- name = name[:-3]
- items = []
- # Only add package name for addons, so that system module paths match
- # the path from the commonjs root directory and also match the loader
- # mappings.
- if self.packageName != "addon-sdk":
- items.append(self.packageName)
- # And for the same reason, do not append `lib/`.
- if self.sectionName == "tests":
- items.append(self.sectionName)
- items.append(name)
- return "/".join(items)
- def get_entry_for_manifest(self):
- entry = { "packageName": self.packageName,
- "sectionName": self.sectionName,
- "moduleName": self.moduleName,
- "jsSHA256": self.js_hash,
- "docsSHA256": self.docs_hash,
- "requirements": {},
- }
- for req in self.requirements:
- if isinstance(self.requirements[req], ManifestEntry):
- them = self.requirements[req] # this is another ManifestEntry
- entry["requirements"][req] = them.get_path()
- else:
- # something magic. The manifest entry indicates that they're
- # allowed to require() it
- entry["requirements"][req] = self.requirements[req]
- assert isinstance(entry["requirements"][req], unicode) or \
- isinstance(entry["requirements"][req], str)
- return entry
- def add_js(self, js_filename):
- self.js_filename = js_filename
- self.js_hash = hash_file(js_filename)
- def add_docs(self, docs_filename):
- self.docs_filename = docs_filename
- self.docs_hash = hash_file(docs_filename)
- def add_requirement(self, reqname, reqdata):
- self.requirements[reqname] = reqdata
- def add_data(self, datamap):
- self.datamap = datamap
- def get_js_zipname(self):
- return js_zipname(self.packagename, self.modulename)
- def get_docs_zipname(self):
- if self.docs_hash:
- return docs_zipname(self.packagename, self.modulename)
- return None
- # self.js_filename
- # self.docs_filename
- def hash_file(fn):
- return hashlib.sha256(open(fn,"rb").read()).hexdigest()
- def get_datafiles(datadir):
- # yields pathnames relative to DATADIR, ignoring some files
- for dirpath, dirnames, filenames in os.walk(datadir):
- filenames = list(filter_filenames(filenames))
- # this tells os.walk to prune the search
- dirnames[:] = filter_dirnames(dirnames)
- for filename in filenames:
- fullname = os.path.join(dirpath, filename)
- assert fullname.startswith(datadir+SEP), "%s%s not in %s" % (datadir, SEP, fullname)
- yield fullname[len(datadir+SEP):]
- class DataMap:
- # one per package
- def __init__(self, pkg):
- self.pkg = pkg
- self.name = pkg.name
- self.files_to_copy = []
- datamap = {}
- datadir = os.path.join(pkg.root_dir, "data")
- for dataname in get_datafiles(datadir):
- absname = os.path.join(datadir, dataname)
- zipname = datafile_zipname(pkg.name, dataname)
- datamap[dataname] = hash_file(absname)
- self.files_to_copy.append( (zipname, absname) )
- self.data_manifest = to_json(datamap)
- self.data_manifest_hash = hashlib.sha256(self.data_manifest).hexdigest()
- self.data_manifest_zipname = datamap_zipname(pkg.name)
- self.data_uri_prefix = "%s/data/" % (self.name)
- class BadChromeMarkerError(Exception):
- pass
- class ModuleInfo:
- def __init__(self, package, section, name, js, docs):
- self.package = package
- self.section = section
- self.name = name
- self.js = js
- self.docs = docs
- def __hash__(self):
- return hash( (self.package.name, self.section, self.name,
- self.js, self.docs) )
- def __eq__(self, them):
- if them.__class__ is not self.__class__:
- return False
- if ((them.package.name, them.section, them.name, them.js, them.docs) !=
- (self.package.name, self.section, self.name, self.js, self.docs) ):
- return False
- return True
- def __repr__(self):
- return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name,
- self.section,
- self.name,
- self.js, self.docs)
- class ManifestBuilder:
- def __init__(self, target_cfg, pkg_cfg, deps, extra_modules,
- stderr=sys.stderr):
- self.manifest = {} # maps (package,section,module) to ManifestEntry
- self.target_cfg = target_cfg # the entry point
- self.pkg_cfg = pkg_cfg # all known packages
- self.deps = deps # list of package names to search
- self.used_packagenames = set()
- self.stderr = stderr
- self.extra_modules = extra_modules
- self.modules = {} # maps ModuleInfo to URI in self.manifest
- self.datamaps = {} # maps package name to DataMap instance
- self.files = [] # maps manifest index to (absfn,absfn) js/docs pair
- self.test_modules = [] # for runtime
- def build(self, scan_tests, test_filter_re):
- # process the top module, which recurses to process everything it
- # reaches
- if "main" in self.target_cfg:
- top_mi = self.find_top(self.target_cfg)
- top_me = self.process_module(top_mi)
- self.top_path = top_me.get_path()
- self.datamaps[self.target_cfg.name] = DataMap(self.target_cfg)
- if scan_tests:
- mi = self._find_module_in_package("addon-sdk", "lib", "sdk/test/runner", [])
- self.process_module(mi)
- # also scan all test files in all packages that we use. By making
- # a copy of self.used_packagenames first, we refrain from
- # processing tests in packages that our own tests depend upon. If
- # we're running tests for package A, and either modules in A or
- # tests in A depend upon modules from package B, we *don't* want
- # to run tests for package B.
- test_modules = []
- dirnames = self.target_cfg["tests"]
- if isinstance(dirnames, basestring):
- dirnames = [dirnames]
- dirnames = [os.path.join(self.target_cfg.root_dir, d)
- for d in dirnames]
- for d in dirnames:
- for filename in os.listdir(d):
- if filename.startswith("test-") and filename.endswith(".js"):
- testname = filename[:-3] # require(testname)
- if test_filter_re:
- if not re.search(test_filter_re, testname):
- continue
- tmi = ModuleInfo(self.target_cfg, "tests", testname,
- os.path.join(d, filename), None)
- # scan the test's dependencies
- tme = self.process_module(tmi)
- test_modules.append( (testname, tme) )
- # also add it as an artificial dependency of unit-test-finder, so
- # the runtime dynamic load can work.
- test_finder = self.get_manifest_entry("addon-sdk", "lib",
- "sdk/deprecated/unit-test-finder")
- for (testname,tme) in test_modules:
- test_finder.add_requirement(testname, tme)
- # finally, tell the runtime about it, so they won't have to
- # search for all tests. self.test_modules will be passed
- # through the harness-options.json file in the
- # .allTestModules property.
- # Pass the absolute module path.
- self.test_modules.append(tme.get_path())
- # include files used by the loader
- for em in self.extra_modules:
- (pkgname, section, modname, js) = em
- mi = ModuleInfo(self.pkg_cfg.packages[pkgname], section, modname,
- js, None)
- self.process_module(mi)
- def get_module_entries(self):
- return frozenset(self.manifest.values())
- def get_data_entries(self):
- return frozenset(self.datamaps.values())
- def get_used_packages(self):
- used = set()
- for index in self.manifest:
- (package, section, module) = index
- used.add(package)
- return sorted(used)
- def get_used_files(self, bundle_sdk_modules):
- # returns all .js files that we reference, plus data/ files. You will
- # need to add the loader, off-manifest files that it needs, and
- # generated metadata.
- for datamap in self.datamaps.values():
- for (zipname, absname) in datamap.files_to_copy:
- yield absname
- for me in self.get_module_entries():
- # Only ship SDK files if we are told to do so
- if me.packageName != "addon-sdk" or bundle_sdk_modules:
- yield me.js_filename
- def get_all_test_modules(self):
- return self.test_modules
- def get_harness_options_manifest(self, bundle_sdk_modules):
- manifest = {}
- for me in self.get_module_entries():
- path = me.get_path()
- # Do not add manifest entries for system modules.
- # Doesn't prevent from shipping modules.
- # Shipping modules is decided in `get_used_files`.
- if me.packageName != "addon-sdk" or bundle_sdk_modules:
- manifest[path] = me.get_entry_for_manifest()
- return manifest
- def get_manifest_entry(self, package, section, module):
- index = (package, section, module)
- if index not in self.manifest:
- m = self.manifest[index] = ManifestEntry()
- m.packageName = package
- m.sectionName = section
- m.moduleName = module
- self.used_packagenames.add(package)
- return self.manifest[index]
- def uri_name_from_path(self, pkg, fn):
- # given a filename like .../pkg1/lib/bar/foo.js, and a package
- # specification (with a .root_dir like ".../pkg1" and a .lib list of
- # paths where .lib[0] is like "lib"), return the appropriate NAME
- # that can be put into a URI like resource://JID-pkg1-lib/NAME . This
- # will throw an exception if the file is outside of the lib/
- # directory, since that means we can't construct a URI that points to
- # it.
- #
- # This should be a lot easier, and shouldn't fail when the file is in
- # the root of the package. Both should become possible when the XPI
- # is rearranged and our URI scheme is simplified.
- fn = os.path.abspath(fn)
- pkglib = pkg.lib[0]
- libdir = os.path.abspath(os.path.join(pkg.root_dir, pkglib))
- # AARGH, section and name! we need to reverse-engineer a
- # ModuleInfo instance that will produce a URI (in the form
- # PREFIX/PKGNAME-SECTION/JS) that will map to the existing file.
- # Until we fix URI generation to get rid of "sections", this is
- # limited to files in the same .directories.lib as the rest of
- # the package uses. So if the package's main files are in lib/,
- # but the main.js is in the package root, there is no URI we can
- # construct that will point to it, and we must fail.
- #
- # This will become much easier (and the failure case removed)
- # when we get rid of sections and change the URIs to look like
- # (PREFIX/PKGNAME/PATH-TO-JS).
- # AARGH 2, allowing .lib to be a list is really getting in the
- # way. That needs to go away eventually too.
- if not fn.startswith(libdir):
- raise UnreachablePrefixError("Sorry, but the 'main' file (%s) in package %s is outside that package's 'lib' directory (%s), so I cannot construct a URI to reach it."
- % (fn, pkg.name, pkglib))
- name = fn[len(libdir):].lstrip(SEP)[:-len(".js")]
- return name
- def parse_main(self, root_dir, main, check_lib_dir=None):
- # 'main' can be like one of the following:
- # a: ./lib/main.js b: ./lib/main c: lib/main
- # we require it to be a path to the file, though, and ignore the
- # .directories stuff. So just "main" is insufficient if you really
- # want something in a "lib/" subdirectory.
- if main.endswith(".js"):
- main = main[:-len(".js")]
- if main.startswith("./"):
- main = main[len("./"):]
- # package.json must always use "/", but on windows we'll replace that
- # with "\" before using it as an actual filename
- main = os.sep.join(main.split("/"))
- paths = [os.path.join(root_dir, main+".js")]
- if check_lib_dir is not None:
- paths.append(os.path.join(root_dir, check_lib_dir, main+".js"))
- return paths
- def find_top_js(self, target_cfg):
- for libdir in target_cfg.lib:
- for n in self.parse_main(target_cfg.root_dir, target_cfg.main,
- libdir):
- if os.path.exists(n):
- return n
- raise KeyError("unable to find main module '%s.js' in top-level package" % target_cfg.main)
- def find_top(self, target_cfg):
- top_js = self.find_top_js(target_cfg)
- n = os.path.join(target_cfg.root_dir, "README.md")
- if os.path.exists(n):
- top_docs = n
- else:
- top_docs = None
- name = self.uri_name_from_path(target_cfg, top_js)
- return ModuleInfo(target_cfg, "lib", name, top_js, top_docs)
- def process_module(self, mi):
- pkg = mi.package
- #print "ENTERING", pkg.name, mi.name
- # mi.name must be fully-qualified
- assert (not mi.name.startswith("./") and
- not mi.name.startswith("../"))
- # create and claim the manifest row first
- me = self.get_manifest_entry(pkg.name, mi.section, mi.name)
- me.add_js(mi.js)
- if mi.docs:
- me.add_docs(mi.docs)
- js_lines = open(mi.js,"r").readlines()
- requires, problems, locations = scan_module(mi.js,js_lines,self.stderr)
- if problems:
- # the relevant instructions have already been written to stderr
- raise BadChromeMarkerError()
- # We update our requirements on the way out of the depth-first
- # traversal of the module graph
- for reqname in sorted(requires.keys()):
- # If requirement is chrome or a pseudo-module (starts with @) make
- # path a requirement name.
- if reqname == "chrome" or reqname.startswith("@"):
- me.add_requirement(reqname, reqname)
- else:
- # when two modules require() the same name, do they get a
- # shared instance? This is a deep question. For now say yes.
- # find_req_for() returns an entry to put in our
- # 'requirements' dict, and will recursively process
- # everything transitively required from here. It will also
- # populate the self.modules[] cache. Note that we must
- # tolerate cycles in the reference graph.
- looked_in = [] # populated by subroutines
- them_me = self.find_req_for(mi, reqname, looked_in, locations)
- if them_me is None:
- if mi.section == "tests":
- # tolerate missing modules in tests, because
- # test-securable-module.js, and the modules/red.js
- # that it imports, both do that intentionally
- continue
- lineno = locations.get(reqname) # None means define()
- if lineno is None:
- reqtype = "define"
- else:
- reqtype = "require"
- err = ModuleNotFoundError(reqtype, reqname,
- mi.js, lineno, looked_in)
- raise err
- else:
- me.add_requirement(reqname, them_me)
- return me
- #print "LEAVING", pkg.name, mi.name
- def find_req_for(self, from_module, reqname, looked_in, locations):
- # handle a single require(reqname) statement from from_module .
- # Return a uri that exists in self.manifest
- # Populate looked_in with places we looked.
- def BAD(msg):
- return BadModuleIdentifier(msg + " in require(%s) from %s" %
- (reqname, from_module))
- if not reqname:
- raise BAD("no actual modulename")
- # Allow things in tests/*.js to require both test code and real code.
- # But things in lib/*.js can only require real code.
- if from_module.section == "tests":
- lookfor_sections = ["tests", "lib"]
- elif from_module.section == "lib":
- lookfor_sections = ["lib"]
- else:
- raise BadSection(from_module.section)
- modulename = from_module.name
- #print " %s require(%s))" % (from_module, reqname)
- if reqname.startswith("./") or reqname.startswith("../"):
- # 1: they want something relative to themselves, always from
- # their own package
- them = modulename.split("/")[:-1]
- bits = reqname.split("/")
- while bits[0] in (".", ".."):
- if not bits:
- raise BAD("no actual modulename")
- if bits[0] == "..":
- if not them:
- raise BAD("too many ..")
- them.pop()
- bits.pop(0)
- bits = them+bits
- lookfor_pkg = from_module.package.name
- lookfor_mod = "/".join(bits)
- return self._get_module_from_package(lookfor_pkg,
- lookfor_sections, lookfor_mod,
- looked_in)
- # non-relative import. Might be a short name (requiring a search
- # through "library" packages), or a fully-qualified one.
- if "/" in reqname:
- # 2: PKG/MOD: find PKG, look inside for MOD
- bits = reqname.split("/")
- lookfor_pkg = bits[0]
- lookfor_mod = "/".join(bits[1:])
- mi = self._get_module_from_package(lookfor_pkg,
- lookfor_sections, lookfor_mod,
- looked_in)
- if mi: # caution, 0==None
- return mi
- else:
- # 3: try finding PKG, if found, use its main.js entry point
- lookfor_pkg = reqname
- mi = self._get_entrypoint_from_package(lookfor_pkg, looked_in)
- if mi:
- return mi
- # 4: search packages for MOD or MODPARENT/MODCHILD. We always search
- # their own package first, then the list of packages defined by their
- # .dependencies list
- from_pkg = from_module.package.name
- mi = self._search_packages_for_module(from_pkg,
- lookfor_sections, reqname,
- looked_in)
- if mi:
- return mi
- # Only after we look for module in the addon itself, search for a module
- # in new layout.
- # First normalize require argument in order to easily find a mapping
- normalized = reqname
- if normalized.endswith(".js"):
- normalized = normalized[:-len(".js")]
- if normalized.startswith("addon-kit/"):
- normalized = normalized[len("addon-kit/"):]
- if normalized.startswith("api-utils/"):
- normalized = normalized[len("api-utils/"):]
- if normalized in NEW_LAYOUT_MAPPING:
- # get the new absolute path for this module
- original_reqname = reqname
- reqname = NEW_LAYOUT_MAPPING[normalized]
- from_pkg = from_module.package.name
- # If the addon didn't explicitely told us to ignore deprecated
- # require path, warn the developer:
- # (target_cfg is the package.json file)
- if not "ignore-deprecated-path" in self.target_cfg:
- lineno = locations.get(original_reqname)
- print >>self.stderr, "Warning: Use of deprecated require path:"
- print >>self.stderr, " In %s:%d:" % (from_module.js, lineno)
- print >>self.stderr, " require('%s')." % original_reqname
- print >>self.stderr, " New path should be:"
- print >>self.stderr, " require('%s')" % reqname
- return self._search_packages_for_module(from_pkg,
- lookfor_sections, reqname,
- looked_in)
- else:
- # We weren't able to find this module, really.
- return None
- def _handle_module(self, mi):
- if not mi:
- return None
- # we tolerate cycles in the reference graph, which means we need to
- # populate the self.modules cache before recursing into
- # process_module() . We must also check the cache first, so recursion
- # can terminate.
- if mi in self.modules:
- return self.modules[mi]
- # this creates the entry
- new_entry = self.get_manifest_entry(mi.package.name, mi.section, mi.name)
- # and populates the cache
- self.modules[mi] = new_entry
- self.process_module(mi)
- return new_entry
- def _get_module_from_package(self, pkgname, sections, modname, looked_in):
- if pkgname not in self.pkg_cfg.packages:
- return None
- mi = self._find_module_in_package(pkgname, sections, modname,
- looked_in)
- return self._handle_module(mi)
- def _get_entrypoint_from_package(self, pkgname, looked_in):
- if pkgname not in self.pkg_cfg.packages:
- return None
- pkg = self.pkg_cfg.packages[pkgname]
- main = pkg.get("main", None)
- if not main:
- return None
- for js in self.parse_main(pkg.root_dir, main):
- looked_in.append(js)
- if os.path.exists(js):
- section = "lib"
- name = self.uri_name_from_path(pkg, js)
- docs = None
- mi = ModuleInfo(pkg, section, name, js, docs)
- return self._handle_module(mi)
- return None
- def _search_packages_for_module(self, from_pkg, sections, reqname,
- looked_in):
- searchpath = [] # list of package names
- searchpath.append(from_pkg) # search self first
- us = self.pkg_cfg.packages[from_pkg]
- if 'dependencies' in us:
- # only look in dependencies
- searchpath.extend(us['dependencies'])
- else:
- # they didn't declare any dependencies (or they declared an empty
- # list, but we'll treat that as not declaring one, because it's
- # easier), so look in all deps, sorted alphabetically, so
- # addon-kit comes first. Note that self.deps includes all
- # packages found by traversing the ".dependencies" lists in each
- # package.json, starting from the main addon package, plus
- # everything added by --extra-packages
- searchpath.extend(sorted(self.deps))
- for pkgname in searchpath:
- mi = self._find_module_in_package(pkgname, sections, reqname,
- looked_in)
- if mi:
- return self._handle_module(mi)
- return None
- def _find_module_in_package(self, pkgname, sections, name, looked_in):
- # require("a/b/c") should look at ...\a\b\c.js on windows
- filename = os.sep.join(name.split("/"))
- # normalize filename, make sure that we do not add .js if it already has
- # it.
- if not filename.endswith(".js") and not filename.endswith(".json"):
- filename += ".js"
- if filename.endswith(".js"):
- basename = filename[:-3]
- if filename.endswith(".json"):
- basename = filename[:-5]
- pkg = self.pkg_cfg.packages[pkgname]
- if isinstance(sections, basestring):
- sections = [sections]
- for section in sections:
- for sdir in pkg.get(section, []):
- js = os.path.join(pkg.root_dir, sdir, filename)
- looked_in.append(js)
- if os.path.exists(js):
- docs = None
- maybe_docs = os.path.join(pkg.root_dir, "docs",
- basename+".md")
- if section == "lib" and os.path.exists(maybe_docs):
- docs = maybe_docs
- return ModuleInfo(pkg, section, name, js, docs)
- return None
- def build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
- test_filter_re=None, extra_modules=[]):
- """
- Perform recursive dependency analysis starting from entry_point,
- building up a manifest of modules that need to be included in the XPI.
- Each entry will map require() names to the URL of the module that will
- be used to satisfy that dependency. The manifest will be used by the
- runtime's require() code.
- This returns a ManifestBuilder object, with two public methods. The
- first, get_module_entries(), returns a set of ManifestEntry objects, each
- of which can be asked for the following:
- * its contribution to the harness-options.json '.manifest'
- * the local disk name
- * the name in the XPI at which it should be placed
- The second is get_data_entries(), which returns a set of DataEntry
- objects, each of which has:
- * local disk name
- * name in the XPI
- note: we don't build the XPI here, but our manifest is passed to the
- code which does, so it knows what to copy into the XPI.
- """
- mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules)
- mxt.build(scan_tests, test_filter_re)
- return mxt
- COMMENT_PREFIXES = ["//", "/*", "*", "dump("]
- REQUIRE_RE = r"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)"
- # detect the define idiom of the form:
- # define("module name", ["dep1", "dep2", "dep3"], function() {})
- # by capturing the contents of the list in a group.
- DEF_RE = re.compile(r"(require|define)\s*\(\s*([\'\"][^\'\"]+[\'\"]\s*,)?\s*\[([^\]]+)\]")
- # Out of the async dependencies, do not allow quotes in them.
- DEF_RE_ALLOWED = re.compile(r"^[\'\"][^\'\"]+[\'\"]$")
- def scan_requirements_with_grep(fn, lines):
- requires = {}
- first_location = {}
- for (lineno0, line) in enumerate(lines):
- for clause in line.split(";"):
- clause = clause.strip()
- iscomment = False
- for commentprefix in COMMENT_PREFIXES:
- if clause.startswith(commentprefix):
- iscomment = True
- if iscomment:
- continue
- mo = re.finditer(REQUIRE_RE, clause)
- if mo:
- for mod in mo:
- modname = mod.group(1)
- requires[modname] = {}
- if modname not in first_location:
- first_location[modname] = lineno0 + 1
- # define() can happen across multiple lines, so join everyone up.
- wholeshebang = "\n".join(lines)
- for match in DEF_RE.finditer(wholeshebang):
- # this should net us a list of string literals separated by commas
- for strbit in match.group(3).split(","):
- strbit = strbit.strip()
- # There could be a trailing comma netting us just whitespace, so
- # filter that out. Make sure that only string values with
- # quotes around them are allowed, and no quotes are inside
- # the quoted value.
- if strbit and DEF_RE_ALLOWED.match(strbit):
- modname = strbit[1:-1]
- if modname not in ["exports"]:
- requires[modname] = {}
- # joining all the lines means we lose line numbers, so we
- # can't fill first_location[]
- return requires, first_location
- CHROME_ALIASES = [
- (re.compile(r"Components\.classes"), "Cc"),
- (re.compile(r"Components\.interfaces"), "Ci"),
- (re.compile(r"Components\.utils"), "Cu"),
- (re.compile(r"Components\.results"), "Cr"),
- (re.compile(r"Components\.manager"), "Cm"),
- ]
- OTHER_CHROME = re.compile(r"Components\.[a-zA-Z]")
- def scan_for_bad_chrome(fn, lines, stderr):
- problems = False
- old_chrome = set() # i.e. "Cc" when we see "Components.classes"
- old_chrome_lines = [] # list of (lineno, line.strip()) tuples
- for lineno,line in enumerate(lines):
- # note: this scanner is not obligated to spot all possible forms of
- # chrome access. The scanner is detecting voluntary requests for
- # chrome. Runtime tools will enforce allowance or denial of access.
- line = line.strip()
- iscomment = False
- for commentprefix in COMMENT_PREFIXES:
- if line.startswith(commentprefix):
- iscomment = True
- break
- if iscomment:
- continue
- old_chrome_in_this_line = set()
- for (regexp,alias) in CHROME_ALIASES:
- if regexp.search(line):
- old_chrome_in_this_line.add(alias)
- if not old_chrome_in_this_line:
- if OTHER_CHROME.search(line):
- old_chrome_in_this_line.add("components")
- old_chrome.update(old_chrome_in_this_line)
- if old_chrome_in_this_line:
- old_chrome_lines.append( (lineno+1, line) )
- if old_chrome:
- print >>stderr, """
- The following lines from file %(fn)s:
- %(lines)s
- use 'Components' to access chrome authority. To do so, you need to add a
- line somewhat like the following:
- const {%(needs)s} = require("chrome");
- Then you can use any shortcuts to its properties that you import from the
- 'chrome' module ('Cc', 'Ci', 'Cm', 'Cr', and 'Cu' for the 'classes',
- 'interfaces', 'manager', 'results', and 'utils' properties, respectively. And
- `components` for `Components` object itself).
- """ % { "fn": fn, "needs": ",".join(sorted(old_chrome)),
- "lines": "\n".join([" %3d: %s" % (lineno,line)
- for (lineno, line) in old_chrome_lines]),
- }
- problems = True
- return problems
- def scan_module(fn, lines, stderr=sys.stderr):
- filename = os.path.basename(fn)
- requires, locations = scan_requirements_with_grep(fn, lines)
- if filename == "cuddlefish.js":
- # this is the loader: don't scan for chrome
- problems = False
- else:
- problems = scan_for_bad_chrome(fn, lines, stderr)
- return requires, problems, locations
- if __name__ == '__main__':
- for fn in sys.argv[1:]:
- requires, problems, locations = scan_module(fn, open(fn).readlines())
- print
- print "---", fn
- if problems:
- print "PROBLEMS"
- sys.exit(1)
- print "requires: %s" % (",".join(sorted(requires.keys())))
- print "locations: %s" % locations
|