# 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_.h. includes = defaultdict(set) # StaticPrefList_.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_.h wraps StaticPrefList_.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"])