123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 |
- import os, sys, re, hashlib
- import simplejson as json
- SEP = os.path.sep
- from cuddlefish.util import filter_filenames, filter_dirnames
- 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
- self.requirement_name = requirement_name
- self.used_by = used_by
- self.line_number = line_number
- self.looked_in = looked_in
- 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 = []
-
-
-
- if self.packageName != "addon-sdk":
- items.append(self.packageName)
-
- 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]
- entry["requirements"][req] = them.get_path()
- else:
-
-
- 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
-
-
- def hash_file(fn):
- return hashlib.sha256(open(fn,"rb").read()).hexdigest()
- def get_datafiles(datadir):
-
- for dirpath, dirnames, filenames in os.walk(datadir):
- filenames = list(filter_filenames(filenames))
-
- 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:
-
- 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 = {}
- self.target_cfg = target_cfg
- self.pkg_cfg = pkg_cfg
- self.deps = deps
- self.used_packagenames = set()
- self.stderr = stderr
- self.extra_modules = extra_modules
- self.modules = {}
- self.datamaps = {}
- self.files = []
- self.test_modules = []
- def build(self, scan_tests, test_filter_re):
-
-
- 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)
-
-
-
-
-
-
- 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]
- 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)
-
- tme = self.process_module(tmi)
- test_modules.append( (testname, tme) )
-
-
- 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)
-
-
-
-
-
- self.test_modules.append(tme.get_path())
-
- 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):
-
-
-
- for datamap in self.datamaps.values():
- for (zipname, absname) in datamap.files_to_copy:
- yield absname
- for me in self.get_module_entries():
-
- 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()
-
-
-
- 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):
-
-
-
-
-
-
-
-
-
-
-
- fn = os.path.abspath(fn)
- pkglib = pkg.lib[0]
- libdir = os.path.abspath(os.path.join(pkg.root_dir, pkglib))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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):
-
-
-
-
-
- if main.endswith(".js"):
- main = main[:-len(".js")]
- if main.startswith("./"):
- main = main[len("./"):]
-
-
- 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
-
-
- assert (not mi.name.startswith("./") and
- not mi.name.startswith("../"))
-
- 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:
-
- raise BadChromeMarkerError()
-
-
- for reqname in sorted(requires.keys()):
-
-
- if reqname == "chrome" or reqname.startswith("@"):
- me.add_requirement(reqname, reqname)
- else:
-
-
-
-
-
-
-
- looked_in = []
- them_me = self.find_req_for(mi, reqname, looked_in, locations)
- if them_me is None:
- if mi.section == "tests":
-
-
-
- continue
- lineno = locations.get(reqname)
- 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
-
- def find_req_for(self, from_module, reqname, looked_in, locations):
-
-
-
- def BAD(msg):
- return BadModuleIdentifier(msg + " in require(%s) from %s" %
- (reqname, from_module))
- if not reqname:
- raise BAD("no actual modulename")
-
-
- 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
-
- if reqname.startswith("./") or reqname.startswith("../"):
-
-
- 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)
-
-
- if "/" in reqname:
-
- 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:
- return mi
- else:
-
- lookfor_pkg = reqname
- mi = self._get_entrypoint_from_package(lookfor_pkg, looked_in)
- if mi:
- return mi
-
-
-
- from_pkg = from_module.package.name
- mi = self._search_packages_for_module(from_pkg,
- lookfor_sections, reqname,
- looked_in)
- if mi:
- return mi
-
-
-
- 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:
-
- original_reqname = reqname
- reqname = NEW_LAYOUT_MAPPING[normalized]
- from_pkg = from_module.package.name
-
-
-
- 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:
-
- return None
- def _handle_module(self, mi):
- if not mi:
- return None
-
-
-
-
- if mi in self.modules:
- return self.modules[mi]
-
- new_entry = self.get_manifest_entry(mi.package.name, mi.section, mi.name)
-
- 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 = []
- searchpath.append(from_pkg)
- us = self.pkg_cfg.packages[from_pkg]
- if 'dependencies' in us:
-
- searchpath.extend(us['dependencies'])
- else:
-
-
-
-
-
-
-
- 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):
-
- filename = os.sep.join(name.split("/"))
-
-
- 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*\)"
- DEF_RE = re.compile(r"(require|define)\s*\(\s*([\'\"][^\'\"]+[\'\"]\s*,)?\s*\[([^\]]+)\]")
- 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
-
- wholeshebang = "\n".join(lines)
- for match in DEF_RE.finditer(wholeshebang):
-
- for strbit in match.group(3).split(","):
- strbit = strbit.strip()
-
-
-
-
- if strbit and DEF_RE_ALLOWED.match(strbit):
- modname = strbit[1:-1]
- if modname not in ["exports"]:
- requires[modname] = {}
-
-
- 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()
- old_chrome_lines = []
- for lineno,line in enumerate(lines):
-
-
-
- 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":
-
- 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
|