Source code
Revision control
Copy as Markdown
Other Tools
# 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,
import os
import sys
from collections import defaultdict
import buildconfig
import yaml
from mozbuild.dirutils import ensureParentDir
from mozbuild.preprocessor import Preprocessor
from mozbuild.util import FileAvoidWrite
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) is not 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) is not 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) is not 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) is not 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) is not 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"])