import json import os import re import struct from collections import defaultdict from uuid import UUID import buildconfig from mozbuild.util import FileAvoidWrite from perfecthash import PerfectHash NO_CONTRACT_ID = 0xFFFFFFFF PHF_SIZE = 512 TINY_PHF_SIZE = 16 # In tests, we might not have a (complete) buildconfig. ENDIAN = ( "<" if buildconfig.substs.get("TARGET_ENDIANNESS", "little") == "little" else ">" ) # Represents a UUID in the format used internally by Gecko, and supports # serializing it in that format to both C++ source and raw byte arrays. class UUIDRepr(object): def __init__(self, uuid): self.uuid = uuid fields = uuid.fields self.a = fields[0] self.b = fields[1] self.c = fields[2] d = list(fields[3:5]) for i in range(0, 6): d.append(fields[5] >> (8 * (5 - i)) & 0xFF) self.d = tuple(d) def __str__(self): return str(self.uuid) @property def bytes(self): return struct.pack(ENDIAN + "IHHBBBBBBBB", self.a, self.b, self.c, *self.d) def to_cxx(self): rest = ", ".join("0x%02x" % b for b in self.d) return "{ 0x%x, 0x%x, 0x%x, { %s } }" % (self.a, self.b, self.c, rest) # Corresponds to the Module::ProcessSelector enum in Module.h. The actual # values don't matter, since the code generator emits symbolic constants for # these values, but we use the same values as the enum constants for clarity. class ProcessSelector: ANY_PROCESS = 0x0 MAIN_PROCESS_ONLY = 0x1 CONTENT_PROCESS_ONLY = 0x2 ALLOW_IN_GPU_PROCESS = 0x4 ALLOW_IN_VR_PROCESS = 0x8 ALLOW_IN_SOCKET_PROCESS = 0x10 ALLOW_IN_RDD_PROCESS = 0x20 ALLOW_IN_UTILITY_PROCESS = 0x30 ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY ALLOW_IN_GPU_AND_SOCKET_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = ( ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS ) ALLOW_IN_RDD_AND_SOCKET_PROCESS = ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = ( ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS ) ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = ( ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS | ALLOW_IN_UTILITY_PROCESS ) ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = ( ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS ) ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = ( ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS | ALLOW_IN_UTILITY_PROCESS ) # Maps ProcessSelector constants to the name of the corresponding # Module::ProcessSelector enum value. PROCESSES = { ProcessSelector.ANY_PROCESS: "ANY_PROCESS", ProcessSelector.MAIN_PROCESS_ONLY: "MAIN_PROCESS_ONLY", ProcessSelector.CONTENT_PROCESS_ONLY: "CONTENT_PROCESS_ONLY", ProcessSelector.ALLOW_IN_GPU_PROCESS: "ALLOW_IN_GPU_PROCESS", ProcessSelector.ALLOW_IN_VR_PROCESS: "ALLOW_IN_VR_PROCESS", ProcessSelector.ALLOW_IN_SOCKET_PROCESS: "ALLOW_IN_SOCKET_PROCESS", ProcessSelector.ALLOW_IN_RDD_PROCESS: "ALLOW_IN_RDD_PROCESS", ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS: "ALLOW_IN_GPU_AND_MAIN_PROCESS", ProcessSelector.ALLOW_IN_GPU_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_AND_SOCKET_PROCESS", ProcessSelector.ALLOW_IN_GPU_AND_VR_PROCESS: "ALLOW_IN_GPU_AND_VR_PROCESS", ProcessSelector.ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS", ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_RDD_AND_SOCKET_PROCESS", ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS", ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS", # NOQA: E501 ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 } # Emits the C++ symbolic constant corresponding to a ProcessSelector constant. def lower_processes(processes): return "Module::ProcessSelector::%s" % PROCESSES[processes] # Emits the C++ symbolic constant for a ModuleEntry's ModuleID enum entry. def lower_module_id(module): return "ModuleID::%s" % module.name # Corresponds to the Module::BackgroundTasksSelector enum in Module.h. The # actual values don't matter, since the code generator emits symbolic constants # for these values, but we use the same values as the enum constants for # clarity. class BackgroundTasksSelector: NO_TASKS = 0x0 ALL_TASKS = 0xFFFF # Maps BackgroundTasksSelector constants to the name of the corresponding # Module::BackgroundTasksSelector enum value. BACKGROUNDTASKS = { BackgroundTasksSelector.ALL_TASKS: "ALL_TASKS", BackgroundTasksSelector.NO_TASKS: "NO_TASKS", } # Emits the C++ symbolic constant corresponding to a BackgroundTasks constant. def lower_backgroundtasks(backgroundtasks): return "Module::BackgroundTasksSelector::%s" % BACKGROUNDTASKS[backgroundtasks] # Represents a static string table, indexed by offset. This allows us to # reference strings from static data structures without requiring runtime # relocations. class StringTable(object): def __init__(self): self.entries = {} self.entry_list = [] self.size = 0 self._serialized = False # Returns the index of the given string in the `entry_list` array. If # no entry for the string exists, it first creates one. def get_idx(self, string): idx = self.entries.get(string, None) if idx is not None: return idx assert not self._serialized assert len(string) == len(string.encode("utf-8")) idx = self.size self.size += len(string) + 1 self.entries[string] = idx self.entry_list.append(string) return idx # Returns the C++ code representing string data of this string table, as a # single string literal. This must only be called after the last call to # `get_idx()` or `entry_to_cxx()` for this instance. def to_cxx(self): self._serialized = True lines = [] idx = 0 for entry in self.entry_list: str_ = entry.replace("\\", "\\\\").replace('"', r"\"").replace("\n", r"\n") lines.append(' /* 0x%x */ "%s\\0"\n' % (idx, str_)) idx += len(entry) + 1 return "".join(lines) # Returns a `StringEntry` struct initializer for the string table entry # corresponding to the given string. If no matching entry exists, it is # first created. def entry_to_cxx(self, string): idx = self.get_idx(string) return "{ 0x%x } /* %s */" % (idx, pretty_string(string)) strings = StringTable() interfaces = [] # Represents a C++ namespace, containing a set of classes and potentially # sub-namespaces. This is used to generate pre-declarations for incomplete # types referenced in XPCOM manifests. class Namespace(object): def __init__(self, name=None): self.name = name self.classes = set() self.namespaces = {} # Returns a Namespace object for the sub-namespace with the given name. def sub(self, name): assert name not in self.classes if name not in self.namespaces: self.namespaces[name] = Namespace(name) return self.namespaces[name] # Generates C++ code to pre-declare all classes in this namespace and all # of its sub-namespaces. def to_cxx(self): res = "" if self.name: res += "namespace %s {\n" % self.name for clas in sorted(self.classes): res += "class %s;\n" % clas for ns in sorted(self.namespaces.keys()): res += self.namespaces[ns].to_cxx() if self.name: res += "} // namespace %s\n" % self.name return res # Represents a component defined in an XPCOM manifest's `Classes` array. class ModuleEntry(object): next_anon_id = 0 def __init__(self, data, init_idx): self.cid = UUIDRepr(UUID(data["cid"])) self.contract_ids = data.get("contract_ids", []) self.type = data.get("type", "nsISupports") self.categories = data.get("categories", {}) self.processes = data.get("processes", 0) self.headers = data.get("headers", []) self.js_name = data.get("js_name", None) self.interfaces = data.get("interfaces", []) if len(self.interfaces) > 255: raise Exception( "JS service %s may not have more than 255 " "interfaces" % self.js_name ) self.interfaces_offset = len(interfaces) for iface in self.interfaces: interfaces.append(iface) # If the manifest declares Init or Unload functions, this contains its # index, as understood by the `CallInitFunc()` function. # # If it contains any value other than `None`, a corresponding # `CallInitFunc(init_idx)` call will be genrated before calling this # module's constructor. self.init_idx = init_idx self.constructor = data.get("constructor", None) self.legacy_constructor = data.get("legacy_constructor", None) self.init_method = data.get("init_method", []) self.jsm = data.get("jsm", None) self.esModule = data.get("esModule", None) self.external = data.get( "external", not (self.headers or self.legacy_constructor) ) self.singleton = data.get("singleton", False) self.overridable = data.get("overridable", False) self.protocol_config = data.get("protocol_config", None) if "name" in data: self.anonymous = False self.name = data["name"] else: self.anonymous = True self.name = "Anonymous%03d" % ModuleEntry.next_anon_id ModuleEntry.next_anon_id += 1 def error(str_): raise Exception( "Error defining component %s (%s): %s" % (str(self.cid), ", ".join(map(repr, self.contract_ids)), str_) ) if self.jsm: if not self.constructor: error("JavaScript components must specify a constructor") for prop in ("init_method", "legacy_constructor", "headers"): if getattr(self, prop): error( "JavaScript components may not specify a '%s' " "property" % prop ) elif self.esModule: if not self.constructor: error("JavaScript components must specify a constructor") for prop in ("init_method", "legacy_constructor", "headers"): if getattr(self, prop): error( "JavaScript components may not specify a '%s' " "property" % prop ) elif self.external: if self.constructor or self.legacy_constructor: error( "Externally-constructed components may not specify " "'constructor' or 'legacy_constructor' properties" ) if self.init_method: error( "Externally-constructed components may not specify " "'init_method' properties" ) if self.type == "nsISupports": error( "Externally-constructed components must specify a type " "other than nsISupports" ) if self.constructor and self.legacy_constructor: error( "The 'constructor' and 'legacy_constructor' properties " "are mutually exclusive" ) if self.overridable and not self.contract_ids: error("Overridable components must specify at least one contract " "ID") @property def contract_id(self): return self.contract_ids[0] # Generates the C++ code for a StaticModule struct initializer # representing this component. def to_cxx(self): contract_id = ( strings.entry_to_cxx(self.contract_id) if self.overridable else "{ 0x%x }" % NO_CONTRACT_ID ) return """ /* {name} */ {{ /* {{{cid_string}}} */ {cid}, {contract_id}, {processes}, }}""".format( name=self.name, cid=self.cid.to_cxx(), cid_string=str(self.cid), contract_id=contract_id, processes=lower_processes(self.processes), ) # Generates the C++ code for a JSServiceEntry representing this module. def lower_js_service(self): return """ {{ {js_name}, ModuleID::{name}, {{ {iface_offset} }}, {iface_count} }}""".format( js_name=strings.entry_to_cxx(self.js_name), name=self.name, iface_offset=self.interfaces_offset, iface_count=len(self.interfaces), ) # Generates the C++ code necessary to construct an instance of this # component. # # This code lives in a function with the following arguments: # # - aIID: The `const nsIID&` interface ID that the resulting instance # will be queried to. # # - aResult: The `void**` pointer in which to store the result. # # And which returns an `nsresult` indicating success or failure. def lower_constructor(self): res = "" if self.init_idx is not None: res += " MOZ_TRY(CallInitFunc(%d));\n" % self.init_idx if self.legacy_constructor: res += ( " return /* legacy */ %s(aIID, aResult);\n" % self.legacy_constructor ) return res if self.jsm: res += ( " nsCOMPtr inst;\n" " MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n" " %s,\n" " getter_AddRefs(inst)));" "\n" % (json.dumps(self.jsm), json.dumps(self.constructor)) ) elif self.esModule: res += ( " nsCOMPtr inst;\n" " MOZ_TRY(ConstructESModuleComponent(nsLiteralCString(%s),\n" " %s,\n" " getter_AddRefs(inst)));" "\n" % (json.dumps(self.esModule), json.dumps(self.constructor)) ) elif self.external: res += ( " nsCOMPtr inst = " "mozCreateComponent<%s>();\n" % self.type ) # The custom constructor may return null, so check before calling # any methods. res += " NS_ENSURE_TRUE(inst, NS_ERROR_FAILURE);\n" else: res += " RefPtr<%s> inst = " % self.type if not self.constructor: res += "new %s();\n" % self.type else: res += "%s();\n" % self.constructor # The `new` operator is infallible, so we don't need to worry # about it returning null, but custom constructors may, so # check before calling any methods. res += " NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);\n" # Check that the constructor function returns an appropriate # `already_AddRefed` value for our declared type. res += """ using T = RemoveAlreadyAddRefed::Type; static_assert( std::is_same_v, decltype(%(constructor)s())>, "Singleton constructor must return already_AddRefed"); static_assert( std::is_base_of<%(type)s, T>::value, "Singleton constructor must return correct already_AddRefed"); """ % { "type": self.type, "constructor": self.constructor, } if self.init_method: res += " MOZ_TRY(inst->%s());\n" % self.init_method res += " return inst->QueryInterface(aIID, aResult);\n" return res # Generates the C++ code for the `mozilla::components::` entry # corresponding to this component. This may not be called for modules # without an explicit `name` (in which cases, `self.anonymous` will be # true). def lower_getters(self): assert not self.anonymous substs = { "name": self.name, "id": "::mozilla::xpcom::ModuleID::%s" % self.name, } res = ( """ namespace %(name)s { static inline const nsID& CID() { return ::mozilla::xpcom::Components::GetCID(%(id)s); } static inline ::mozilla::xpcom::GetServiceHelper Service(nsresult* aRv = nullptr) { return {%(id)s, aRv}; } """ % substs ) if not self.singleton: res += ( """ static inline ::mozilla::xpcom::CreateInstanceHelper Create(nsresult* aRv = nullptr) { return {%(id)s, aRv}; } """ % substs ) res += ( """\ } // namespace %(name)s """ % substs ) return res # Generates the rust code for the `xpcom::components::` entry # corresponding to this component. This may not be called for modules # without an explicit `name` (in which cases, `self.anonymous` will be # true). def lower_getters_rust(self): assert not self.anonymous substs = { "name": self.name, "id": "super::ModuleID::%s" % self.name, } res = ( """ #[allow(non_snake_case)] pub mod %(name)s { /// Get the singleton service instance for this component. pub fn service() -> Result, nserror::nsresult> { let mut ga = crate::GetterAddrefs::::new(); let rv = unsafe { super::Gecko_GetServiceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; if rv.failed() { return Err(rv); } ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) } """ % substs ) if not self.singleton: res += ( """ /// Create a new instance of this component. pub fn create() -> Result, nserror::nsresult> { let mut ga = crate::GetterAddrefs::::new(); let rv = unsafe { super::Gecko_CreateInstanceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; if rv.failed() { return Err(rv); } ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) } """ % substs ) res += """\ } """ return res # Returns a quoted string literal representing the given raw string, with # certain special characters replaced so that it can be used in a C++-style # (/* ... */) comment. def pretty_string(string): return json.dumps(string).replace("*/", r"*\/").replace("/*", r"/\*") # Represents a static contract ID entry, corresponding to a C++ ContractEntry # struct, mapping a contract ID to a static module entry. class ContractEntry(object): def __init__(self, contract, module): self.contract = contract self.module = module def to_cxx(self): return """ {{ {contract}, {module_id}, }}""".format( contract=strings.entry_to_cxx(self.contract), module_id=lower_module_id(self.module), ) # Represents a static ProtocolHandler entry, corresponding to a C++ # ProtocolEntry struct, mapping a scheme to a static module entry and metadata. class ProtocolHandler(object): def __init__(self, config, module): def error(str_): raise Exception( "Error defining protocol handler %s (%s): %s" % (str(module.cid), ", ".join(map(repr, module.contract_ids)), str_) ) self.module = module self.scheme = config.get("scheme", None) if self.scheme is None: error("No scheme defined for protocol component") self.flags = config.get("flags", None) if self.flags is None: error("No flags defined for protocol component") self.default_port = config.get("default_port", -1) self.has_dynamic_flags = config.get("has_dynamic_flags", False) def to_cxx(self): return """ {{ .mScheme = {scheme}, .mModuleID = {module_id}, .mProtocolFlags = {flags}, .mDefaultPort = {default_port}, .mHasDynamicFlags = {has_dynamic_flags}, }} """.format( scheme=strings.entry_to_cxx(self.scheme), module_id=lower_module_id(self.module), flags=" | ".join("nsIProtocolHandler::%s" % flag for flag in self.flags), default_port=self.default_port, has_dynamic_flags="true" if self.has_dynamic_flags else "false", ) # Generates the C++ code for the StaticCategoryEntry and StaticCategory # structs for all category entries declared in XPCOM manifests. def gen_categories(substs, categories): cats = [] ents = [] count = 0 for category, entries in sorted(categories.items()): def k(entry): return tuple(entry[0]["name"]) + entry[1:] entries.sort(key=k) cats.append( " { %s,\n" " %d, %d },\n" % (strings.entry_to_cxx(category), count, len(entries)) ) count += len(entries) ents.append(" /* %s */\n" % pretty_string(category)) for entry, value, processes in entries: name = entry["name"] backgroundtasks = entry.get( "backgroundtasks", BackgroundTasksSelector.NO_TASKS ) ents.append( " { %s,\n" " %s,\n" " %s,\n" " %s },\n" % ( strings.entry_to_cxx(name), strings.entry_to_cxx(value), lower_backgroundtasks(backgroundtasks), lower_processes(processes), ) ) ents.append("\n") ents.pop() substs["category_count"] = len(cats) substs["categories"] = "".join(cats) substs["category_entries"] = "".join(ents) # Generates the C++ code for all Init and Unload functions declared in XPCOM # manifests. These form the bodies of the `CallInitFunc()` and `CallUnload` # functions in StaticComponents.cpp. def gen_module_funcs(substs, funcs): inits = [] unloads = [] template = """\ case %d: %s break; """ for i, (init, unload) in enumerate(funcs): init_code = "%s();" % init if init else "/* empty */" inits.append(template % (i, init_code)) if unload: unloads.append( """\ if (CalledInit(%d)) { %s(); } """ % (i, unload) ) substs["init_funcs"] = "".join(inits) substs["unload_funcs"] = "".join(unloads) substs["init_count"] = len(funcs) def gen_interfaces(ifaces): res = [] for iface in ifaces: res.append(" nsXPTInterface::%s,\n" % iface) return "".join(res) # Generates class pre-declarations for any types referenced in `Classes` array # entries which do not have corresponding `headers` entries to fully declare # their types. def gen_decls(types): root_ns = Namespace() for type_ in sorted(types): parts = type_.split("::") ns = root_ns for part in parts[:-1]: ns = ns.sub(part) ns.classes.add(parts[-1]) return root_ns.to_cxx() # Generates the `switch` body for the `CreateInstanceImpl()` function, with a # `case` for each value in ModuleID to construct an instance of the # corresponding component. def gen_constructors(entries): constructors = [] for entry in entries: constructors.append( """\ case {id}: {{ {constructor}\ }} """.format( id=lower_module_id(entry), constructor=entry.lower_constructor() ) ) return "".join(constructors) # Generates the getter code for each named component entry in the # `mozilla::components::` namespace. def gen_getters(entries): entries = list(entries) entries.sort(key=lambda e: e.name) return "".join(entry.lower_getters() for entry in entries if not entry.anonymous) # Generates the rust getter code for each named component entry in the # `xpcom::components::` module. def gen_getters_rust(entries): entries = list(entries) entries.sort(key=lambda e: e.name) return "".join( entry.lower_getters_rust() for entry in entries if not entry.anonymous ) def gen_includes(substs, all_headers): headers = set() absolute_headers = set() for header in all_headers: if header.startswith("/"): absolute_headers.add(header) else: headers.add(header) includes = ['#include "%s"' % header for header in sorted(headers)] substs["includes"] = "\n".join(includes) + "\n" relative_includes = [ '#include "../..%s"' % header for header in sorted(absolute_headers) ] substs["relative_includes"] = "\n".join(relative_includes) + "\n" def to_category_list(val): # Entries can be bare strings (like `"m-browser"`), lists of bare strings, # or dictionaries (like `{"name": "m-browser", "backgroundtasks": # BackgroundTasksSelector.ALL_TASKS}`), somewhat recursively. def ensure_dict(v): # Turn `v` into `{"name": v}` if it's not already a dict. if isinstance(v, dict): return v return {"name": v} if isinstance(val, (list, tuple)): return tuple(ensure_dict(v) for v in val) if isinstance(val, dict): # Explode `{"name": ["x", "y"], "backgroundtasks": ...}` into # `[{"name": "x", "backgroundtasks": ...}, {"name": "y", "backgroundtasks": ...}]`. names = val.pop("name") vals = [] for entry in to_category_list(names): d = dict(val) d["name"] = entry["name"] vals.append(d) return tuple(vals) return (ensure_dict(val),) def gen_substs(manifests): module_funcs = [] headers = set() modules = [] categories = defaultdict(list) for manifest in manifests: headers |= set(manifest.get("Headers", [])) init_idx = None init = manifest.get("InitFunc") unload = manifest.get("UnloadFunc") if init or unload: init_idx = len(module_funcs) module_funcs.append((init, unload)) for clas in manifest["Classes"]: modules.append(ModuleEntry(clas, init_idx)) for category, entries in manifest.get("Categories", {}).items(): for key, entry in entries.items(): if isinstance(entry, tuple): value, process = entry else: value, process = entry, 0 categories[category].append(({"name": key}, value, process)) cids = set() contracts = [] contract_map = {} js_services = {} protocol_handlers = {} jsms = set() esModules = set() types = set() for mod in modules: headers |= set(mod.headers) for contract_id in mod.contract_ids: if contract_id in contract_map: raise Exception("Duplicate contract ID: %s" % contract_id) entry = ContractEntry(contract_id, mod) contracts.append(entry) contract_map[contract_id] = entry for category, entries in mod.categories.items(): for entry in to_category_list(entries): categories[category].append((entry, mod.contract_id, mod.processes)) if mod.type and not mod.headers: types.add(mod.type) if mod.jsm: jsms.add(mod.jsm) if mod.esModule: esModules.add(mod.esModule) if mod.js_name: if mod.js_name in js_services: raise Exception("Duplicate JS service name: %s" % mod.js_name) js_services[mod.js_name] = mod if mod.protocol_config: handler = ProtocolHandler(mod.protocol_config, mod) if handler.scheme in protocol_handlers: raise Exception("Duplicate protocol handler: %s" % handler.scheme) protocol_handlers[handler.scheme] = handler if str(mod.cid) in cids: raise Exception("Duplicate cid: %s" % str(mod.cid)) cids.add(str(mod.cid)) cid_phf = PerfectHash(modules, PHF_SIZE, key=lambda module: module.cid.bytes) contract_phf = PerfectHash(contracts, PHF_SIZE, key=lambda entry: entry.contract) js_services_phf = PerfectHash( list(js_services.values()), PHF_SIZE, key=lambda entry: entry.js_name ) protocol_handlers_phf = PerfectHash( list(protocol_handlers.values()), TINY_PHF_SIZE, key=lambda entry: entry.scheme ) js_services_json = {} for entry in js_services.values(): for iface in entry.interfaces: js_services_json[iface] = entry.js_name substs = {} gen_categories(substs, categories) substs["module_ids"] = "".join(" %s,\n" % entry.name for entry in cid_phf.entries) substs["module_count"] = len(modules) substs["contract_count"] = len(contracts) substs["protocol_handler_count"] = len(protocol_handlers) substs["default_protocol_handler_idx"] = protocol_handlers_phf.get_index("default") gen_module_funcs(substs, module_funcs) gen_includes(substs, headers) substs["component_jsms"] = ( "\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n" ) substs["component_esmodules"] = ( "\n".join( " %s," % strings.entry_to_cxx(esModule) for esModule in sorted(esModules) ) + "\n" ) substs["interfaces"] = gen_interfaces(interfaces) substs["decls"] = gen_decls(types) substs["constructors"] = gen_constructors(cid_phf.entries) substs["component_getters"] = gen_getters(cid_phf.entries) substs["component_getters_rust"] = gen_getters_rust(cid_phf.entries) substs["module_cid_table"] = cid_phf.cxx_codegen( name="ModuleByCID", entry_type="StaticModule", entries_name="gStaticModules", lower_entry=lambda entry: entry.to_cxx(), return_type="const StaticModule*", return_entry=( "return entry.CID().Equals(aKey) && entry.Active()" " ? &entry : nullptr;" ), key_type="const nsID&", key_bytes="reinterpret_cast(&aKey)", key_length="sizeof(nsID)", ) substs["module_contract_id_table"] = contract_phf.cxx_codegen( name="LookupContractID", entry_type="ContractEntry", entries_name="gContractEntries", lower_entry=lambda entry: entry.to_cxx(), return_type="const ContractEntry*", return_entry="return entry.Matches(aKey) ? &entry : nullptr;", key_type="const nsACString&", key_bytes="aKey.BeginReading()", key_length="aKey.Length()", ) substs["js_services_table"] = js_services_phf.cxx_codegen( name="LookupJSService", entry_type="JSServiceEntry", entries_name="gJSServices", lower_entry=lambda entry: entry.lower_js_service(), return_type="const JSServiceEntry*", return_entry="return entry.Name() == aKey ? &entry : nullptr;", key_type="const nsACString&", key_bytes="aKey.BeginReading()", key_length="aKey.Length()", ) substs["protocol_handlers_table"] = protocol_handlers_phf.cxx_codegen( name="LookupProtocolHandler", entry_type="StaticProtocolHandler", entries_name="gStaticProtocolHandlers", lower_entry=lambda entry: entry.to_cxx(), return_type="const StaticProtocolHandler*", return_entry="return entry.Scheme() == aKey ? &entry : nullptr;", key_type="const nsACString&", key_bytes="aKey.BeginReading()", key_length="aKey.Length()", ) substs["js_services_json"] = json.dumps(js_services_json, sort_keys=True, indent=4) # Do this only after everything else has been emitted so we're sure the # string table is complete. substs["strings"] = strings.to_cxx() return substs # Returns true if the given build config substitution is defined and truthy. def defined(subst): return bool(buildconfig.substs.get(subst)) def read_manifest(filename): glbl = { "buildconfig": buildconfig, "defined": defined, "ProcessSelector": ProcessSelector, "BackgroundTasksSelector": BackgroundTasksSelector, } code = compile(open(filename).read(), filename, "exec") exec(code, glbl) return glbl def main(fd, conf_file, template_file): def open_output(filename): return FileAvoidWrite(os.path.join(os.path.dirname(fd.name), filename)) conf = json.load(open(conf_file, "r")) deps = set() manifests = [] for filename in conf["manifests"]: deps.add(filename) manifest = read_manifest(filename) manifests.append(manifest) manifest.setdefault("Priority", 50) manifest["__filename__"] = filename manifests.sort(key=lambda man: (man["Priority"], man["__filename__"])) substs = gen_substs(manifests) def replacer(match): return substs[match.group(1)] with open_output("StaticComponents.cpp") as fh: with open(template_file, "r") as tfh: template = tfh.read() fh.write(re.sub(r"//# @([a-zA-Z_]+)@\n", replacer, template)) with open_output("StaticComponentData.h") as fh: fh.write( """\ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #ifndef StaticComponentData_h #define StaticComponentData_h #include namespace mozilla { namespace xpcom { static constexpr size_t kStaticModuleCount = %(module_count)d; static constexpr size_t kContractCount = %(contract_count)d; static constexpr size_t kStaticCategoryCount = %(category_count)d; static constexpr size_t kModuleInitCount = %(init_count)d; static constexpr size_t kStaticProtocolHandlerCount = %(protocol_handler_count)d; static constexpr size_t kDefaultProtocolHandlerIndex = %(default_protocol_handler_idx)d; } // namespace xpcom } // namespace mozilla #endif """ % substs ) with open_output("components.rs") as fh: fh.write( """\ /// Unique IDs for each statically-registered module. #[repr(u16)] pub enum ModuleID { %(module_ids)s } %(component_getters_rust)s """ % substs ) fd.write( """\ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #ifndef mozilla_Components_h #define mozilla_Components_h #include "nsCOMPtr.h" struct nsID; #define NS_IMPL_COMPONENT_FACTORY(iface) \\ template <> \\ already_AddRefed mozCreateComponent() template already_AddRefed mozCreateComponent(); namespace mozilla { namespace xpcom { enum class ModuleID : uint16_t { %(module_ids)s }; // May be added as a friend function to allow constructing services via // private constructors and init methods. nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult); class MOZ_STACK_CLASS StaticModuleHelper : public nsCOMPtr_helper { public: StaticModuleHelper(ModuleID aId, nsresult* aErrorPtr) : mId(aId), mErrorPtr(aErrorPtr) {} protected: nsresult SetResult(nsresult aRv) const { if (mErrorPtr) { *mErrorPtr = aRv; } return aRv; } ModuleID mId; nsresult* mErrorPtr; }; class MOZ_STACK_CLASS GetServiceHelper final : public StaticModuleHelper { public: using StaticModuleHelper::StaticModuleHelper; nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aResult) const override; }; class MOZ_STACK_CLASS CreateInstanceHelper final : public StaticModuleHelper { public: using StaticModuleHelper::StaticModuleHelper; nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aResult) const override; }; class Components final { public: static const nsID& GetCID(ModuleID aID); }; } // namespace xpcom namespace components { %(component_getters)s } // namespace components } // namespace mozilla #endif """ % substs ) with open_output("services.json") as fh: fh.write(substs["js_services_json"]) return deps