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

Commit 0de0d9ed authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "record-finalized-flags: use metalava's flag-report as source of truth" into main

parents 5aa9ba4b fc12d8d5
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@ rust_defaults {
    lints: "android",
    srcs: ["src/main.rs"],
    rustlibs: [
        "libaconfig_protos",
        "libanyhow",
        "libclap",
        "libregex",
+0 −1
Original line number Diff line number Diff line
@@ -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" }
+46 −0
Original line number Diff line number Diff line
@@ -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))
}

@@ -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(),]));
    }
}
+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()]));
    }
}
+15 −80
Original line number Diff line number Diff line
@@ -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;

@@ -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
";

@@ -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