Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0c02fb05 authored by Dennis Shen's avatar Dennis Shen Committed by Android (Google) Code Review
Browse files

Merge "Allow mainline beta namespace config" into main

parents f4358382 e69d60a8
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -45,6 +45,9 @@ rust_test_host {
        "libitertools",
    ],
    test_suites: ["general-tests"],
    data: [
        "tests/mainline_beta_namespaces.json"
    ]
}

// integration tests: general
+24 −0
Original line number Diff line number Diff line
{
    "namespaces": {
        "com_android_tethering": {
            "container": "com.android.tethering",
            "allow_exported": true
        },
        "com_android_networkstack": {
            "container": "com.android.networkstack",
            "allow_exported": false
        },
        "com_android_captiveportallogin": {
            "container": "com.android.captiveportallogin",
            "allow_exported": false
        },
        "com_android_healthfitness": {
            "container": "com.android.healthfitness",
            "allow_exported": true
        },
        "com_android_mediaprovider": {
            "container": "com.android.mediaprovider",
            "allow_exported": true
        }
    }
}
+47 −3
Original line number Diff line number Diff line
@@ -91,6 +91,24 @@ Allow the same flag to be present in multiple cache files; if duplicates are fou
a single instance.
"#;

const MAINLINE_BETA_NAMESPACE_CONFIG: &str = r#"
A json file to configure mainline beta namespaces. This option is internal to Google. The json
configuration should assume the following format:

{
    "namespaces": {
        "com_android_tethering": {
            "container": "com.android.tethering",
            "allow_exported": true
        },
        "com_android_mediaprovider": {
            "container": "com.android.mediaprovider",
            "allow_exported": true
        }
    }
}
"#;

/// Conventional prefix to mark response file
pub const RESPONSE_FILE_PREFIX: char = '@';

@@ -111,6 +129,7 @@ pub enum ParsedCommand {
        default_permission: aconfig_protos::ProtoFlagPermission,
        allow_read_write: bool,
        cache_out_path: String,
        mainline_beta_namespace_config: Option<PathBuf>,
    },
    CreateJavaLib {
        cache_path: String,
@@ -168,8 +187,11 @@ fn build_cli() -> Command {
                        .value_parser(clap::value_parser!(bool))
                        .default_value("true"),
                )
                .arg(Arg::new("cache").long("cache").required(true).help("Output cache file path."))
                .arg(
                    Arg::new("cache").long("cache").required(true).help("Output cache file path."),
                    Arg::new("mainline-beta-namespace-config")
                        .long("mainline-beta-namespace-config")
                        .long_help(MAINLINE_BETA_NAMESPACE_CONFIG.trim()),
                ),
        )
        .subcommand(
@@ -339,6 +361,17 @@ pub fn parse_args(
        Some(("create-cache", sub_matches)) => {
            let declarations = get_zero_or_more_string_paths_from_arg(sub_matches, "declarations");
            let values = get_zero_or_more_string_paths_from_arg(sub_matches, "values");
            let mainline_beta_namespace_config =
                match sub_matches.get_one::<String>("mainline-beta-namespace-config") {
                    Some(config) => {
                        if config.is_empty() {
                            None
                        } else {
                            Some(PathBuf::from(config))
                        }
                    }
                    None => None,
                };
            Ok(ParsedCommand::CreateCache {
                package: get_required_arg::<String>(sub_matches, "package")?.clone(),
                container: get_required_arg::<String>(sub_matches, "container")?.clone(),
@@ -350,6 +383,7 @@ pub fn parse_args(
                )?,
                allow_read_write: *get_required_arg::<bool>(sub_matches, "allow-read-write")?,
                cache_out_path: get_required_arg::<String>(sub_matches, "cache")?.clone(),
                mainline_beta_namespace_config,
            })
        }
        Some(("create-java-lib", sub_matches)) => Ok(ParsedCommand::CreateJavaLib {
@@ -452,7 +486,8 @@ mod tests {
             --values flag.val \
             --cache /output/cache.pb \
             --default-permission READ_WRITE \
             --allow-read-write true";
             --allow-read-write true \
             --mainline-beta-namespace-config /path/to/some/file.json";
        let input_args = create_os_command(command_string);
        let parsed = parse_args(input_args)?;

@@ -465,6 +500,7 @@ mod tests {
            default_permission,
            allow_read_write,
            cache_out_path,
            mainline_beta_namespace_config,
        } = parsed
        {
            assert_eq!(package, "com.test.cache");
@@ -476,6 +512,10 @@ mod tests {
            assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE);
            assert!(allow_read_write);
            assert_eq!(cache_out_path, "/output/cache.pb");
            assert_eq!(
                mainline_beta_namespace_config,
                Some(PathBuf::from("/path/to/some/file.json"))
            );
        }
        Ok(())
    }
@@ -661,6 +701,7 @@ mod tests {
            cache_out_path,
            default_permission,
            allow_read_write,
            mainline_beta_namespace_config,
        } = parsed
        {
            assert_eq!(package, "com.via.respfile");
@@ -672,6 +713,7 @@ mod tests {
            assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE);
            assert!(allow_read_write);
            assert_eq!(cache_out_path, "cache.pb");
            assert_eq!(mainline_beta_namespace_config, None);
        }

        Ok(())
@@ -705,6 +747,7 @@ mod tests {
            cache_out_path,
            default_permission,
            allow_read_write,
            mainline_beta_namespace_config,
        } = parsed
        {
            assert_eq!(package, "com.via.respfile");
@@ -716,6 +759,7 @@ mod tests {
            assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE);
            assert!(allow_read_write);
            assert_eq!(cache_out_path, "cache.pb");
            assert_eq!(mainline_beta_namespace_config, None);
        }

        Ok(())
+182 −72
Original line number Diff line number Diff line
@@ -17,8 +17,9 @@
use anyhow::{anyhow, bail, ensure, Context, Result};
use convert_finalized_flags::FinalizedFlagMap;
use itertools::Itertools;
use lazy_static::lazy_static;
use protobuf::Message;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use std::hash::Hasher;
@@ -73,18 +74,50 @@ pub struct OutputFile {
pub const DEFAULT_FLAG_STATE: ProtoFlagState = ProtoFlagState::DISABLED;
pub const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission = ProtoFlagPermission::READ_WRITE;

// A hash map from beta namespace name to the corresponding containers. This is used to
// determine if an RW flag is a mainline beta flag which is defined as:
// 1, defined inside a beta namespace
// 2, has the corresponding mainline module as container
// TODO(b/328657418): Populate the map with mainline beta namespaces
lazy_static! {
    static ref MAINLINE_BETA_NAMESPACES: HashMap<&'static str, &'static str> =
        vec![].into_iter().collect();
#[derive(Serialize, Deserialize, Debug)]
pub struct NamespaceSetting {
    pub container: String,
    pub allow_exported: bool,
}

fn assign_storage_backend(pf: &mut ProtoParsedFlag) -> Result<()> {
    let is_mainline_beta = MAINLINE_BETA_NAMESPACES.get(pf.namespace()) == Some(&pf.container());
#[derive(Serialize, Deserialize, Debug)]
pub struct MainlineBetaNamespaces {
    pub namespaces: HashMap<String, NamespaceSetting>,
}

#[allow(dead_code)]
impl MainlineBetaNamespaces {
    fn is_mainline_beta_flag(&self, pf: &ProtoParsedFlag) -> bool {
        match self.namespaces.get(pf.namespace()) {
            Some(setting) => setting.container == pf.container(),
            None => false,
        }
    }

    fn supports_exported_mode(&self, pf: &ProtoParsedFlag) -> bool {
        match self.namespaces.get(pf.namespace()) {
            Some(setting) => {
                if setting.container == pf.container() {
                    setting.allow_exported
                } else {
                    panic!("Should not be called on none mainline beta flag")
                }
            }
            None => {
                panic!("Should not be called on none mainline beta flag")
            }
        }
    }
}

fn assign_storage_backend(
    pf: &mut ProtoParsedFlag,
    beta_namespaces: &Option<MainlineBetaNamespaces>,
) -> Result<()> {
    let is_mainline_beta = match beta_namespaces {
        Some(namespaces) => namespaces.is_mainline_beta_flag(pf),
        None => false,
    };
    let is_read_only = pf.permission() == ProtoFlagPermission::READ_ONLY;
    let storage = if is_read_only {
        ProtoFlagStorageBackend::NONE
@@ -105,9 +138,18 @@ pub fn parse_flags(
    values: Vec<Input>,
    default_permission: ProtoFlagPermission,
    allow_read_write: bool,
    mainline_beta_namespace_config: Option<PathBuf>,
) -> Result<Vec<u8>> {
    let mut parsed_flags = ProtoParsedFlags::new();

    let beta_namespaces: Option<MainlineBetaNamespaces> = match mainline_beta_namespace_config {
        Some(file) => {
            let contents = std::fs::read_to_string(file)?;
            Some(serde_json::from_str(&contents)?)
        }
        None => None,
    };

    for mut input in declarations {
        let mut contents = String::new();
        input
@@ -131,6 +173,7 @@ pub fn parse_flags(
            container,
            flag_declarations.container()
        );

        for mut flag_declaration in flag_declarations.flag.into_iter() {
            aconfig_protos::flag_declaration::verify_fields(&flag_declaration)
                .with_context(|| input.error_context())?;
@@ -162,7 +205,7 @@ pub fn parse_flags(
            let purpose = flag_declaration.metadata.purpose();
            metadata.set_purpose(purpose);
            parsed_flag.metadata = Some(metadata).into();
            assign_storage_backend(&mut parsed_flag)?;
            assign_storage_backend(&mut parsed_flag, &beta_namespaces)?;

            // verify ParsedFlag looks reasonable
            aconfig_protos::parsed_flag::verify_fields(&parsed_flag)?;
@@ -211,7 +254,7 @@ pub fn parse_flags(
            parsed_flag.set_state(flag_value.state());
            if parsed_flag.permission() != flag_value.permission() {
                parsed_flag.set_permission(flag_value.permission());
                assign_storage_backend(parsed_flag)?;
                assign_storage_backend(parsed_flag, &beta_namespaces)?;
            }
            let mut tracepoint = ProtoTracepoint::new();
            tracepoint.set_source(input.source.clone());
@@ -590,6 +633,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            true,
            None,
        )
        .unwrap();
        let parsed_flags =
@@ -624,6 +668,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -656,6 +701,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -685,6 +731,7 @@ mod tests {
            vec![],
            ProtoFlagPermission::READ_WRITE,
            false,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -727,6 +774,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            false,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -769,6 +817,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            false,
            None,
        )
        .unwrap();
        let parsed_flags =
@@ -814,6 +863,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -850,6 +900,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            true,
            None,
        )
        .unwrap();
        let parsed_flags =
@@ -859,6 +910,43 @@ mod tests {
        assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose());
    }

    fn get_parsed_flag_proto(
        container: &'static str,
        package: &'static str,
        decl: &'static str,
        val: Option<&'static str>,
        config: Option<PathBuf>,
    ) -> ProtoParsedFlag {
        let declaration =
            vec![Input { source: "memory".to_string(), reader: Box::new(decl.as_bytes()) }];

        let value: Vec<Input> = match val {
            Some(val_str) => {
                vec![Input { source: "memory".to_string(), reader: Box::new(val_str.as_bytes()) }]
            }
            None => {
                vec![]
            }
        };

        let flags_bytes = crate::commands::parse_flags(
            package,
            container,
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            config,
        )
        .unwrap();

        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();

        assert_eq!(1, parsed_flags.parsed_flag.len());
        parsed_flags.parsed_flag.first().unwrap().clone()
    }

    #[test]
    fn test_parse_flags_metadata_storage() {
        let metadata_flag = r#"
@@ -872,33 +960,14 @@ mod tests {
        }
        "#;

        // Case 1, regular RW flag without value file override
        let declaration = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(metadata_flag.as_bytes()),
        }];
        let value: Vec<Input> = vec![];
        let config = Some(PathBuf::from("tests/mainline_beta_namespaces.json"));

        let flags_bytes = crate::commands::parse_flags(
            "com.first",
            "test",
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
        )
        .unwrap();
        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
        assert_eq!(1, parsed_flags.parsed_flag.len());
        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
        // Case 1, regular RW flag without value file override
        let parsed_flag =
            get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone());
        assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());

        // Case 2, regular RW flag with value file override to RO
        let declaration = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(metadata_flag.as_bytes()),
        }];
        let first_flag_value = r#"
        flag_value {
            package: "com.first"
@@ -907,23 +976,13 @@ mod tests {
            permission: READ_ONLY
        }
        "#;
        let value = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(first_flag_value.as_bytes()),
        }];
        let flags_bytes = crate::commands::parse_flags(
            "com.first",
        let parsed_flag = get_parsed_flag_proto(
            "test",
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
        )
        .unwrap();
        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
        assert_eq!(1, parsed_flags.parsed_flag.len());
        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
            "com.first",
            metadata_flag,
            Some(first_flag_value),
            config.clone(),
        );
        assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());

        // Case 3, fixed read only flag
@@ -938,30 +997,81 @@ mod tests {
            is_fixed_read_only: true
        }
        "#;
        let declaration = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(metadata_flag.as_bytes()),
        }];
        let value: Vec<Input> = vec![];

        let flags_bytes = crate::commands::parse_flags(
        let parsed_flag =
            get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone());
        assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());

        // Case 4, mainline beta namespace fixed read only flag
        let metadata_flag = r#"
        package: "com.first"
        container: "com.android.tethering"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
            is_fixed_read_only: true
        }
        "#;
        let parsed_flag = get_parsed_flag_proto(
            "com.android.tethering",
            "com.first",
            "test",
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
        )
        .unwrap();
        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
        assert_eq!(1, parsed_flags.parsed_flag.len());
        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
            metadata_flag,
            None,
            config.clone(),
        );
        assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());

        // TODO case 4, mainline beta namespace fixed read only flag
        // TODO case 5, mainline beta namespace platform flag
        // TODO case 6, mainline beta namespace mainline flag
        // Case 5, mainline beta namespace platform flag
        let metadata_flag = r#"
        package: "com.first"
        container: "system"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
        }
        "#;
        let parsed_flag =
            get_parsed_flag_proto("system", "com.first", metadata_flag, None, config.clone());
        assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());

        // Case 6, mainline beta namespace mainline flag
        let metadata_flag = r#"
        package: "com.first"
        container: "com.android.tethering"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
        }
        "#;
        let parsed_flag = get_parsed_flag_proto(
            "com.android.tethering",
            "com.first",
            metadata_flag,
            None,
            config.clone(),
        );
        assert_eq!(ProtoFlagStorageBackend::DEVICE_CONFIG, parsed_flag.metadata.storage());

        // Case 7, mainline beta namespace mainline flag but without config
        let metadata_flag = r#"
        package: "com.first"
        container: "com.android.tethering"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
        }
        "#;
        let parsed_flag =
            get_parsed_flag_proto("com.android.tethering", "com.first", metadata_flag, None, None);
        assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());
    }

    #[test]
+2 −0
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ fn main() -> Result<()> {
            default_permission,
            allow_read_write,
            cache_out_path,
            mainline_beta_namespace_config,
        } => {
            let output = commands::parse_flags(
                &package,
@@ -121,6 +122,7 @@ fn main() -> Result<()> {
                open_zero_or_more_files(&values)?,       // values
                default_permission,
                allow_read_write,
                mainline_beta_namespace_config,
            )
            .context("failed to create cache")?;
            write_output_to_file_or_stdout(&cache_out_path, &output)?;
Loading