summaryrefslogtreecommitdiffstats
path: root/modules/libpref/init/generate_static_pref_list.py
blob: 2e9cf9d98fda75cc38f57deba021639841971130 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# 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
import sys
from collections import defaultdict

import buildconfig
import yaml
from mozbuild.preprocessor import Preprocessor
from mozbuild.util import FileAvoidWrite, ensureParentDir
from six import StringIO

VALID_KEYS = {
    "name",
    "type",
    "value",
    "mirror",
    "do_not_use_directly",
    "include",
    "rust",
}

# Each key is a C++ type; its value is the equivalent non-atomic C++ type.
VALID_BOOL_TYPES = {
    "bool": "bool",
    # These ones are defined in StaticPrefsBase.h.
    "RelaxedAtomicBool": "bool",
    "ReleaseAcquireAtomicBool": "bool",
    "SequentiallyConsistentAtomicBool": "bool",
}

VALID_TYPES = VALID_BOOL_TYPES.copy()
VALID_TYPES.update(
    {
        "int32_t": "int32_t",
        "uint32_t": "uint32_t",
        "float": "float",
        # These ones are defined in StaticPrefsBase.h.
        "RelaxedAtomicInt32": "int32_t",
        "RelaxedAtomicUint32": "uint32_t",
        "ReleaseAcquireAtomicInt32": "int32_t",
        "ReleaseAcquireAtomicUint32": "uint32_t",
        "SequentiallyConsistentAtomicInt32": "int32_t",
        "SequentiallyConsistentAtomicUint32": "uint32_t",
        "AtomicFloat": "float",
        "String": None,
        "DataMutexString": "nsACString",
    }
)

# Map non-atomic C++ types to equivalent Rust types.
RUST_TYPES = {
    "bool": "bool",
    "int32_t": "i32",
    "uint32_t": "u32",
    "float": "f32",
    "DataMutexString": "nsCString",
}

HEADER_LINE = (
    "// This file was generated by generate_static_pref_list.py from {input_filename}."
    " DO NOT EDIT."
)

MIRROR_TEMPLATES = {
    "never": """\
NEVER_PREF("{name}", {typ}, {value})
""",
    "once": """\
ONCE_PREF(
  "{name}",
   {base_id},
   {full_id},
  {typ}, {value}
)
""",
    "always": """\
ALWAYS_PREF(
  "{name}",
   {base_id},
   {full_id},
  {typ}, {value}
)
""",
    "always_datamutex": """\
ALWAYS_DATAMUTEX_PREF(
  "{name}",
   {base_id},
   {full_id},
  {typ}, {value}
)
""",
}

STATIC_PREFS_GROUP_H_TEMPLATE1 = """\
// Include it to gain access to StaticPrefs::{group}_*.

#ifndef mozilla_StaticPrefs_{group}_h
#define mozilla_StaticPrefs_{group}_h
"""

STATIC_PREFS_GROUP_H_TEMPLATE2 = """\
#include "mozilla/StaticPrefListBegin.h"
#include "mozilla/StaticPrefList_{group}.h"
#include "mozilla/StaticPrefListEnd.h"

#endif  // mozilla_StaticPrefs_{group}_h
"""

STATIC_PREFS_C_GETTERS_TEMPLATE = """\
extern "C" {typ} StaticPrefs_{full_id}() {{
  return mozilla::StaticPrefs::{full_id}();
}}
"""

STATIC_PREFS_C_GETTERS_NSSTRING_TEMPLATE = """\
extern "C" void StaticPrefs_{full_id}(nsACString *result) {{
  const auto preflock = mozilla::StaticPrefs::{full_id}();
  result->Append(*preflock);
}}
"""


def error(msg):
    raise ValueError(msg)


def mk_id(name):
    "Replace '.' and '-' with '_', e.g. 'foo.bar-baz' becomes 'foo_bar_baz'."
    return name.replace(".", "_").replace("-", "_")


def mk_group(pref):
    name = pref["name"]
    return mk_id(name.split(".", 1)[0])


def check_pref_list(pref_list):
    # Pref names seen so far. Used to detect any duplicates.
    seen_names = set()

    # The previous pref. Used to detect mis-ordered prefs.
    prev_pref = None

    for pref in pref_list:
        # Check all given keys are known ones.
        for key in pref:
            if key not in VALID_KEYS:
                error("invalid key `{}`".format(key))

        # 'name' must be present, valid, and in the right section.
        if "name" not in pref:
            error("missing `name` key")
        name = pref["name"]
        if type(name) != str:
            error("non-string `name` value `{}`".format(name))
        if "." not in name:
            error("`name` value `{}` lacks a '.'".format(name))
        if name in seen_names:
            error("`{}` pref is defined more than once".format(name))
        seen_names.add(name)

        # Prefs must be ordered appropriately.
        if prev_pref:
            if mk_group(prev_pref) > mk_group(pref):
                error(
                    "`{}` pref must come before `{}` pref".format(
                        name, prev_pref["name"]
                    )
                )

        # 'type' must be present and valid.
        if "type" not in pref:
            error("missing `type` key for pref `{}`".format(name))
        typ = pref["type"]
        if typ not in VALID_TYPES:
            error("invalid `type` value `{}` for pref `{}`".format(typ, name))

        # 'value' must be present and valid.
        if "value" not in pref:
            error("missing `value` key for pref `{}`".format(name))
        value = pref["value"]
        if typ == "String" or typ == "DataMutexString":
            if type(value) != str:
                error(
                    "non-string `value` value `{}` for `{}` pref `{}`; "
                    "add double quotes".format(value, typ, name)
                )
        elif typ in VALID_BOOL_TYPES:
            if value not in (True, False):
                error("invalid boolean value `{}` for pref `{}`".format(value, name))

        # 'mirror' must be present and valid.
        if "mirror" not in pref:
            error("missing `mirror` key for pref `{}`".format(name))
        mirror = pref["mirror"]
        if typ.startswith("DataMutex"):
            mirror += "_datamutex"
        if mirror not in MIRROR_TEMPLATES:
            error("invalid `mirror` value `{}` for pref `{}`".format(mirror, name))

        # Check 'do_not_use_directly' if present.
        if "do_not_use_directly" in pref:
            do_not_use_directly = pref["do_not_use_directly"]
            if type(do_not_use_directly) != bool:
                error(
                    "non-boolean `do_not_use_directly` value `{}` for pref "
                    "`{}`".format(do_not_use_directly, name)
                )
            if do_not_use_directly and mirror == "never":
                error(
                    "`do_not_use_directly` uselessly set with `mirror` value "
                    "`never` for pref `{}`".format(pref["name"])
                )

        # Check 'include' if present.
        if "include" in pref:
            include = pref["include"]
            if type(include) != str:
                error(
                    "non-string `include` value `{}` for pref `{}`".format(
                        include, name
                    )
                )
            if include.startswith("<") and not include.endswith(">"):
                error(
                    "`include` value `{}` starts with `<` but does not "
                    "end with `>` for pref `{}`".format(include, name)
                )

        # Check 'rust' if present.
        if "rust" in pref:
            rust = pref["rust"]
            if type(rust) != bool:
                error("non-boolean `rust` value `{}` for pref `{}`".format(rust, name))
            if rust and mirror == "never":
                error(
                    "`rust` uselessly set with `mirror` value `never` for "
                    "pref `{}`".format(pref["name"])
                )

        prev_pref = pref


def generate_code(pref_list, input_filename):
    check_pref_list(pref_list)

    first_line = HEADER_LINE.format(input_filename=input_filename)

    # The required includes for StaticPrefs_<group>.h.
    includes = defaultdict(set)

    # StaticPrefList_<group>.h contains all the pref definitions for this
    # group.
    static_pref_list_group_h = defaultdict(lambda: [first_line, ""])

    # StaticPrefsCGetters.cpp contains C getters for all the mirrored prefs,
    # for use by Rust code.
    static_prefs_c_getters_cpp = [first_line, ""]

    # static_prefs.rs contains C getter declarations and a macro.
    static_prefs_rs_decls = []
    static_prefs_rs_macro = []

    # Generate the per-pref code (spread across multiple files).
    for pref in pref_list:
        name = pref["name"]
        typ = pref["type"]
        value = pref["value"]
        mirror = pref["mirror"]
        do_not_use_directly = pref.get("do_not_use_directly")
        include = pref.get("include")
        rust = pref.get("rust")

        base_id = mk_id(pref["name"])
        full_id = base_id
        if mirror == "once":
            full_id += "_AtStartup"
        if do_not_use_directly:
            full_id += "_DoNotUseDirectly"
        if typ.startswith("DataMutex"):
            mirror += "_datamutex"

        group = mk_group(pref)

        if include:
            if not include.startswith("<"):
                # It's not a system header. Add double quotes.
                include = '"{}"'.format(include)
            includes[group].add(include)

        if typ == "String":
            # Quote string literals, and escape double-quote chars.
            value = '"{}"'.format(value.replace('"', '\\"'))
        elif typ == "DataMutexString":
            # Quote string literals, and escape double-quote chars.
            value = '"{}"_ns'.format(value.replace('"', '\\"'))
        elif typ in VALID_BOOL_TYPES:
            # Convert Python bools to C++ bools.
            if value is True:
                value = "true"
            elif value is False:
                value = "false"

        # Append the C++ definition to the relevant output file's code.
        static_pref_list_group_h[group].append(
            MIRROR_TEMPLATES[mirror].format(
                name=name,
                base_id=base_id,
                full_id=full_id,
                typ=typ,
                value=value,
            )
        )

        if rust:
            passed_type = VALID_TYPES[typ]
            if passed_type == "nsACString":
                # Generate the C getter.
                static_prefs_c_getters_cpp.append(
                    STATIC_PREFS_C_GETTERS_NSSTRING_TEMPLATE.format(full_id=full_id)
                )

                # Generate the C getter declaration, in Rust.
                decl = "    pub fn StaticPrefs_{full_id}(result: *mut nsstring::nsACString);"
                static_prefs_rs_decls.append(decl.format(full_id=full_id))

                # Generate the Rust macro entry.
                macro = '    ("{name}") => (unsafe {{ let mut result = $crate::nsCString::new(); $crate::StaticPrefs_{full_id}(&mut *result); result }});'
                static_prefs_rs_macro.append(macro.format(name=name, full_id=full_id))

            else:
                # Generate the C getter.
                static_prefs_c_getters_cpp.append(
                    STATIC_PREFS_C_GETTERS_TEMPLATE.format(
                        typ=passed_type, full_id=full_id
                    )
                )

                # Generate the C getter declaration, in Rust.
                decl = "    pub fn StaticPrefs_{full_id}() -> {typ};"
                static_prefs_rs_decls.append(
                    decl.format(full_id=full_id, typ=RUST_TYPES[passed_type])
                )

                # Generate the Rust macro entry.
                macro = (
                    '    ("{name}") => (unsafe {{ $crate::StaticPrefs_{full_id}() }});'
                )
                static_prefs_rs_macro.append(macro.format(name=name, full_id=full_id))

        # Delete this so that `group` can be reused below without Flake8
        # complaining.
        del group

    # StaticPrefListAll.h contains one `#include "mozilla/StaticPrefList_X.h`
    # line per pref group.
    static_pref_list_all_h = [first_line, ""]
    static_pref_list_all_h.extend(
        '#include "mozilla/StaticPrefList_{}.h"'.format(group)
        for group in sorted(static_pref_list_group_h)
    )
    static_pref_list_all_h.append("")

    # StaticPrefsAll.h contains one `#include "mozilla/StaticPrefs_X.h` line per
    # pref group.
    static_prefs_all_h = [first_line, ""]
    static_prefs_all_h.extend(
        '#include "mozilla/StaticPrefs_{}.h"'.format(group)
        for group in sorted(static_pref_list_group_h)
    )
    static_prefs_all_h.append("")

    # StaticPrefs_<group>.h wraps StaticPrefList_<group>.h. It is the header
    # used directly by application code.
    static_prefs_group_h = defaultdict(list)
    for group in sorted(static_pref_list_group_h):
        static_prefs_group_h[group] = [first_line]
        static_prefs_group_h[group].append(
            STATIC_PREFS_GROUP_H_TEMPLATE1.format(group=group)
        )
        if group in includes:
            # Add any necessary includes, from 'h_include' values.
            for include in sorted(includes[group]):
                static_prefs_group_h[group].append("#include {}".format(include))
            static_prefs_group_h[group].append("")
        static_prefs_group_h[group].append(
            STATIC_PREFS_GROUP_H_TEMPLATE2.format(group=group)
        )

    # static_prefs.rs contains the Rust macro getters.
    static_prefs_rs = [first_line, "", "pub use nsstring::nsCString;", 'extern "C" {']
    static_prefs_rs.extend(static_prefs_rs_decls)
    static_prefs_rs.extend(["}", "", "#[macro_export]", "macro_rules! pref {"])
    static_prefs_rs.extend(static_prefs_rs_macro)
    static_prefs_rs.extend(["}", ""])

    def fold(lines):
        return "\n".join(lines)

    return {
        "static_pref_list_all_h": fold(static_pref_list_all_h),
        "static_prefs_all_h": fold(static_prefs_all_h),
        "static_pref_list_group_h": {
            k: fold(v) for k, v in static_pref_list_group_h.items()
        },
        "static_prefs_group_h": {k: fold(v) for k, v in static_prefs_group_h.items()},
        "static_prefs_c_getters_cpp": fold(static_prefs_c_getters_cpp),
        "static_prefs_rs": fold(static_prefs_rs),
    }


def emit_code(fd, pref_list_filename):
    pp = Preprocessor()
    pp.context.update(buildconfig.defines["ALLDEFINES"])

    # A necessary hack until MOZ_DEBUG_FLAGS are part of buildconfig.defines.
    if buildconfig.substs.get("MOZ_DEBUG"):
        pp.context["DEBUG"] = "1"

    if buildconfig.substs.get("CPU_ARCH") == "aarch64":
        pp.context["MOZ_AARCH64"] = True

    if buildconfig.substs.get("MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS"):
        pp.context["MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS"] = True

    pp.out = StringIO()
    pp.do_filter("substitution")
    pp.do_include(pref_list_filename)

    try:
        pref_list = yaml.safe_load(pp.out.getvalue())
        input_file = os.path.relpath(
            pref_list_filename,
            os.environ.get("GECKO_PATH", os.environ.get("TOPSRCDIR")),
        )
        code = generate_code(pref_list, input_file)
    except (IOError, ValueError) as e:
        print("{}: error:\n  {}\n".format(pref_list_filename, e))
        sys.exit(1)

    # When generating multiple files from a script, the build system treats the
    # first named output file (StaticPrefListAll.h in this case) specially -- it
    # is created elsewhere, and written to via `fd`.
    fd.write(code["static_pref_list_all_h"])

    # We must create the remaining output files ourselves. This requires
    # creating the output directory directly if it doesn't already exist.
    ensureParentDir(fd.name)
    init_dirname = os.path.dirname(fd.name)

    with FileAvoidWrite("StaticPrefsAll.h") as fd:
        fd.write(code["static_prefs_all_h"])

    for group, text in sorted(code["static_pref_list_group_h"].items()):
        filename = "StaticPrefList_{}.h".format(group)
        with FileAvoidWrite(os.path.join(init_dirname, filename)) as fd:
            fd.write(text)

    for group, text in sorted(code["static_prefs_group_h"].items()):
        filename = "StaticPrefs_{}.h".format(group)
        with FileAvoidWrite(filename) as fd:
            fd.write(text)

    with FileAvoidWrite(os.path.join(init_dirname, "StaticPrefsCGetters.cpp")) as fd:
        fd.write(code["static_prefs_c_getters_cpp"])

    with FileAvoidWrite("static_prefs.rs") as fd:
        fd.write(code["static_prefs_rs"])