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

Commit 0f4e0326 authored by Mårten Kongstad's avatar Mårten Kongstad
Browse files

record-finalized-flags: add actual implementation

From the command's --help text:

---- 8< ----
The prebuilts/sdk/<version>/finalized-flags.txt files list all aconfig flags that have been used
with @FlaggedApi annotations on APIs that have been finalized. These files are used to prevent
flags from being re-used for new, unfinalized, APIs, and by the aconfig code generation.

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 [--flag-file]
  - Read the previous finalized-flags.txt files from prebuilts/sdk [--finalized-flag-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 previos API finalizations
  - Print the set of flags to stdout
---- >8 ----

Bug: 377676163
Test: atest record-finalized-flags-test
Merged-In: Icde8c63fc54791429865168989bfb6af01845d15
Change-Id: Icde8c63fc54791429865168989bfb6af01845d15
parent 658988a0
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -9,7 +9,10 @@ rust_defaults {
    lints: "android",
    srcs: ["src/main.rs"],
    rustlibs: [
        "libaconfig_protos",
        "libanyhow",
        "libclap",
        "libregex",
    ],
}

+3 −0
Original line number Diff line number Diff line
@@ -9,4 +9,7 @@ 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" }
+49 −0
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 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());
    Ok(HashSet::from_iter(iter))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[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(),
            ])
        );
    }
}
+47 −0
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 anyhow::Result;
use std::{collections::HashSet, io::Read};

use crate::FlagId;

/// Read a list of flag names. The input is expected to be plain text, with each line containing
/// the name of a single flag.
pub(crate) fn read_finalized_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> {
    let mut contents = String::new();
    reader.read_to_string(&mut contents)?;
    let iter = contents.lines().map(|s| s.to_owned());
    Ok(HashSet::from_iter(iter))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let input = include_bytes!("../tests/finalized-flags.txt");
        let flags = read_finalized_flags(&input[..]).unwrap();
        assert_eq!(
            flags,
            HashSet::from_iter(vec![
                "record_finalized_flags.test.bar".to_string(),
                "record_finalized_flags.test.baz".to_string(),
            ])
        );
    }
}
+53 −0
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()]));
    }
}
Loading