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,
# 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.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"])