• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 use aconfig_protos::ParsedFlagExt;
18 use anyhow::{anyhow, Context, Result};
19 use regex::Regex;
20 use std::{
21     collections::HashSet,
22     io::{BufRead, BufReader, Read},
23 };
24 
25 pub(crate) type FlagId = String;
26 
27 /// Grep for all flags used with @FlaggedApi annotations in an API signature file (*current.txt
28 /// file).
extract_flagged_api_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>>29 pub(crate) fn extract_flagged_api_flags<R: Read>(mut reader: R) -> Result<HashSet<FlagId>> {
30     let mut haystack = String::new();
31     reader.read_to_string(&mut haystack)?;
32     let regex = Regex::new(r#"(?ms)@FlaggedApi\("(.*?)"\)"#).unwrap();
33     let iter = regex.captures_iter(&haystack).map(|cap| cap[1].to_owned());
34     Ok(HashSet::from_iter(iter))
35 }
36 
37 /// Read a list of flag names. The input is expected to be plain text, with each line containing
38 /// the name of a single flag.
read_finalized_flags<R: Read>(reader: R) -> Result<HashSet<FlagId>>39 pub(crate) fn read_finalized_flags<R: Read>(reader: R) -> Result<HashSet<FlagId>> {
40     BufReader::new(reader)
41         .lines()
42         .map(|line_result| line_result.context("Failed to read line from finalized flags file"))
43         .collect()
44 }
45 
46 /// Parse a ProtoParsedFlags binary protobuf blob and return the fully qualified names of flags
47 /// have is_exported as true.
get_exported_flags_from_binary_proto<R: Read>( mut reader: R, ) -> Result<HashSet<FlagId>>48 pub(crate) fn get_exported_flags_from_binary_proto<R: Read>(
49     mut reader: R,
50 ) -> Result<HashSet<FlagId>> {
51     let mut buffer = Vec::new();
52     reader.read_to_end(&mut buffer)?;
53     let parsed_flags = aconfig_protos::parsed_flags::try_from_binary_proto(&buffer)
54         .map_err(|_| anyhow!("failed to parse binary proto"))?;
55     let iter = parsed_flags
56         .parsed_flag
57         .into_iter()
58         .filter(|flag| flag.is_exported())
59         .map(|flag| flag.fully_qualified_name());
60     Ok(HashSet::from_iter(iter))
61 }
62 
get_allow_flag_list() -> Result<HashSet<FlagId>>63 fn get_allow_flag_list() -> Result<HashSet<FlagId>> {
64     let allow_list: HashSet<FlagId> =
65         include_str!("../allow_flag_list.txt").lines().map(|x| x.into()).collect();
66     Ok(allow_list)
67 }
68 
get_allow_package_list() -> Result<HashSet<FlagId>>69 fn get_allow_package_list() -> Result<HashSet<FlagId>> {
70     let allow_list: HashSet<FlagId> =
71         include_str!("../allow_package_list.txt").lines().map(|x| x.into()).collect();
72     Ok(allow_list)
73 }
74 
75 /// Filter out the flags have is_exported as true but not used with @FlaggedApi annotations
76 /// in the source tree, or in the previously finalized flags set.
check_all_exported_flags( flags_used_with_flaggedapi_annotation: &HashSet<FlagId>, all_flags: &HashSet<FlagId>, already_finalized_flags: &HashSet<FlagId>, ) -> Result<Vec<FlagId>>77 pub(crate) fn check_all_exported_flags(
78     flags_used_with_flaggedapi_annotation: &HashSet<FlagId>,
79     all_flags: &HashSet<FlagId>,
80     already_finalized_flags: &HashSet<FlagId>,
81 ) -> Result<Vec<FlagId>> {
82     let allow_flag_list = get_allow_flag_list()?;
83     let allow_package_list = get_allow_package_list()?;
84 
85     let new_flags: Vec<FlagId> = all_flags
86         .difference(flags_used_with_flaggedapi_annotation)
87         .cloned()
88         .collect::<HashSet<_>>()
89         .difference(already_finalized_flags)
90         .cloned()
91         .collect::<HashSet<_>>()
92         .difference(&allow_flag_list)
93         .filter(|flag| {
94             if let Some(last_dot_index) = flag.rfind('.') {
95                 let package_name = &flag[..last_dot_index];
96                 !allow_package_list.contains(package_name)
97             } else {
98                 true
99             }
100         })
101         .cloned()
102         .collect();
103 
104     Ok(new_flags)
105 }
106 
107 #[cfg(test)]
108 mod tests {
109     use super::*;
110 
111     #[test]
test_extract_flagged_api_flags()112     fn test_extract_flagged_api_flags() {
113         let api_signature_file = include_bytes!("../tests/api-signature-file.txt");
114         let flags = extract_flagged_api_flags(&api_signature_file[..]).unwrap();
115         assert_eq!(
116             flags,
117             HashSet::from_iter(vec![
118                 "record_finalized_flags.test.foo".to_string(),
119                 "this.flag.is.not.used".to_string(),
120             ])
121         );
122     }
123 
124     #[test]
test_read_finalized_flags()125     fn test_read_finalized_flags() {
126         let input = include_bytes!("../tests/finalized-flags.txt");
127         let flags = read_finalized_flags(&input[..]).unwrap();
128         assert_eq!(
129             flags,
130             HashSet::from_iter(vec![
131                 "record_finalized_flags.test.bar".to_string(),
132                 "record_finalized_flags.test.baz".to_string(),
133             ])
134         );
135     }
136 
137     #[test]
test_disabled_or_read_write_flags_are_ignored()138     fn test_disabled_or_read_write_flags_are_ignored() {
139         let bytes = include_bytes!("../tests/flags.protobuf");
140         let flags = get_exported_flags_from_binary_proto(&bytes[..]).unwrap();
141         assert_eq!(
142             flags,
143             HashSet::from_iter(vec![
144                 "record_finalized_flags.test.foo".to_string(),
145                 "record_finalized_flags.test.not_enabled".to_string()
146             ])
147         );
148     }
149 }
150