1import json
2import os
3import re
4import struct
5from collections import defaultdict
6
7from uuid import UUID
8
9from mozbuild.util import FileAvoidWrite
10from perfecthash import PerfectHash
11import buildconfig
12
13
14NO_CONTRACT_ID = 0xFFFFFFFF
15
16PHF_SIZE = 512
17
18# In tests, we might not have a (complete) buildconfig.
19ENDIAN = (
20    "<" if buildconfig.substs.get("TARGET_ENDIANNESS", "little") == "little" else ">"
21)
22
23
24# Represents a UUID in the format used internally by Gecko, and supports
25# serializing it in that format to both C++ source and raw byte arrays.
26class UUIDRepr(object):
27    def __init__(self, uuid):
28        self.uuid = uuid
29
30        fields = uuid.fields
31
32        self.a = fields[0]
33        self.b = fields[1]
34        self.c = fields[2]
35
36        d = list(fields[3:5])
37        for i in range(0, 6):
38            d.append(fields[5] >> (8 * (5 - i)) & 0xFF)
39
40        self.d = tuple(d)
41
42    def __str__(self):
43        return str(self.uuid)
44
45    @property
46    def bytes(self):
47        return struct.pack(ENDIAN + "IHHBBBBBBBB", self.a, self.b, self.c, *self.d)
48
49    def to_cxx(self):
50        rest = ", ".join("0x%02x" % b for b in self.d)
51
52        return "{ 0x%x, 0x%x, 0x%x, { %s } }" % (self.a, self.b, self.c, rest)
53
54
55# Corresponds to the Module::ProcessSelector enum in Module.h. The actual
56# values don't matter, since the code generator emits symbolic constants for
57# these values, but we use the same values as the enum constants for clarity.
58class ProcessSelector:
59    ANY_PROCESS = 0x0
60    MAIN_PROCESS_ONLY = 0x1
61    CONTENT_PROCESS_ONLY = 0x2
62    ALLOW_IN_GPU_PROCESS = 0x4
63    ALLOW_IN_VR_PROCESS = 0x8
64    ALLOW_IN_SOCKET_PROCESS = 0x10
65    ALLOW_IN_RDD_PROCESS = 0x20
66    ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY
67    ALLOW_IN_GPU_AND_SOCKET_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS
68    ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS
69    ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = (
70        ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS
71    )
72    ALLOW_IN_RDD_AND_SOCKET_PROCESS = ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS
73    ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = (
74        ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS
75    )
76    ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = (
77        ALLOW_IN_GPU_PROCESS
78        | ALLOW_IN_RDD_PROCESS
79        | ALLOW_IN_VR_PROCESS
80        | ALLOW_IN_SOCKET_PROCESS
81    )
82
83
84# Maps ProcessSelector constants to the name of the corresponding
85# Module::ProcessSelector enum value.
86PROCESSES = {
87    ProcessSelector.ANY_PROCESS: "ANY_PROCESS",
88    ProcessSelector.MAIN_PROCESS_ONLY: "MAIN_PROCESS_ONLY",
89    ProcessSelector.CONTENT_PROCESS_ONLY: "CONTENT_PROCESS_ONLY",
90    ProcessSelector.ALLOW_IN_GPU_PROCESS: "ALLOW_IN_GPU_PROCESS",
91    ProcessSelector.ALLOW_IN_VR_PROCESS: "ALLOW_IN_VR_PROCESS",
92    ProcessSelector.ALLOW_IN_SOCKET_PROCESS: "ALLOW_IN_SOCKET_PROCESS",
93    ProcessSelector.ALLOW_IN_RDD_PROCESS: "ALLOW_IN_RDD_PROCESS",
94    ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS: "ALLOW_IN_GPU_AND_MAIN_PROCESS",
95    ProcessSelector.ALLOW_IN_GPU_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_AND_SOCKET_PROCESS",
96    ProcessSelector.ALLOW_IN_GPU_AND_VR_PROCESS: "ALLOW_IN_GPU_AND_VR_PROCESS",
97    ProcessSelector.ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS",
98    ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_RDD_AND_SOCKET_PROCESS",
99    ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS",
100    ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS",  # NOQA: E501
101}
102
103
104# Emits the C++ symbolic constant corresponding to a ProcessSelector constant.
105def lower_processes(processes):
106    return "Module::ProcessSelector::%s" % PROCESSES[processes]
107
108
109# Emits the C++ symbolic constant for a ModuleEntry's ModuleID enum entry.
110def lower_module_id(module):
111    return "ModuleID::%s" % module.name
112
113
114# Corresponds to the Module::BackgroundTasksSelector enum in Module.h. The
115# actual values don't matter, since the code generator emits symbolic constants
116# for these values, but we use the same values as the enum constants for
117# clarity.
118class BackgroundTasksSelector:
119    NO_TASKS = 0x0
120    ALL_TASKS = 0xFFFF
121
122
123# Maps BackgroundTasksSelector constants to the name of the corresponding
124# Module::BackgroundTasksSelector enum value.
125BACKGROUNDTASKS = {
126    BackgroundTasksSelector.ALL_TASKS: "ALL_TASKS",
127    BackgroundTasksSelector.NO_TASKS: "NO_TASKS",
128}
129
130
131# Emits the C++ symbolic constant corresponding to a BackgroundTasks constant.
132def lower_backgroundtasks(backgroundtasks):
133    return "Module::BackgroundTasksSelector::%s" % BACKGROUNDTASKS[backgroundtasks]
134
135
136# Represents a static string table, indexed by offset. This allows us to
137# reference strings from static data structures without requiring runtime
138# relocations.
139class StringTable(object):
140    def __init__(self):
141        self.entries = {}
142        self.entry_list = []
143        self.size = 0
144
145        self._serialized = False
146
147    # Returns the index of the given string in the `entry_list` array. If
148    # no entry for the string exists, it first creates one.
149    def get_idx(self, string):
150        idx = self.entries.get(string, None)
151        if idx is not None:
152            return idx
153
154        assert not self._serialized
155
156        assert len(string) == len(string.encode("utf-8"))
157
158        idx = self.size
159        self.size += len(string) + 1
160
161        self.entries[string] = idx
162        self.entry_list.append(string)
163        return idx
164
165    # Returns the C++ code representing string data of this string table, as a
166    # single string literal. This must only be called after the last call to
167    # `get_idx()` or `entry_to_cxx()` for this instance.
168    def to_cxx(self):
169        self._serialized = True
170
171        lines = []
172
173        idx = 0
174        for entry in self.entry_list:
175            str_ = entry.replace("\\", "\\\\").replace('"', r"\"").replace("\n", r"\n")
176
177            lines.append('    /* 0x%x */ "%s\\0"\n' % (idx, str_))
178
179            idx += len(entry) + 1
180
181        return "".join(lines)
182
183    # Returns a `StringEntry` struct initializer for the string table entry
184    # corresponding to the given string. If no matching entry exists, it is
185    # first created.
186    def entry_to_cxx(self, string):
187        idx = self.get_idx(string)
188        return "{ 0x%x } /* %s */" % (idx, pretty_string(string))
189
190
191strings = StringTable()
192
193interfaces = []
194
195
196# Represents a C++ namespace, containing a set of classes and potentially
197# sub-namespaces. This is used to generate pre-declarations for incomplete
198# types referenced in XPCOM manifests.
199class Namespace(object):
200    def __init__(self, name=None):
201        self.name = name
202        self.classes = set()
203        self.namespaces = {}
204
205    # Returns a Namespace object for the sub-namespace with the given name.
206    def sub(self, name):
207        assert name not in self.classes
208
209        if name not in self.namespaces:
210            self.namespaces[name] = Namespace(name)
211        return self.namespaces[name]
212
213    # Generates C++ code to pre-declare all classes in this namespace and all
214    # of its sub-namespaces.
215    def to_cxx(self):
216        res = ""
217        if self.name:
218            res += "namespace %s {\n" % self.name
219
220        for clas in sorted(self.classes):
221            res += "class %s;\n" % clas
222
223        for ns in sorted(self.namespaces.keys()):
224            res += self.namespaces[ns].to_cxx()
225
226        if self.name:
227            res += "}  // namespace %s\n" % self.name
228
229        return res
230
231
232# Represents a component defined in an XPCOM manifest's `Classes` array.
233class ModuleEntry(object):
234    next_anon_id = 0
235
236    def __init__(self, data, init_idx):
237        self.cid = UUIDRepr(UUID(data["cid"]))
238        self.contract_ids = data.get("contract_ids", [])
239        self.type = data.get("type", "nsISupports")
240        self.categories = data.get("categories", {})
241        self.processes = data.get("processes", 0)
242        self.headers = data.get("headers", [])
243
244        self.js_name = data.get("js_name", None)
245        self.interfaces = data.get("interfaces", [])
246
247        if len(self.interfaces) > 255:
248            raise Exception(
249                "JS service %s may not have more than 255 " "interfaces" % self.js_name
250            )
251
252        self.interfaces_offset = len(interfaces)
253        for iface in self.interfaces:
254            interfaces.append(iface)
255
256        # If the manifest declares Init or Unload functions, this contains its
257        # index, as understood by the `CallInitFunc()` function.
258        #
259        # If it contains any value other than `None`, a corresponding
260        # `CallInitFunc(init_idx)` call will be genrated before calling this
261        # module's constructor.
262        self.init_idx = init_idx
263
264        self.constructor = data.get("constructor", None)
265        self.legacy_constructor = data.get("legacy_constructor", None)
266        self.init_method = data.get("init_method", [])
267
268        self.jsm = data.get("jsm", None)
269
270        self.external = data.get(
271            "external", not (self.headers or self.legacy_constructor)
272        )
273        self.singleton = data.get("singleton", False)
274        self.overridable = data.get("overridable", False)
275
276        if "name" in data:
277            self.anonymous = False
278            self.name = data["name"]
279        else:
280            self.anonymous = True
281            self.name = "Anonymous%03d" % ModuleEntry.next_anon_id
282            ModuleEntry.next_anon_id += 1
283
284        def error(str_):
285            raise Exception(
286                "Error defining component %s (%s): %s"
287                % (str(self.cid), ", ".join(map(repr, self.contract_ids)), str_)
288            )
289
290        if self.jsm:
291            if not self.constructor:
292                error("JavaScript components must specify a constructor")
293
294            for prop in ("init_method", "legacy_constructor", "headers"):
295                if getattr(self, prop):
296                    error(
297                        "JavaScript components may not specify a '%s' "
298                        "property" % prop
299                    )
300        elif self.external:
301            if self.constructor or self.legacy_constructor:
302                error(
303                    "Externally-constructed components may not specify "
304                    "'constructor' or 'legacy_constructor' properties"
305                )
306            if self.init_method:
307                error(
308                    "Externally-constructed components may not specify "
309                    "'init_method' properties"
310                )
311            if self.type == "nsISupports":
312                error(
313                    "Externally-constructed components must specify a type "
314                    "other than nsISupports"
315                )
316
317        if self.constructor and self.legacy_constructor:
318            error(
319                "The 'constructor' and 'legacy_constructor' properties "
320                "are mutually exclusive"
321            )
322
323        if self.overridable and not self.contract_ids:
324            error("Overridable components must specify at least one contract " "ID")
325
326    @property
327    def contract_id(self):
328        return self.contract_ids[0]
329
330    # Generates the C++ code for a StaticModule struct initializer
331    # representing this component.
332    def to_cxx(self):
333        contract_id = (
334            strings.entry_to_cxx(self.contract_id)
335            if self.overridable
336            else "{ 0x%x }" % NO_CONTRACT_ID
337        )
338
339        return """
340        /* {name} */ {{
341          /* {{{cid_string}}} */
342          {cid},
343          {contract_id},
344          {processes},
345        }}""".format(
346            name=self.name,
347            cid=self.cid.to_cxx(),
348            cid_string=str(self.cid),
349            contract_id=contract_id,
350            processes=lower_processes(self.processes),
351        )
352
353    # Generates the C++ code for a JSServiceEntry representing this module.
354    def lower_js_service(self):
355        return """
356        {{
357          {js_name},
358          ModuleID::{name},
359          {{ {iface_offset} }},
360          {iface_count}
361        }}""".format(
362            js_name=strings.entry_to_cxx(self.js_name),
363            name=self.name,
364            iface_offset=self.interfaces_offset,
365            iface_count=len(self.interfaces),
366        )
367
368    # Generates the C++ code necessary to construct an instance of this
369    # component.
370    #
371    # This code lives in a function with the following arguments:
372    #
373    #  - aIID: The `const nsIID&` interface ID that the resulting instance
374    #          will be queried to.
375    #
376    #  - aResult: The `void**` pointer in which to store the result.
377    #
378    # And which returns an `nsresult` indicating success or failure.
379    def lower_constructor(self):
380        res = ""
381
382        if self.init_idx is not None:
383            res += "      MOZ_TRY(CallInitFunc(%d));\n" % self.init_idx
384
385        if self.legacy_constructor:
386            res += (
387                "      return /* legacy */ %s(nullptr, aIID, aResult);\n"
388                % self.legacy_constructor
389            )
390            return res
391
392        if self.jsm:
393            res += (
394                "      nsCOMPtr<nsISupports> inst;\n"
395                "      MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n"
396                "                                    %s,\n"
397                "                                    getter_AddRefs(inst)));"
398                "\n" % (json.dumps(self.jsm), json.dumps(self.constructor))
399            )
400        elif self.external:
401            res += (
402                "      nsCOMPtr<nsISupports> inst = "
403                "mozCreateComponent<%s>();\n" % self.type
404            )
405            # The custom constructor may return null, so check before calling
406            # any methods.
407            res += "      NS_ENSURE_TRUE(inst, NS_ERROR_FAILURE);\n"
408        else:
409            res += "      RefPtr<%s> inst = " % self.type
410
411            if not self.constructor:
412                res += "new %s();\n" % self.type
413            else:
414                res += "%s();\n" % self.constructor
415                # The `new` operator is infallible, so we don't need to worry
416                # about it returning null, but custom constructors may, so
417                # check before calling any methods.
418                res += "      NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);\n"
419
420                # Check that the constructor function returns an appropriate
421                # `already_AddRefed` value for our declared type.
422                res += """
423      using T =
424          RemoveAlreadyAddRefed<decltype(%(constructor)s())>::Type;
425      static_assert(
426          std::is_same_v<already_AddRefed<T>, decltype(%(constructor)s())>,
427          "Singleton constructor must return already_AddRefed");
428      static_assert(
429          std::is_base_of<%(type)s, T>::value,
430          "Singleton constructor must return correct already_AddRefed");
431
432""" % {
433                    "type": self.type,
434                    "constructor": self.constructor,
435                }
436
437            if self.init_method:
438                res += "      MOZ_TRY(inst->%s());\n" % self.init_method
439
440        res += "      return inst->QueryInterface(aIID, aResult);\n"
441
442        return res
443
444    # Generates the C++ code for the `mozilla::components::<name>` entry
445    # corresponding to this component. This may not be called for modules
446    # without an explicit `name` (in which cases, `self.anonymous` will be
447    # true).
448    def lower_getters(self):
449        assert not self.anonymous
450
451        substs = {
452            "name": self.name,
453            "id": "::mozilla::xpcom::ModuleID::%s" % self.name,
454        }
455
456        res = (
457            """
458namespace %(name)s {
459static inline const nsID& CID() {
460  return ::mozilla::xpcom::Components::GetCID(%(id)s);
461}
462
463static inline ::mozilla::xpcom::GetServiceHelper Service(nsresult* aRv = nullptr) {
464  return {%(id)s, aRv};
465}
466"""
467            % substs
468        )
469
470        if not self.singleton:
471            res += (
472                """
473static inline ::mozilla::xpcom::CreateInstanceHelper Create(nsresult* aRv = nullptr) {
474  return {%(id)s, aRv};
475}
476"""
477                % substs
478            )
479
480        res += (
481            """\
482}  // namespace %(name)s
483"""
484            % substs
485        )
486
487        return res
488
489
490# Returns a quoted string literal representing the given raw string, with
491# certain special characters replaced so that it can be used in a C++-style
492# (/* ... */) comment.
493def pretty_string(string):
494    return json.dumps(string).replace("*/", r"*\/").replace("/*", r"/\*")
495
496
497# Represents a static contract ID entry, corresponding to a C++ ContractEntry
498# struct, mapping a contract ID to a static module entry.
499class ContractEntry(object):
500    def __init__(self, contract, module):
501        self.contract = contract
502        self.module = module
503
504    def to_cxx(self):
505        return """
506        {{
507          {contract},
508          {module_id},
509        }}""".format(
510            contract=strings.entry_to_cxx(self.contract),
511            module_id=lower_module_id(self.module),
512        )
513
514
515# Generates the C++ code for the StaticCategoryEntry and StaticCategory
516# structs for all category entries declared in XPCOM manifests.
517def gen_categories(substs, categories):
518    cats = []
519    ents = []
520
521    count = 0
522    for category, entries in sorted(categories.items()):
523
524        def k(entry):
525            return tuple(entry[0]["name"]) + entry[1:]
526
527        entries.sort(key=k)
528
529        cats.append(
530            "  { %s,\n"
531            "    %d, %d },\n" % (strings.entry_to_cxx(category), count, len(entries))
532        )
533        count += len(entries)
534
535        ents.append("  /* %s */\n" % pretty_string(category))
536        for entry, value, processes in entries:
537            name = entry["name"]
538            backgroundtasks = entry.get(
539                "backgroundtasks", BackgroundTasksSelector.NO_TASKS
540            )
541
542            ents.append(
543                "  { %s,\n"
544                "    %s,\n"
545                "    %s,\n"
546                "    %s },\n"
547                % (
548                    strings.entry_to_cxx(name),
549                    strings.entry_to_cxx(value),
550                    lower_backgroundtasks(backgroundtasks),
551                    lower_processes(processes),
552                )
553            )
554        ents.append("\n")
555    ents.pop()
556
557    substs["category_count"] = len(cats)
558    substs["categories"] = "".join(cats)
559    substs["category_entries"] = "".join(ents)
560
561
562# Generates the C++ code for all Init and Unload functions declared in XPCOM
563# manifests. These form the bodies of the `CallInitFunc()` and `CallUnload`
564# functions in StaticComponents.cpp.
565def gen_module_funcs(substs, funcs):
566    inits = []
567    unloads = []
568
569    template = """\
570    case %d:
571      %s
572      break;
573"""
574
575    for i, (init, unload) in enumerate(funcs):
576        init_code = "%s();" % init if init else "/* empty */"
577        inits.append(template % (i, init_code))
578
579        if unload:
580            unloads.append(
581                """\
582  if (CalledInit(%d)) {
583    %s();
584  }
585"""
586                % (i, unload)
587            )
588
589    substs["init_funcs"] = "".join(inits)
590    substs["unload_funcs"] = "".join(unloads)
591    substs["init_count"] = len(funcs)
592
593
594def gen_interfaces(ifaces):
595    res = []
596    for iface in ifaces:
597        res.append("  nsXPTInterface::%s,\n" % iface)
598    return "".join(res)
599
600
601# Generates class pre-declarations for any types referenced in `Classes` array
602# entries which do not have corresponding `headers` entries to fully declare
603# their types.
604def gen_decls(types):
605    root_ns = Namespace()
606
607    for type_ in sorted(types):
608        parts = type_.split("::")
609
610        ns = root_ns
611        for part in parts[:-1]:
612            ns = ns.sub(part)
613        ns.classes.add(parts[-1])
614
615    return root_ns.to_cxx()
616
617
618# Generates the `switch` body for the `CreateInstanceImpl()` function, with a
619# `case` for each value in ModuleID to construct an instance of the
620# corresponding component.
621def gen_constructors(entries):
622    constructors = []
623    for entry in entries:
624        constructors.append(
625            """\
626    case {id}: {{
627{constructor}\
628    }}
629""".format(
630                id=lower_module_id(entry), constructor=entry.lower_constructor()
631            )
632        )
633
634    return "".join(constructors)
635
636
637# Generates the getter code for each named component entry in the
638# `mozilla::components::` namespace.
639def gen_getters(entries):
640    entries = list(entries)
641    entries.sort(key=lambda e: e.name)
642
643    return "".join(entry.lower_getters() for entry in entries if not entry.anonymous)
644
645
646def gen_includes(substs, all_headers):
647    headers = set()
648    absolute_headers = set()
649
650    for header in all_headers:
651        if header.startswith("/"):
652            absolute_headers.add(header)
653        else:
654            headers.add(header)
655
656    includes = ['#include "%s"' % header for header in sorted(headers)]
657    substs["includes"] = "\n".join(includes) + "\n"
658
659    relative_includes = [
660        '#include "../..%s"' % header for header in sorted(absolute_headers)
661    ]
662    substs["relative_includes"] = "\n".join(relative_includes) + "\n"
663
664
665def to_category_list(val):
666    # Entries can be bare strings (like `"m-browser"`), lists of bare strings,
667    # or dictionaries (like `{"name": "m-browser", "backgroundtasks":
668    # BackgroundTasksSelector.ALL_TASKS}`), somewhat recursively.
669
670    def ensure_dict(v):
671        # Turn `v` into `{"name": v}` if it's not already a dict.
672        if isinstance(v, dict):
673            return v
674        return {"name": v}
675
676    if isinstance(val, (list, tuple)):
677        return tuple(ensure_dict(v) for v in val)
678
679    if isinstance(val, dict):
680        # Explode `{"name": ["x", "y"], "backgroundtasks": ...}` into
681        # `[{"name": "x", "backgroundtasks": ...}, {"name": "y", "backgroundtasks": ...}]`.
682        names = val.pop("name")
683
684        vals = []
685        for entry in to_category_list(names):
686            d = dict(val)
687            d["name"] = entry["name"]
688            vals.append(d)
689
690        return tuple(vals)
691
692    return (ensure_dict(val),)
693
694
695def gen_substs(manifests):
696    module_funcs = []
697
698    headers = set()
699
700    modules = []
701    categories = defaultdict(list)
702
703    for manifest in manifests:
704        headers |= set(manifest.get("Headers", []))
705
706        init_idx = None
707        init = manifest.get("InitFunc")
708        unload = manifest.get("UnloadFunc")
709        if init or unload:
710            init_idx = len(module_funcs)
711            module_funcs.append((init, unload))
712
713        for clas in manifest["Classes"]:
714            modules.append(ModuleEntry(clas, init_idx))
715
716        for category, entries in manifest.get("Categories", {}).items():
717            for key, entry in entries.items():
718                if isinstance(entry, tuple):
719                    value, process = entry
720                else:
721                    value, process = entry, 0
722                categories[category].append((key, value, process))
723
724    cids = set()
725    contracts = []
726    contract_map = {}
727    js_services = {}
728
729    jsms = set()
730
731    types = set()
732
733    for mod in modules:
734        headers |= set(mod.headers)
735
736        for contract_id in mod.contract_ids:
737            if contract_id in contract_map:
738                raise Exception("Duplicate contract ID: %s" % contract_id)
739
740            entry = ContractEntry(contract_id, mod)
741            contracts.append(entry)
742            contract_map[contract_id] = entry
743
744        for category, entries in mod.categories.items():
745            for entry in to_category_list(entries):
746                categories[category].append((entry, mod.contract_id, mod.processes))
747
748        if mod.type and not mod.headers:
749            types.add(mod.type)
750
751        if mod.jsm:
752            jsms.add(mod.jsm)
753
754        if mod.js_name:
755            if mod.js_name in js_services:
756                raise Exception("Duplicate JS service name: %s" % mod.js_name)
757            js_services[mod.js_name] = mod
758
759        if str(mod.cid) in cids:
760            raise Exception("Duplicate cid: %s" % str(mod.cid))
761        cids.add(str(mod.cid))
762
763    cid_phf = PerfectHash(modules, PHF_SIZE, key=lambda module: module.cid.bytes)
764
765    contract_phf = PerfectHash(contracts, PHF_SIZE, key=lambda entry: entry.contract)
766
767    js_services_phf = PerfectHash(
768        list(js_services.values()), PHF_SIZE, key=lambda entry: entry.js_name
769    )
770
771    js_services_json = {}
772    for entry in js_services.values():
773        for iface in entry.interfaces:
774            js_services_json[iface] = entry.js_name
775
776    substs = {}
777
778    gen_categories(substs, categories)
779
780    substs["module_ids"] = "".join("  %s,\n" % entry.name for entry in cid_phf.entries)
781
782    substs["module_count"] = len(modules)
783    substs["contract_count"] = len(contracts)
784
785    gen_module_funcs(substs, module_funcs)
786
787    gen_includes(substs, headers)
788
789    substs["component_jsms"] = (
790        "\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n"
791    )
792
793    substs["interfaces"] = gen_interfaces(interfaces)
794
795    substs["decls"] = gen_decls(types)
796
797    substs["constructors"] = gen_constructors(cid_phf.entries)
798
799    substs["component_getters"] = gen_getters(cid_phf.entries)
800
801    substs["module_cid_table"] = cid_phf.cxx_codegen(
802        name="ModuleByCID",
803        entry_type="StaticModule",
804        entries_name="gStaticModules",
805        lower_entry=lambda entry: entry.to_cxx(),
806        return_type="const StaticModule*",
807        return_entry=(
808            "return entry.CID().Equals(aKey) && entry.Active()" " ? &entry : nullptr;"
809        ),
810        key_type="const nsID&",
811        key_bytes="reinterpret_cast<const char*>(&aKey)",
812        key_length="sizeof(nsID)",
813    )
814
815    substs["module_contract_id_table"] = contract_phf.cxx_codegen(
816        name="LookupContractID",
817        entry_type="ContractEntry",
818        entries_name="gContractEntries",
819        lower_entry=lambda entry: entry.to_cxx(),
820        return_type="const ContractEntry*",
821        return_entry="return entry.Matches(aKey) ? &entry : nullptr;",
822        key_type="const nsACString&",
823        key_bytes="aKey.BeginReading()",
824        key_length="aKey.Length()",
825    )
826
827    substs["js_services_table"] = js_services_phf.cxx_codegen(
828        name="LookupJSService",
829        entry_type="JSServiceEntry",
830        entries_name="gJSServices",
831        lower_entry=lambda entry: entry.lower_js_service(),
832        return_type="const JSServiceEntry*",
833        return_entry="return entry.Name() == aKey ? &entry : nullptr;",
834        key_type="const nsACString&",
835        key_bytes="aKey.BeginReading()",
836        key_length="aKey.Length()",
837    )
838
839    substs["js_services_json"] = json.dumps(js_services_json, sort_keys=True, indent=4)
840
841    # Do this only after everything else has been emitted so we're sure the
842    # string table is complete.
843    substs["strings"] = strings.to_cxx()
844    return substs
845
846
847# Returns true if the given build config substitution is defined and truthy.
848def defined(subst):
849    return bool(buildconfig.substs.get(subst))
850
851
852def read_manifest(filename):
853    glbl = {
854        "buildconfig": buildconfig,
855        "defined": defined,
856        "ProcessSelector": ProcessSelector,
857        "BackgroundTasksSelector": BackgroundTasksSelector,
858    }
859    code = compile(open(filename).read(), filename, "exec")
860    exec(code, glbl)
861    return glbl
862
863
864def main(fd, conf_file, template_file):
865    def open_output(filename):
866        return FileAvoidWrite(os.path.join(os.path.dirname(fd.name), filename))
867
868    conf = json.load(open(conf_file, "r"))
869
870    deps = set()
871
872    manifests = []
873    for filename in conf["manifests"]:
874        deps.add(filename)
875        manifest = read_manifest(filename)
876        manifests.append(manifest)
877        manifest.setdefault("Priority", 50)
878        manifest["__filename__"] = filename
879
880    manifests.sort(key=lambda man: (man["Priority"], man["__filename__"]))
881
882    substs = gen_substs(manifests)
883
884    def replacer(match):
885        return substs[match.group(1)]
886
887    with open_output("StaticComponents.cpp") as fh:
888        with open(template_file, "r") as tfh:
889            template = tfh.read()
890
891        fh.write(re.sub(r"//# @([a-zA-Z_]+)@\n", replacer, template))
892
893    with open_output("StaticComponentData.h") as fh:
894        fh.write(
895            """\
896/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
897/* vim: set ts=8 sts=2 et sw=2 tw=80: */
898/* This Source Code Form is subject to the terms of the Mozilla Public
899 * License, v. 2.0. If a copy of the MPL was not distributed with this
900 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
901
902#ifndef StaticComponentData_h
903#define StaticComponentData_h
904
905#include <stddef.h>
906
907namespace mozilla {
908namespace xpcom {
909
910static constexpr size_t kStaticModuleCount = %(module_count)d;
911
912static constexpr size_t kContractCount = %(contract_count)d;
913
914static constexpr size_t kStaticCategoryCount = %(category_count)d;
915
916static constexpr size_t kModuleInitCount = %(init_count)d;
917
918}  // namespace xpcom
919}  // namespace mozilla
920
921#endif
922"""
923            % substs
924        )
925
926    fd.write(
927        """\
928/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
929/* vim: set ts=8 sts=2 et sw=2 tw=80: */
930/* This Source Code Form is subject to the terms of the Mozilla Public
931 * License, v. 2.0. If a copy of the MPL was not distributed with this
932 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
933
934#ifndef mozilla_Components_h
935#define mozilla_Components_h
936
937#include "nsCOMPtr.h"
938
939struct nsID;
940
941#define NS_IMPL_COMPONENT_FACTORY(iface) \\
942  template <>                            \\
943  already_AddRefed<nsISupports> mozCreateComponent<iface>()
944
945template <typename T>
946already_AddRefed<nsISupports> mozCreateComponent();
947
948namespace mozilla {
949namespace xpcom {
950
951enum class ModuleID : uint16_t {
952%(module_ids)s
953};
954
955class MOZ_STACK_CLASS StaticModuleHelper : public nsCOMPtr_helper {
956 public:
957  StaticModuleHelper(ModuleID aId, nsresult* aErrorPtr)
958      : mId(aId), mErrorPtr(aErrorPtr) {}
959
960 protected:
961  nsresult SetResult(nsresult aRv) const {
962    if (mErrorPtr) {
963      *mErrorPtr = aRv;
964    }
965    return aRv;
966  }
967
968  ModuleID mId;
969  nsresult* mErrorPtr;
970};
971
972class MOZ_STACK_CLASS GetServiceHelper final : public StaticModuleHelper {
973 public:
974  using StaticModuleHelper::StaticModuleHelper;
975
976  nsresult NS_FASTCALL operator()(const nsIID& aIID,
977                                  void** aResult) const override;
978};
979
980class MOZ_STACK_CLASS CreateInstanceHelper final : public StaticModuleHelper {
981 public:
982  using StaticModuleHelper::StaticModuleHelper;
983
984  nsresult NS_FASTCALL operator()(const nsIID& aIID,
985                                  void** aResult) const override;
986};
987
988class Components final {
989 public:
990  static const nsID& GetCID(ModuleID aID);
991};
992
993}  // namespace xpcom
994
995namespace components {
996%(component_getters)s
997}  // namespace components
998
999}  // namespace mozilla
1000
1001#endif
1002"""
1003        % substs
1004    )
1005
1006    with open_output("services.json") as fh:
1007        fh.write(substs["js_services_json"])
1008
1009    return deps
1010