fune/toolkit/components/nimbus/generate/generate_feature_manifest.py
2022-11-22 18:23:54 +00:00

158 lines
5.5 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 json
import sys
from pathlib import Path
import jsonschema
import yaml
HEADER_LINE = (
"// This file was generated by generate_feature_manifest.py from FeatureManifest.yaml."
" DO NOT EDIT.\n"
)
FEATURE_MANIFEST_SCHEMA = Path("schemas", "ExperimentFeatureManifest.schema.json")
NIMBUS_FALLBACK_PREFS = (
"constexpr std::pair<nsLiteralCString, nsLiteralCString>"
"NIMBUS_FALLBACK_PREFS[]{{{}}};"
)
EXPORTED_SYMBOLS = 'const EXPORTED_SYMBOLS = ["{}"];'
def write_fm_cpp_headers(fd):
fd.write(HEADER_LINE)
def write_fm_js_headers(fd):
fd.write("\n".join([HEADER_LINE, EXPORTED_SYMBOLS.format("FeatureManifest")]))
def validate_feature_manifest(schema_path, manifest_path, manifest):
with open(schema_path, "r") as f:
schema = json.load(f)
set_prefs = {}
fallback_prefs = {}
for feature, feature_def in manifest.items():
try:
jsonschema.validate(feature_def, schema)
for variable, variable_def in feature_def.get("variables", {}).items():
set_pref = variable_def.get("setPref")
if set_pref is not None:
if set_pref in set_prefs:
other_feature = set_prefs[set_pref][0]
other_variable = set_prefs[set_pref][1]
print("Multiple variables cannot declare the same setPref")
print(
f"{feature} variable {variable} wants to set pref {set_pref}"
)
print(
f"{other_feature} variable {other_variable} wants to set pref "
f"{set_pref}"
)
raise Exception("Set prefs are exclusive")
set_prefs[set_pref] = (feature, variable)
fallback_pref = variable_def.get("fallbackPref")
if fallback_pref is not None:
fallback_prefs[fallback_pref] = (feature, variable)
conflicts = [
(
"setPref",
fallback_pref,
"fallbackPref",
set_prefs.get(fallback_pref),
),
("fallbackPref", set_pref, "setPref", fallback_prefs.get(set_pref)),
]
for kind, pref, other_kind, conflict in conflicts:
if conflict is not None:
print(
"The same pref cannot be specified in setPref and fallbackPref"
)
print(
f"{feature} variable {variable} has specified {kind} {pref}"
)
print(
f"{conflict[0]} variable {conflict[1]} has specified {other_kind} "
f"{pref}"
)
raise Exception("Set prefs and fallback prefs cannot overlap")
except Exception as e:
print("Error while validating FeatureManifest.yaml")
print(f"On key: {feature}")
print(f"Input file: {manifest_path}")
raise e
def generate_feature_manifest(fd, input_file):
write_fm_js_headers(fd)
try:
with open(input_file, "r", encoding="utf-8") as f:
manifest = yaml.safe_load(f)
validate_feature_manifest(
Path(input_file).parent / FEATURE_MANIFEST_SCHEMA, input_file, manifest
)
fd.write(f"const FeatureManifest = {json.dumps(manifest)};")
except (IOError) as e:
print(f"{input_file}: error:\n {e}\n")
sys.exit(1)
def platform_feature_manifest_array(features):
entries = []
for (feature, featureData) in features.items():
# Features have to be tagged isEarlyStartup to be accessible
# to Nimbus platform API
if not featureData.get("isEarlyStartup", False):
continue
entries.extend(
'{{ "{}_{}"_ns, "{}"_ns }}'.format(
feature, variable, variableData["fallbackPref"]
)
for (variable, variableData) in featureData.get("variables", {}).items()
if variableData.get("fallbackPref", False)
)
return NIMBUS_FALLBACK_PREFS.format(", ".join(entries))
def generate_platform_feature_manifest(fd, input_file):
write_fm_cpp_headers(fd)
def file_structure(data):
return "\n".join(
[
"#ifndef mozilla_NimbusFeaturesManifest_h",
"#define mozilla_NimbusFeaturesManifest_h",
"#include <utility>",
'#include "mozilla/Maybe.h"',
'#include "nsStringFwd.h"',
"namespace mozilla {",
platform_feature_manifest_array(data),
'#include "./lib/NimbusFeatureManifest.inc.h"',
"} // namespace mozilla",
"#endif // mozilla_NimbusFeaturesManifest_h",
]
)
try:
with open(input_file, "r", encoding="utf-8") as yaml_input:
data = yaml.safe_load(yaml_input)
fd.write(file_structure(data))
except (IOError) as e:
print("{}: error:\n {}\n".format(input_file, e))
sys.exit(1)