forked from mirrors/gecko-dev
Adding a new pref for a new SpiderMonkey feature or optimization requires a lot of boilerplate code. This patch stack lets us eliminate almost all of this boilerplate by parsing `StaticPrefList.yaml` at SpiderMonkey build time and generating code to automatically expose the `javascript.options.*` prefs that have the `set_spidermonkey_pref` attribute. Both JS shell and browser builds will default to the pref value in `StaticPrefList.yaml`. In the JS shell, it'll be possible to set a pref value with `--setpref` or a custom shell flag. Code for this will be added in later patches. Differential Revision: https://phabricator.services.mozilla.com/D199992
472 lines
16 KiB
Python
472 lines
16 KiB
Python
# 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",
|
|
"set_spidermonkey_pref",
|
|
}
|
|
|
|
# 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("TARGET_CPU") == "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"])
|