From d427974184e01b38f0858a94213686abe9cd3a8b Mon Sep 17 00:00:00 2001 From: Beth Rennie Date: Mon, 4 May 2026 14:31:23 -0400 Subject: [PATCH] Bug 2036867 - Include experimenter setPref annotations for gecko-pref variables in FML Now that we have pref support on Firefox for Android 152+, we need to include the pref annotations that Experimenter expects in the generated manifests. --- .../ExperimentFeatureManifest.schema.json | 12 +++++ .../src/backends/experimenter_manifest.rs | 50 +++++++++++-------- .../nimbus-fml/src/command_line/workflows.rs | 48 ++++++++++++++++++ 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/components/support/nimbus-fml/ExperimentFeatureManifest.schema.json b/components/support/nimbus-fml/ExperimentFeatureManifest.schema.json index b6dff0c87b..cb62634da4 100644 --- a/components/support/nimbus-fml/ExperimentFeatureManifest.schema.json +++ b/components/support/nimbus-fml/ExperimentFeatureManifest.schema.json @@ -43,6 +43,18 @@ "description": { "type": "string", "description": "Explain how this value is being used" + }, + "setPref": { + "type": "object", + "properties": { + "branch": { + "type": "string", + "enum": ["user", "default"], + }, + "pref": { + "type": "string" + } + } } }, "required": ["type", "description"], diff --git a/components/support/nimbus-fml/src/backends/experimenter_manifest.rs b/components/support/nimbus-fml/src/backends/experimenter_manifest.rs index cd9e194e3c..fafcd75e3b 100644 --- a/components/support/nimbus-fml/src/backends/experimenter_manifest.rs +++ b/components/support/nimbus-fml/src/backends/experimenter_manifest.rs @@ -10,7 +10,9 @@ use serde::{Deserialize, Serialize}; use crate::{ command_line::commands::GenerateExperimenterManifestCmd, error::{FMLError, Result}, - intermediate_representation::{FeatureDef, FeatureManifest, PropDef, TargetLanguage, TypeRef}, + intermediate_representation::{ + FeatureDef, FeatureManifest, GeckoPrefDef, PropDef, TargetLanguage, TypeRef, + }, }; pub(crate) type ExperimenterManifest = BTreeMap; @@ -36,6 +38,10 @@ pub(crate) struct ExperimenterFeatureProperty { #[serde(rename = "enum")] #[serde(skip_serializing_if = "Option::is_none")] variants: Option>, + + #[serde(rename = "setPref")] + #[serde(skip_serializing_if = "Option::is_none")] + set_pref: Option, } impl TryFrom for ExperimenterManifest { @@ -71,30 +77,32 @@ impl FeatureManifest { props.iter().try_for_each(|prop| -> Result<()> { let typ = ExperimentManifestPropType::from(prop.typ()).to_string(); - let yaml_prop = if let TypeRef::Enum(e) = prop.typ() { - let enum_def = self - .find_enum(&e) - .ok_or(FMLError::InternalError("Found enum with no definition"))?; - - let variants = enum_def - .variants - .iter() - .map(|variant| variant.name()) - .collect::>(); - - ExperimenterFeatureProperty { - variants: Some(variants), - description: prop.doc(), - property_type: typ, + let variants = match prop.typ() { + TypeRef::Enum(e) => { + let enum_def = self + .find_enum(&e) + .ok_or(FMLError::InternalError("Found enum with no definition"))?; + + Some( + enum_def + .variants + .iter() + .map(|variant| variant.name()) + .collect::>(), + ) } - } else { + _ => None, + }; + + map.insert( + prop.name(), ExperimenterFeatureProperty { - variants: None, + variants, description: prop.doc(), property_type: typ, - } - }; - map.insert(prop.name(), yaml_prop); + set_pref: prop.gecko_pref.clone(), + }, + ); Ok(()) })?; Ok(map) diff --git a/components/support/nimbus-fml/src/command_line/workflows.rs b/components/support/nimbus-fml/src/command_line/workflows.rs index dce029e771..f3ee484ab0 100644 --- a/components/support/nimbus-fml/src/command_line/workflows.rs +++ b/components/support/nimbus-fml/src/command_line/workflows.rs @@ -567,6 +567,54 @@ mod test { Ok(()) } + #[test] + fn test_generate_experimenter_gecko_prefs() -> Result<()> { + let tmpfile = NamedTempFile::new()?; + let cmd = create_experimenter_manifest_cmd("fixtures/fe/gecko-pref.yaml", tmpfile.path())?; + generate_experimenter_manifest(&cmd)?; + + let manifest: serde_yaml::Value = serde_yaml::from_reader(&tmpfile)?; + println!("{:?}", manifest); + + let variables = manifest + .get("gecko-nimbus-validation") + .unwrap() + .get("variables") + .unwrap(); + + fn assert_pref_var( + variable: &serde_yaml::Value, + expected_pref: &str, + expected_branch: &str, + ) { + let pref_annotation = variable.get("setPref").unwrap(); + let pref = pref_annotation.get("pref").unwrap().as_str().unwrap(); + assert_eq!(pref, expected_pref); + let branch = pref_annotation.get("branch").unwrap().as_str().unwrap(); + assert_eq!(branch, expected_branch); + } + + assert_pref_var( + variables.get("test-preference-bool").unwrap(), + "gecko.nimbus.test.bool", + "default", + ); + + assert_pref_var( + variables.get("test-preference-int").unwrap(), + "gecko.nimbus.test.int", + "default", + ); + + assert_pref_var( + variables.get("test-preference-string").unwrap(), + "gecko.nimbus.test.string", + "default", + ); + + Ok(()) + } + #[test] fn test_validate_command() -> Result<()> { let paths = MANIFEST_PATHS