generate_static_pref_list.py (16216B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 # You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import os 6 import sys 7 from collections import defaultdict 8 from io import StringIO 9 10 import buildconfig 11 import yaml 12 from mozbuild.dirutils import ensureParentDir 13 from mozbuild.preprocessor import Preprocessor 14 from mozbuild.util import FileAvoidWrite 15 16 VALID_KEYS = { 17 "name", 18 "type", 19 "value", 20 "mirror", 21 "do_not_use_directly", 22 "include", 23 "rust", 24 "set_spidermonkey_pref", 25 } 26 27 # Each key is a C++ type; its value is the equivalent non-atomic C++ type. 28 VALID_BOOL_TYPES = { 29 "bool": "bool", 30 # These ones are defined in StaticPrefsBase.h. 31 "RelaxedAtomicBool": "bool", 32 "ReleaseAcquireAtomicBool": "bool", 33 "SequentiallyConsistentAtomicBool": "bool", 34 } 35 36 VALID_TYPES = VALID_BOOL_TYPES.copy() 37 VALID_TYPES.update({ 38 "int32_t": "int32_t", 39 "uint32_t": "uint32_t", 40 "float": "float", 41 # These ones are defined in StaticPrefsBase.h. 42 "RelaxedAtomicInt32": "int32_t", 43 "RelaxedAtomicUint32": "uint32_t", 44 "ReleaseAcquireAtomicInt32": "int32_t", 45 "ReleaseAcquireAtomicUint32": "uint32_t", 46 "SequentiallyConsistentAtomicInt32": "int32_t", 47 "SequentiallyConsistentAtomicUint32": "uint32_t", 48 "AtomicFloat": "float", 49 "String": None, 50 "DataMutexString": "nsACString", 51 }) 52 53 # Map non-atomic C++ types to equivalent Rust types. 54 RUST_TYPES = { 55 "bool": "bool", 56 "int32_t": "i32", 57 "uint32_t": "u32", 58 "float": "f32", 59 "DataMutexString": "nsCString", 60 } 61 62 HEADER_LINE = ( 63 "// This file was generated by generate_static_pref_list.py from {input_filenames}." 64 " DO NOT EDIT." 65 ) 66 67 MIRROR_TEMPLATES = { 68 "never": """\ 69 NEVER_PREF("{name}", {typ}, {value}) 70 """, 71 "once": """\ 72 ONCE_PREF( 73 "{name}", 74 {base_id}, 75 {full_id}, 76 {typ}, {value} 77 ) 78 """, 79 "always": """\ 80 ALWAYS_PREF( 81 "{name}", 82 {base_id}, 83 {full_id}, 84 {typ}, {value} 85 ) 86 """, 87 "always_datamutex": """\ 88 ALWAYS_DATAMUTEX_PREF( 89 "{name}", 90 {base_id}, 91 {full_id}, 92 {typ}, {value} 93 ) 94 """, 95 } 96 97 STATIC_PREFS_GROUP_H_TEMPLATE1 = """\ 98 // Include it to gain access to StaticPrefs::{group}_*. 99 100 #ifndef mozilla_StaticPrefs_{group}_h 101 #define mozilla_StaticPrefs_{group}_h 102 """ 103 104 STATIC_PREFS_GROUP_H_TEMPLATE2 = """\ 105 #include "mozilla/StaticPrefListBegin.h" 106 #include "mozilla/StaticPrefList_{group}.h" 107 #include "mozilla/StaticPrefListEnd.h" 108 109 #endif // mozilla_StaticPrefs_{group}_h 110 """ 111 112 STATIC_PREFS_C_GETTERS_TEMPLATE = """\ 113 extern "C" {typ} StaticPrefs_{full_id}() {{ 114 return mozilla::StaticPrefs::{full_id}(); 115 }} 116 """ 117 118 STATIC_PREFS_C_GETTERS_NSSTRING_TEMPLATE = """\ 119 extern "C" void StaticPrefs_{full_id}(nsACString *result) {{ 120 const auto preflock = mozilla::StaticPrefs::{full_id}(); 121 result->Append(*preflock); 122 }} 123 """ 124 125 126 def error(msg): 127 raise ValueError(msg) 128 129 130 def mk_id(name): 131 "Replace '.' and '-' with '_', e.g. 'foo.bar-baz' becomes 'foo_bar_baz'." 132 return name.replace(".", "_").replace("-", "_") 133 134 135 def mk_group(pref): 136 name = pref["name"] 137 return mk_id(name.split(".", 1)[0]) 138 139 140 def check_pref_list(pref_list): 141 # Pref names seen so far. Used to detect any duplicates. 142 seen_names = set() 143 144 # The previous pref. Used to detect mis-ordered prefs. 145 prev_pref = None 146 147 for pref in pref_list: 148 # Check all given keys are known ones. 149 for key in pref: 150 if key not in VALID_KEYS: 151 error(f"invalid key `{key}`") 152 153 # 'name' must be present, valid, and in the right section. 154 if "name" not in pref: 155 error("missing `name` key") 156 name = pref["name"] 157 if type(name) is not str: 158 error(f"non-string `name` value `{name}`") 159 if "." not in name: 160 error(f"`name` value `{name}` lacks a '.'") 161 if name in seen_names: 162 error(f"`{name}` pref is defined more than once") 163 seen_names.add(name) 164 165 # Prefs must be ordered appropriately. 166 if prev_pref: 167 if mk_group(prev_pref) > mk_group(pref): 168 error( 169 "`{}` pref must come before `{}` pref".format( 170 name, prev_pref["name"] 171 ) 172 ) 173 174 # 'type' must be present and valid. 175 if "type" not in pref: 176 error(f"missing `type` key for pref `{name}`") 177 typ = pref["type"] 178 if typ not in VALID_TYPES: 179 error(f"invalid `type` value `{typ}` for pref `{name}`") 180 181 # 'value' must be present and valid. 182 if "value" not in pref: 183 error(f"missing `value` key for pref `{name}`") 184 value = pref["value"] 185 if typ in {"String", "DataMutexString"}: 186 if type(value) is not str: 187 error( 188 f"non-string `value` value `{value}` for `{typ}` pref `{name}`; " 189 "add double quotes" 190 ) 191 elif typ in VALID_BOOL_TYPES: 192 if value not in (True, False): 193 error(f"invalid boolean value `{value}` for pref `{name}`") 194 195 # 'mirror' must be present and valid. 196 if "mirror" not in pref: 197 error(f"missing `mirror` key for pref `{name}`") 198 mirror = pref["mirror"] 199 if typ.startswith("DataMutex"): 200 mirror += "_datamutex" 201 if mirror not in MIRROR_TEMPLATES: 202 error(f"invalid `mirror` value `{mirror}` for pref `{name}`") 203 204 # Check 'do_not_use_directly' if present. 205 if "do_not_use_directly" in pref: 206 do_not_use_directly = pref["do_not_use_directly"] 207 if type(do_not_use_directly) is not bool: 208 error( 209 f"non-boolean `do_not_use_directly` value `{do_not_use_directly}` for pref " 210 f"`{name}`" 211 ) 212 if do_not_use_directly and mirror == "never": 213 error( 214 "`do_not_use_directly` uselessly set with `mirror` value " 215 "`never` for pref `{}`".format(pref["name"]) 216 ) 217 218 # Check 'include' if present. 219 if "include" in pref: 220 include = pref["include"] 221 if type(include) is not str: 222 error(f"non-string `include` value `{include}` for pref `{name}`") 223 if include.startswith("<") and not include.endswith(">"): 224 error( 225 f"`include` value `{include}` starts with `<` but does not " 226 f"end with `>` for pref `{name}`" 227 ) 228 229 # Check 'rust' if present. 230 if "rust" in pref: 231 rust = pref["rust"] 232 if type(rust) is not bool: 233 error(f"non-boolean `rust` value `{rust}` for pref `{name}`") 234 if rust and mirror == "never": 235 error( 236 "`rust` uselessly set with `mirror` value `never` for " 237 "pref `{}`".format(pref["name"]) 238 ) 239 240 prev_pref = pref 241 242 243 def generate_code(pref_list, input_filenames): 244 first_line = HEADER_LINE.format(input_filenames=", ".join(input_filenames)) 245 246 # The required includes for StaticPrefs_<group>.h. 247 includes = defaultdict(set) 248 249 # StaticPrefList_<group>.h contains all the pref definitions for this 250 # group. 251 static_pref_list_group_h = defaultdict(lambda: [first_line, ""]) 252 253 # StaticPrefsCGetters.cpp contains C getters for all the mirrored prefs, 254 # for use by Rust code. 255 static_prefs_c_getters_cpp = [first_line, ""] 256 257 # static_prefs.rs contains C getter declarations and a macro. 258 static_prefs_rs_decls = [] 259 static_prefs_rs_macro = [] 260 261 # Generate the per-pref code (spread across multiple files). 262 for pref in pref_list: 263 name = pref["name"] 264 typ = pref["type"] 265 value = pref["value"] 266 mirror = pref["mirror"] 267 do_not_use_directly = pref.get("do_not_use_directly") 268 include = pref.get("include") 269 rust = pref.get("rust") 270 271 base_id = mk_id(pref["name"]) 272 full_id = base_id 273 if mirror == "once": 274 full_id += "_AtStartup" 275 if do_not_use_directly: 276 full_id += "_DoNotUseDirectly" 277 if typ.startswith("DataMutex"): 278 mirror += "_datamutex" 279 280 group = mk_group(pref) 281 282 if include: 283 if not include.startswith("<"): 284 # It's not a system header. Add double quotes. 285 include = f'"{include}"' 286 includes[group].add(include) 287 288 if typ == "String": 289 # Quote string literals, and escape double-quote chars. 290 value = '"{}"'.format(value.replace('"', '\\"')) 291 elif typ == "DataMutexString": 292 # Quote string literals, and escape double-quote chars. 293 value = '"{}"_ns'.format(value.replace('"', '\\"')) 294 elif typ in VALID_BOOL_TYPES: 295 # Convert Python bools to C++ bools. 296 if value is True: 297 value = "true" 298 elif value is False: 299 value = "false" 300 301 # Append the C++ definition to the relevant output file's code. 302 static_pref_list_group_h[group].append( 303 MIRROR_TEMPLATES[mirror].format( 304 name=name, 305 base_id=base_id, 306 full_id=full_id, 307 typ=typ, 308 value=value, 309 ) 310 ) 311 312 if rust: 313 passed_type = VALID_TYPES[typ] 314 if passed_type == "nsACString": 315 # Generate the C getter. 316 static_prefs_c_getters_cpp.append( 317 STATIC_PREFS_C_GETTERS_NSSTRING_TEMPLATE.format(full_id=full_id) 318 ) 319 320 # Generate the C getter declaration, in Rust. 321 decl = " pub fn StaticPrefs_{full_id}(result: *mut nsstring::nsACString);" 322 static_prefs_rs_decls.append(decl.format(full_id=full_id)) 323 324 # Generate the Rust macro entry. 325 macro = ' ("{name}") => (unsafe {{ let mut result = $crate::nsCString::new(); $crate::StaticPrefs_{full_id}(&mut *result); result }});' 326 static_prefs_rs_macro.append(macro.format(name=name, full_id=full_id)) 327 328 else: 329 # Generate the C getter. 330 static_prefs_c_getters_cpp.append( 331 STATIC_PREFS_C_GETTERS_TEMPLATE.format( 332 typ=passed_type, full_id=full_id 333 ) 334 ) 335 336 # Generate the C getter declaration, in Rust. 337 decl = " pub fn StaticPrefs_{full_id}() -> {typ};" 338 static_prefs_rs_decls.append( 339 decl.format(full_id=full_id, typ=RUST_TYPES[passed_type]) 340 ) 341 342 # Generate the Rust macro entry. 343 macro = ( 344 ' ("{name}") => (unsafe {{ $crate::StaticPrefs_{full_id}() }});' 345 ) 346 static_prefs_rs_macro.append(macro.format(name=name, full_id=full_id)) 347 348 # Delete this so that `group` can be reused below without Flake8 349 # complaining. 350 del group 351 352 # StaticPrefListAll.h contains one `#include "mozilla/StaticPrefList_X.h` 353 # line per pref group. 354 static_pref_list_all_h = [first_line, ""] 355 static_pref_list_all_h.extend( 356 f'#include "mozilla/StaticPrefList_{group}.h"' 357 for group in sorted(static_pref_list_group_h) 358 ) 359 static_pref_list_all_h.append("") 360 361 # StaticPrefsAll.h contains one `#include "mozilla/StaticPrefs_X.h` line per 362 # pref group. 363 static_prefs_all_h = [first_line, ""] 364 static_prefs_all_h.extend( 365 f'#include "mozilla/StaticPrefs_{group}.h"' 366 for group in sorted(static_pref_list_group_h) 367 ) 368 static_prefs_all_h.append("") 369 370 # StaticPrefs_<group>.h wraps StaticPrefList_<group>.h. It is the header 371 # used directly by application code. 372 static_prefs_group_h = defaultdict(list) 373 for group in sorted(static_pref_list_group_h): 374 static_prefs_group_h[group] = [first_line] 375 static_prefs_group_h[group].append( 376 STATIC_PREFS_GROUP_H_TEMPLATE1.format(group=group) 377 ) 378 if group in includes: 379 # Add any necessary includes, from 'h_include' values. 380 for include in sorted(includes[group]): 381 static_prefs_group_h[group].append(f"#include {include}") 382 static_prefs_group_h[group].append("") 383 static_prefs_group_h[group].append( 384 STATIC_PREFS_GROUP_H_TEMPLATE2.format(group=group) 385 ) 386 387 # static_prefs.rs contains the Rust macro getters. 388 static_prefs_rs = [first_line, "", "pub use nsstring::nsCString;", 'extern "C" {'] 389 static_prefs_rs.extend(static_prefs_rs_decls) 390 static_prefs_rs.extend(["}", "", "#[macro_export]", "macro_rules! pref {"]) 391 static_prefs_rs.extend(static_prefs_rs_macro) 392 static_prefs_rs.extend(["}", ""]) 393 394 def fold(lines): 395 return "\n".join(lines) 396 397 return { 398 "static_pref_list_all_h": fold(static_pref_list_all_h), 399 "static_prefs_all_h": fold(static_prefs_all_h), 400 "static_pref_list_group_h": { 401 k: fold(v) for k, v in static_pref_list_group_h.items() 402 }, 403 "static_prefs_group_h": {k: fold(v) for k, v in static_prefs_group_h.items()}, 404 "static_prefs_c_getters_cpp": fold(static_prefs_c_getters_cpp), 405 "static_prefs_rs": fold(static_prefs_rs), 406 } 407 408 409 def emit_code(fd, *pref_list_filenames): 410 pp = Preprocessor() 411 pp.context.update(buildconfig.defines["ALLDEFINES"]) 412 413 # A necessary hack until MOZ_DEBUG_FLAGS are part of buildconfig.defines. 414 if buildconfig.substs.get("MOZ_DEBUG"): 415 pp.context["DEBUG"] = "1" 416 417 if buildconfig.substs.get("TARGET_CPU") == "aarch64": 418 pp.context["MOZ_AARCH64"] = True 419 420 if buildconfig.substs.get("MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS"): 421 pp.context["MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS"] = True 422 423 pref_list = [] 424 input_files = [] 425 for this_filename in pref_list_filenames: 426 pp.out = StringIO() 427 pp.do_filter("substitution") 428 pp.do_include(this_filename) 429 430 try: 431 this_pref_list = yaml.safe_load(pp.out.getvalue()) 432 check_pref_list(this_pref_list) 433 pref_list.extend(this_pref_list) 434 input_files.append( 435 os.path.relpath( 436 this_filename, 437 os.environ.get("GECKO_PATH", os.environ.get("TOPSRCDIR")), 438 ) 439 ) 440 except (OSError, ValueError) as e: 441 print(f"{this_filename}: error:\n {e}\n") 442 sys.exit(1) 443 444 code = generate_code(pref_list, input_files) 445 # When generating multiple files from a script, the build system treats the 446 # first named output file (StaticPrefListAll.h in this case) specially -- it 447 # is created elsewhere, and written to via `fd`. 448 fd.write(code["static_pref_list_all_h"]) 449 450 # We must create the remaining output files ourselves. This requires 451 # creating the output directory directly if it doesn't already exist. 452 ensureParentDir(fd.name) 453 init_dirname = os.path.dirname(fd.name) 454 dirname = os.path.dirname(init_dirname) 455 456 with FileAvoidWrite(os.path.join(dirname, "StaticPrefsAll.h")) as output_file: 457 output_file.write(code["static_prefs_all_h"]) 458 459 for group, text in sorted(code["static_pref_list_group_h"].items()): 460 filename = f"StaticPrefList_{group}.h" 461 with FileAvoidWrite(os.path.join(init_dirname, filename)) as group_file: 462 group_file.write(text) 463 464 for group, text in sorted(code["static_prefs_group_h"].items()): 465 filename = f"StaticPrefs_{group}.h" 466 with FileAvoidWrite(os.path.join(dirname, filename)) as prefs_file: 467 prefs_file.write(text) 468 469 with FileAvoidWrite( 470 os.path.join(init_dirname, "StaticPrefsCGetters.cpp") 471 ) as cpp_file: 472 cpp_file.write(code["static_prefs_c_getters_cpp"]) 473 474 with FileAvoidWrite(os.path.join(dirname, "static_prefs.rs")) as rust_file: 475 rust_file.write(code["static_prefs_rs"])