Loading tools/record-finalized-flags/Android.bp +0 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ rust_defaults { lints: "android", srcs: ["src/main.rs"], rustlibs: [ "libaconfig_protos", "libanyhow", "libclap", "libregex", Loading tools/record-finalized-flags/Cargo.toml +0 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ version = "0.1.0" edition = "2021" [dependencies] aconfig_protos = { path = "../aconfig/aconfig_protos" } anyhow = { path = "../../../../external/rust/android-crates-io/crates/anyhow" } clap = { path = "../../../../external/rust/android-crates-io/crates/clap", features = ["derive"] } regex = { path = "../../../../external/rust/android-crates-io/crates/regex" } tools/record-finalized-flags/src/api_signature_files.rs→tools/record-finalized-flags/src/flag_report.rs +46 −0 Original line number Diff line number Diff line Loading @@ -15,18 +15,21 @@ */ use anyhow::Result; use regex::Regex; use std::{collections::HashSet, io::Read}; use crate::FlagId; /// Grep for all flags used with @FlaggedApi annotations in an API signature file (*current.txt /// file). pub(crate) fn extract_flagged_api_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> { let mut haystack = String::new(); reader.read_to_string(&mut haystack)?; let regex = Regex::new(r#"(?ms)@FlaggedApi\("(.*?)"\)"#).unwrap(); let iter = regex.captures_iter(&haystack).map(|cap| cap[1].to_owned()); /// Read a flag report generated by `metalava flag-report` representing all flags known by the /// build, and filter out the flags in the report that metalava has marked as finalized. pub(crate) fn read_and_filter_flag_report<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> { let mut contents = String::new(); reader.read_to_string(&mut contents)?; let iter = contents.lines().filter(|s| s.ends_with(",finalized") || s.ends_with(",kept")).map(|s| { let (flag, _) = s.split_once(",").expect("previous filter guarantees at least one comma"); flag.to_owned() }); Ok(HashSet::from_iter(iter)) } Loading @@ -36,14 +39,8 @@ mod tests { #[test] fn test() { let api_signature_file = include_bytes!("../tests/api-signature-file.txt"); let flags = extract_flagged_api_flags(&api_signature_file[..]).unwrap(); assert_eq!( flags, HashSet::from_iter(vec![ "record_finalized_flags.test.foo".to_string(), "this.flag.is.not.used".to_string(), ]) ); let input = include_bytes!("../tests/flag-report.csv"); let flags = read_and_filter_flag_report(&input[..]).unwrap(); assert_eq!(flags, HashSet::from_iter(["com.foo".to_string(), "com.baz".to_string(),])); } } tools/record-finalized-flags/src/flag_values.rsdeleted 100644 → 0 +0 −53 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use aconfig_protos::{ParsedFlagExt, ProtoFlagPermission, ProtoFlagState}; use anyhow::{anyhow, Result}; use std::{collections::HashSet, io::Read}; use crate::FlagId; /// Parse a ProtoParsedFlags binary protobuf blob and return the fully qualified names of flags /// that are slated for API finalization (i.e. are both ENABLED and READ_ONLY). pub(crate) fn get_relevant_flags_from_binary_proto<R: Read>( mut reader: R, ) -> Result<HashSet<FlagId>> { let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&buffer) .map_err(|_| anyhow!("failed to parse binary proto"))?; let iter = parsed_flags .parsed_flag .into_iter() .filter(|flag| { flag.state() == ProtoFlagState::ENABLED && flag.permission() == ProtoFlagPermission::READ_ONLY }) .map(|flag| flag.fully_qualified_name()); Ok(HashSet::from_iter(iter)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_disabled_or_read_write_flags_are_ignored() { let bytes = include_bytes!("../tests/flags.protobuf"); let flags = get_relevant_flags_from_binary_proto(&bytes[..]).unwrap(); assert_eq!(flags, HashSet::from_iter(vec!["record_finalized_flags.test.foo".to_string()])); } } tools/record-finalized-flags/src/main.rs +15 −80 Original line number Diff line number Diff line Loading @@ -18,11 +18,10 @@ //! prebuilts/sdk) of the flags used with @FlaggedApi APIs use anyhow::Result; use clap::Parser; use std::{collections::HashSet, fs::File, path::PathBuf}; use std::{fs::File, path::PathBuf}; mod api_signature_files; mod finalized_flags; mod flag_values; mod flag_report; pub(crate) type FlagId = String; Loading @@ -34,12 +33,10 @@ flags from being re-used for new, unfinalized, APIs, and by the aconfig code gen This tool works as follows: - Read API signature files from source tree (*current.txt files) [--api-signature-file] - Read the current aconfig flag values from source tree [--parsed-flags-file] - Read the previous finalized-flags.txt files from prebuilts/sdk [--finalized-flags-file] - Extract the flags slated for API finalization by scanning through the API signature files for flags that are ENABLED and READ_ONLY - Merge the found flags with the recorded flags from previous API finalizations - Read the state of the currently flags in the source as generated by `metalava flag-report`, and filter out the flags marked as finalized [--flag-report] - Merge and sort the two inputs - Print the set of flags to stdout "; Loading @@ -47,88 +44,26 @@ This tool works as follows: #[clap(about=ABOUT)] struct Cli { #[arg(long)] parsed_flags_file: PathBuf, finalized_flags: PathBuf, #[arg(long)] api_signature_file: Vec<PathBuf>, #[arg(long)] finalized_flags_file: PathBuf, } /// Filter out the ENABLED and READ_ONLY flags used with @FlaggedApi annotations in the source /// tree, and add those flags to the set of previously finalized flags. fn calculate_new_finalized_flags( flags_used_with_flaggedapi_annotation: &HashSet<FlagId>, all_flags_to_be_finalized: &HashSet<FlagId>, already_finalized_flags: &HashSet<FlagId>, ) -> HashSet<FlagId> { let new_flags: HashSet<_> = flags_used_with_flaggedapi_annotation .intersection(all_flags_to_be_finalized) .map(|s| s.to_owned()) .collect(); already_finalized_flags.union(&new_flags).map(|s| s.to_owned()).collect() flag_report: PathBuf, } fn main() -> Result<()> { let args = Cli::parse(); let mut flags_used_with_flaggedapi_annotation = HashSet::new(); for path in args.api_signature_file { let file = File::open(path)?; for flag in api_signature_files::extract_flagged_api_flags(file)?.drain() { flags_used_with_flaggedapi_annotation.insert(flag); } } let file = File::open(args.parsed_flags_file)?; let all_flags_to_be_finalized = flag_values::get_relevant_flags_from_binary_proto(file)?; let file = File::open(args.finalized_flags_file)?; let file = File::open(args.finalized_flags)?; let already_finalized_flags = finalized_flags::read_finalized_flags(file)?; let mut new_finalized_flags = Vec::from_iter(calculate_new_finalized_flags( &flags_used_with_flaggedapi_annotation, &all_flags_to_be_finalized, &already_finalized_flags, )); new_finalized_flags.sort(); let file = File::open(args.flag_report)?; let newly_finalized_flags = flag_report::read_and_filter_flag_report(file)?; println!("{}", new_finalized_flags.join("\n")); let mut all_finalized_flags: Vec<_> = already_finalized_flags.union(&newly_finalized_flags).map(|s| s.to_owned()).collect(); all_finalized_flags.sort(); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test() { let input = include_bytes!("../tests/api-signature-file.txt"); let flags_used_with_flaggedapi_annotation = api_signature_files::extract_flagged_api_flags(&input[..]).unwrap(); let input = include_bytes!("../tests/flags.protobuf"); let all_flags_to_be_finalized = flag_values::get_relevant_flags_from_binary_proto(&input[..]).unwrap(); println!("{}", all_finalized_flags.join("\n")); let input = include_bytes!("../tests/finalized-flags.txt"); let already_finalized_flags = finalized_flags::read_finalized_flags(&input[..]).unwrap(); let new_finalized_flags = calculate_new_finalized_flags( &flags_used_with_flaggedapi_annotation, &all_flags_to_be_finalized, &already_finalized_flags, ); assert_eq!( new_finalized_flags, HashSet::from_iter(vec![ "record_finalized_flags.test.foo".to_string(), "record_finalized_flags.test.bar".to_string(), "record_finalized_flags.test.baz".to_string(), ]) ); } Ok(()) } Loading
tools/record-finalized-flags/Android.bp +0 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ rust_defaults { lints: "android", srcs: ["src/main.rs"], rustlibs: [ "libaconfig_protos", "libanyhow", "libclap", "libregex", Loading
tools/record-finalized-flags/Cargo.toml +0 −1 Original line number Diff line number Diff line Loading @@ -9,7 +9,6 @@ version = "0.1.0" edition = "2021" [dependencies] aconfig_protos = { path = "../aconfig/aconfig_protos" } anyhow = { path = "../../../../external/rust/android-crates-io/crates/anyhow" } clap = { path = "../../../../external/rust/android-crates-io/crates/clap", features = ["derive"] } regex = { path = "../../../../external/rust/android-crates-io/crates/regex" }
tools/record-finalized-flags/src/api_signature_files.rs→tools/record-finalized-flags/src/flag_report.rs +46 −0 Original line number Diff line number Diff line Loading @@ -15,18 +15,21 @@ */ use anyhow::Result; use regex::Regex; use std::{collections::HashSet, io::Read}; use crate::FlagId; /// Grep for all flags used with @FlaggedApi annotations in an API signature file (*current.txt /// file). pub(crate) fn extract_flagged_api_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> { let mut haystack = String::new(); reader.read_to_string(&mut haystack)?; let regex = Regex::new(r#"(?ms)@FlaggedApi\("(.*?)"\)"#).unwrap(); let iter = regex.captures_iter(&haystack).map(|cap| cap[1].to_owned()); /// Read a flag report generated by `metalava flag-report` representing all flags known by the /// build, and filter out the flags in the report that metalava has marked as finalized. pub(crate) fn read_and_filter_flag_report<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> { let mut contents = String::new(); reader.read_to_string(&mut contents)?; let iter = contents.lines().filter(|s| s.ends_with(",finalized") || s.ends_with(",kept")).map(|s| { let (flag, _) = s.split_once(",").expect("previous filter guarantees at least one comma"); flag.to_owned() }); Ok(HashSet::from_iter(iter)) } Loading @@ -36,14 +39,8 @@ mod tests { #[test] fn test() { let api_signature_file = include_bytes!("../tests/api-signature-file.txt"); let flags = extract_flagged_api_flags(&api_signature_file[..]).unwrap(); assert_eq!( flags, HashSet::from_iter(vec![ "record_finalized_flags.test.foo".to_string(), "this.flag.is.not.used".to_string(), ]) ); let input = include_bytes!("../tests/flag-report.csv"); let flags = read_and_filter_flag_report(&input[..]).unwrap(); assert_eq!(flags, HashSet::from_iter(["com.foo".to_string(), "com.baz".to_string(),])); } }
tools/record-finalized-flags/src/flag_values.rsdeleted 100644 → 0 +0 −53 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use aconfig_protos::{ParsedFlagExt, ProtoFlagPermission, ProtoFlagState}; use anyhow::{anyhow, Result}; use std::{collections::HashSet, io::Read}; use crate::FlagId; /// Parse a ProtoParsedFlags binary protobuf blob and return the fully qualified names of flags /// that are slated for API finalization (i.e. are both ENABLED and READ_ONLY). pub(crate) fn get_relevant_flags_from_binary_proto<R: Read>( mut reader: R, ) -> Result<HashSet<FlagId>> { let mut buffer = Vec::new(); reader.read_to_end(&mut buffer)?; let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&buffer) .map_err(|_| anyhow!("failed to parse binary proto"))?; let iter = parsed_flags .parsed_flag .into_iter() .filter(|flag| { flag.state() == ProtoFlagState::ENABLED && flag.permission() == ProtoFlagPermission::READ_ONLY }) .map(|flag| flag.fully_qualified_name()); Ok(HashSet::from_iter(iter)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_disabled_or_read_write_flags_are_ignored() { let bytes = include_bytes!("../tests/flags.protobuf"); let flags = get_relevant_flags_from_binary_proto(&bytes[..]).unwrap(); assert_eq!(flags, HashSet::from_iter(vec!["record_finalized_flags.test.foo".to_string()])); } }
tools/record-finalized-flags/src/main.rs +15 −80 Original line number Diff line number Diff line Loading @@ -18,11 +18,10 @@ //! prebuilts/sdk) of the flags used with @FlaggedApi APIs use anyhow::Result; use clap::Parser; use std::{collections::HashSet, fs::File, path::PathBuf}; use std::{fs::File, path::PathBuf}; mod api_signature_files; mod finalized_flags; mod flag_values; mod flag_report; pub(crate) type FlagId = String; Loading @@ -34,12 +33,10 @@ flags from being re-used for new, unfinalized, APIs, and by the aconfig code gen This tool works as follows: - Read API signature files from source tree (*current.txt files) [--api-signature-file] - Read the current aconfig flag values from source tree [--parsed-flags-file] - Read the previous finalized-flags.txt files from prebuilts/sdk [--finalized-flags-file] - Extract the flags slated for API finalization by scanning through the API signature files for flags that are ENABLED and READ_ONLY - Merge the found flags with the recorded flags from previous API finalizations - Read the state of the currently flags in the source as generated by `metalava flag-report`, and filter out the flags marked as finalized [--flag-report] - Merge and sort the two inputs - Print the set of flags to stdout "; Loading @@ -47,88 +44,26 @@ This tool works as follows: #[clap(about=ABOUT)] struct Cli { #[arg(long)] parsed_flags_file: PathBuf, finalized_flags: PathBuf, #[arg(long)] api_signature_file: Vec<PathBuf>, #[arg(long)] finalized_flags_file: PathBuf, } /// Filter out the ENABLED and READ_ONLY flags used with @FlaggedApi annotations in the source /// tree, and add those flags to the set of previously finalized flags. fn calculate_new_finalized_flags( flags_used_with_flaggedapi_annotation: &HashSet<FlagId>, all_flags_to_be_finalized: &HashSet<FlagId>, already_finalized_flags: &HashSet<FlagId>, ) -> HashSet<FlagId> { let new_flags: HashSet<_> = flags_used_with_flaggedapi_annotation .intersection(all_flags_to_be_finalized) .map(|s| s.to_owned()) .collect(); already_finalized_flags.union(&new_flags).map(|s| s.to_owned()).collect() flag_report: PathBuf, } fn main() -> Result<()> { let args = Cli::parse(); let mut flags_used_with_flaggedapi_annotation = HashSet::new(); for path in args.api_signature_file { let file = File::open(path)?; for flag in api_signature_files::extract_flagged_api_flags(file)?.drain() { flags_used_with_flaggedapi_annotation.insert(flag); } } let file = File::open(args.parsed_flags_file)?; let all_flags_to_be_finalized = flag_values::get_relevant_flags_from_binary_proto(file)?; let file = File::open(args.finalized_flags_file)?; let file = File::open(args.finalized_flags)?; let already_finalized_flags = finalized_flags::read_finalized_flags(file)?; let mut new_finalized_flags = Vec::from_iter(calculate_new_finalized_flags( &flags_used_with_flaggedapi_annotation, &all_flags_to_be_finalized, &already_finalized_flags, )); new_finalized_flags.sort(); let file = File::open(args.flag_report)?; let newly_finalized_flags = flag_report::read_and_filter_flag_report(file)?; println!("{}", new_finalized_flags.join("\n")); let mut all_finalized_flags: Vec<_> = already_finalized_flags.union(&newly_finalized_flags).map(|s| s.to_owned()).collect(); all_finalized_flags.sort(); Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test() { let input = include_bytes!("../tests/api-signature-file.txt"); let flags_used_with_flaggedapi_annotation = api_signature_files::extract_flagged_api_flags(&input[..]).unwrap(); let input = include_bytes!("../tests/flags.protobuf"); let all_flags_to_be_finalized = flag_values::get_relevant_flags_from_binary_proto(&input[..]).unwrap(); println!("{}", all_finalized_flags.join("\n")); let input = include_bytes!("../tests/finalized-flags.txt"); let already_finalized_flags = finalized_flags::read_finalized_flags(&input[..]).unwrap(); let new_finalized_flags = calculate_new_finalized_flags( &flags_used_with_flaggedapi_annotation, &all_flags_to_be_finalized, &already_finalized_flags, ); assert_eq!( new_finalized_flags, HashSet::from_iter(vec![ "record_finalized_flags.test.foo".to_string(), "record_finalized_flags.test.bar".to_string(), "record_finalized_flags.test.baz".to_string(), ]) ); } Ok(()) }