diff options
Diffstat (limited to 'toolkit/components/nimbus/generate/generate_feature_manifest.py')
-rw-r--r-- | toolkit/components/nimbus/generate/generate_feature_manifest.py | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/toolkit/components/nimbus/generate/generate_feature_manifest.py b/toolkit/components/nimbus/generate/generate_feature_manifest.py new file mode 100644 index 0000000000..c17f320184 --- /dev/null +++ b/toolkit/components/nimbus/generate/generate_feature_manifest.py @@ -0,0 +1,212 @@ +# 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[]{{{}}};" +) + +# Do not add new feature IDs to this list! isEarlyStartup is being deprecated. +# See https://bugzilla.mozilla.org/show_bug.cgi?id=1875331 for details. +ALLOWED_ISEARLYSTARTUP_FEATURE_IDS = { + "abouthomecache", + "aboutwelcome", + "backgroundThreads", + "backgroundUpdate", + "bookmarks", + "dapTelemetry", + "deviceMigration", + "frecency", + "fullPageTranslation", + "fullPageTranslationAutomaticPopup", + "fxaButtonVisibility", + "gcParallelMarking", + "gleanInternalSdk", + "jitHintsCache", + "jitThresholds", + "jsParallelParsing", + "majorRelease2022", + "migrationWizard", + "newtab", + "nimbus-qa-2", + "opaqueResponseBlocking", + "phc", + "pocketNewtab", + "powerSaver", + "reportBrokenSite", + "saveToPocket", + "searchConfiguration", + "shellService", + "testFeature", + "updatePrompt", + "upgradeDialog", + "windowsJumpList", +} + + +def write_fm_headers(fd): + fd.write(HEADER_LINE) + + +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_id, feature in manifest.items(): + try: + jsonschema.validate(feature, schema) + + is_early_startup = feature.get("isEarlyStartup", False) + allowed_is_early_startup = feature_id in ALLOWED_ISEARLYSTARTUP_FEATURE_IDS + if is_early_startup != allowed_is_early_startup: + if is_early_startup: + print(f"Feature {feature_id} is marked isEarlyStartup: true") + print( + "isEarlyStartup is deprecated and no new isEarlyStartup features can be added" + ) + print( + "See https://bugzilla.mozilla.org/show_bug.cgi?id=1875331 for details" + ) + raise Exception("isEarlyStartup is deprecated") + else: + print( + f"Feature {feature_id} is not early startup but is in the allow list." + ) + print("Please remove it from generate_feature_manifest.py") + raise Exception("isEarlyStatup is deprecated") + + for variable, variable_def in feature.get("variables", {}).items(): + set_pref = variable_def.get("setPref") + + if isinstance(set_pref, dict): + set_pref = set_pref.get("pref") + + 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_id} 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_id, variable) + + fallback_pref = variable_def.get("fallbackPref") + if fallback_pref is not None: + fallback_prefs[fallback_pref] = (feature_id, 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_id} 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_id}") + print(f"Input file: {manifest_path}") + raise e + + +def generate_feature_manifest(fd, input_file): + write_fm_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"export 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_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) |