Merge "Remove tedbauer from OWNERS 🫡" into main
diff --git a/core/Makefile b/core/Makefile
index 651b1e0..2051c2c 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -6279,13 +6279,13 @@
$(foreach device,$(BOARD_SUPER_PARTITION_BLOCK_DEVICES), \
echo "super_$(device)_device_size=$(BOARD_SUPER_PARTITION_$(call to-upper,$(device))_DEVICE_SIZE)" >> $(1);)
$(if $(BOARD_SUPER_PARTITION_PARTITION_LIST), \
- echo "dynamic_partition_list=$(call filter-out-missing-partitions,$(BOARD_SUPER_PARTITION_PARTITION_LIST))" >> $(1))
+ echo "dynamic_partition_list=$(sort $(call filter-out-missing-partitions,$(BOARD_SUPER_PARTITION_PARTITION_LIST)))" >> $(1))
$(if $(BOARD_SUPER_PARTITION_GROUPS),
echo "super_partition_groups=$(BOARD_SUPER_PARTITION_GROUPS)" >> $(1))
$(foreach group,$(BOARD_SUPER_PARTITION_GROUPS), \
echo "super_$(group)_group_size=$(BOARD_$(call to-upper,$(group))_SIZE)" >> $(1); \
$(if $(BOARD_$(call to-upper,$(group))_PARTITION_LIST), \
- echo "super_$(group)_partition_list=$(call filter-out-missing-partitions,$(BOARD_$(call to-upper,$(group))_PARTITION_LIST))" >> $(1);))
+ echo "super_$(group)_partition_list=$(strip $(call filter-out-missing-partitions,$(BOARD_$(call to-upper,$(group))_PARTITION_LIST)))" >> $(1);))
$(if $(filter true,$(TARGET_USERIMAGES_SPARSE_EXT_DISABLED)), \
echo "build_non_sparse_super_partition=true" >> $(1))
$(if $(filter true,$(TARGET_USERIMAGES_SPARSE_F2FS_DISABLED)), \
diff --git a/shell_utils.sh b/shell_utils.sh
index 3124db5..61b0ebc 100644
--- a/shell_utils.sh
+++ b/shell_utils.sh
@@ -97,8 +97,11 @@
local out_dir=$(getoutdir)
local top=$(gettop)
- # return early if out dir is already a symlink
+ # return early if out dir is already a symlink.
if [[ -L "$out_dir" ]]; then
+ destination=$(readlink "$out_dir")
+ # ensure the destination exists.
+ mkdir -p "$destination"
return 0
fi
diff --git a/target/product/base_system.mk b/target/product/base_system.mk
index d699a26..3fe97ba 100644
--- a/target/product/base_system.mk
+++ b/target/product/base_system.mk
@@ -385,6 +385,9 @@
ifeq ($(RELEASE_MEMORY_MANAGEMENT_DAEMON),true)
PRODUCT_PACKAGES += \
mm_daemon
+else
+ PRODUCT_PACKAGES += \
+ init-mmd-prop.rc
endif
# VINTF data for system image
diff --git a/target/product/generic/Android.bp b/target/product/generic/Android.bp
index e96983d..c90c61c 100644
--- a/target/product/generic/Android.bp
+++ b/target/product/generic/Android.bp
@@ -698,7 +698,9 @@
true: [
"mm_daemon", // base_system (RELEASE_MEMORY_MANAGEMENT_DAEMON)
],
- default: [],
+ default: [
+ "init-mmd-prop.rc", // base_system
+ ],
}) + select(product_variable("debuggable"), {
true: [
"alloctop",
diff --git a/tools/aconfig/Cargo.toml b/tools/aconfig/Cargo.toml
index a031b7f..cb8377e 100644
--- a/tools/aconfig/Cargo.toml
+++ b/tools/aconfig/Cargo.toml
@@ -8,8 +8,8 @@
"aconfig_storage_read_api",
"aconfig_storage_write_api",
"aflags",
- "printflags",
- "convert_finalized_flags"
+ "convert_finalized_flags",
+ "exported_flag_check",
]
resolver = "2"
diff --git a/tools/aconfig/exported_flag_check/Android.bp b/tools/aconfig/exported_flag_check/Android.bp
new file mode 100644
index 0000000..184149a
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/Android.bp
@@ -0,0 +1,28 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "exported-flag-check-defaults",
+ edition: "2021",
+ clippy_lints: "android",
+ lints: "android",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "libaconfig_protos",
+ "libanyhow",
+ "libclap",
+ "libregex",
+ ],
+}
+
+rust_binary_host {
+ name: "exported-flag-check",
+ defaults: ["record-finalized-flags-defaults"],
+}
+
+rust_test_host {
+ name: "exported-flag-check-test",
+ defaults: ["record-finalized-flags-defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/tools/aconfig/exported_flag_check/Cargo.toml b/tools/aconfig/exported_flag_check/Cargo.toml
new file mode 100644
index 0000000..6bc07c5
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "exported-flag-check"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = ["cargo"]
+cargo = []
+
+[dependencies]
+aconfig_protos = { path = "../aconfig_protos" }
+anyhow = "1.0.69"
+clap = { version = "4.1.8", features = ["derive"] }
+regex = "1.11.1"
diff --git a/tools/aconfig/exported_flag_check/allow_list.txt b/tools/aconfig/exported_flag_check/allow_list.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/allow_list.txt
diff --git a/tools/aconfig/exported_flag_check/src/main.rs b/tools/aconfig/exported_flag_check/src/main.rs
new file mode 100644
index 0000000..866a700
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/src/main.rs
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+//! `exported-flag-check` is a tool to ensures that exported flags are used as intended
+use anyhow::{ensure, Result};
+use clap::Parser;
+use std::{collections::HashSet, fs::File, path::PathBuf};
+
+mod utils;
+
+use utils::{
+ check_all_exported_flags, extract_flagged_api_flags, get_exported_flags_from_binary_proto,
+ read_finalized_flags,
+};
+
+const ABOUT: &str = "CCheck Exported Flags
+
+This tool ensures that exported flags are used as intended. Exported flags, marked with
+`is_exported: true` in their declaration, are designed to control access to specific API
+features. This tool identifies and reports any exported flags that are not currently
+associated with an API feature, preventing unnecessary flag proliferation and maintaining
+a clear API design.
+
+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 by scanning through the API signature files
+ - Merge the found flags with the recorded flags from previous API finalizations
+ - Error if exported flags are not in the set
+";
+
+#[derive(Parser, Debug)]
+#[clap(about=ABOUT)]
+struct Cli {
+ #[arg(long)]
+ parsed_flags_file: PathBuf,
+
+ #[arg(long)]
+ api_signature_file: Vec<PathBuf>,
+
+ #[arg(long)]
+ finalized_flags_file: 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)?;
+ let flags = extract_flagged_api_flags(file)?;
+ flags_used_with_flaggedapi_annotation.extend(flags);
+ }
+
+ let file = File::open(args.parsed_flags_file)?;
+ let all_flags = get_exported_flags_from_binary_proto(file)?;
+
+ let file = File::open(args.finalized_flags_file)?;
+ let already_finalized_flags = read_finalized_flags(file)?;
+
+ let exported_flags = check_all_exported_flags(
+ &flags_used_with_flaggedapi_annotation,
+ &all_flags,
+ &already_finalized_flags,
+ )?;
+
+ println!("{}", exported_flags.join("\n"));
+
+ ensure!(
+ exported_flags.is_empty(),
+ "Flags {} are exported but not used to guard any API. \
+ Exported flag should be used to guard API",
+ exported_flags.join(",")
+ );
+ 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 = extract_flagged_api_flags(&input[..]).unwrap();
+
+ let input = include_bytes!("../tests/flags.protobuf");
+ let all_flags_to_be_finalized = get_exported_flags_from_binary_proto(&input[..]).unwrap();
+
+ let input = include_bytes!("../tests/finalized-flags.txt");
+ let already_finalized_flags = read_finalized_flags(&input[..]).unwrap();
+
+ let exported_flags = check_all_exported_flags(
+ &flags_used_with_flaggedapi_annotation,
+ &all_flags_to_be_finalized,
+ &already_finalized_flags,
+ )
+ .unwrap();
+
+ assert_eq!(1, exported_flags.len());
+ }
+}
diff --git a/tools/aconfig/exported_flag_check/src/utils.rs b/tools/aconfig/exported_flag_check/src/utils.rs
new file mode 100644
index 0000000..2c30424
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/src/utils.rs
@@ -0,0 +1,133 @@
+/*
+ * 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;
+use anyhow::{anyhow, Context, Result};
+use regex::Regex;
+use std::{
+ collections::HashSet,
+ io::{BufRead, BufReader, Read},
+};
+
+pub(crate) type FlagId = String;
+
+/// 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))
+}
+
+/// 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>(reader: R) -> Result<HashSet<FlagId>> {
+ BufReader::new(reader)
+ .lines()
+ .map(|line_result| line_result.context("Failed to read line from finalized flags file"))
+ .collect()
+}
+
+/// Parse a ProtoParsedFlags binary protobuf blob and return the fully qualified names of flags
+/// have is_exported as true.
+pub(crate) fn get_exported_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.is_exported())
+ .map(|flag| flag.fully_qualified_name());
+ Ok(HashSet::from_iter(iter))
+}
+
+fn get_allow_list() -> Result<HashSet<FlagId>> {
+ let allow_list: HashSet<FlagId> =
+ include_str!("../allow_list.txt").lines().map(|x| x.into()).collect();
+ Ok(allow_list)
+}
+
+/// Filter out the flags have is_exported as true but not used with @FlaggedApi annotations
+/// in the source tree, or in the previously finalized flags set.
+pub(crate) fn check_all_exported_flags(
+ flags_used_with_flaggedapi_annotation: &HashSet<FlagId>,
+ all_flags: &HashSet<FlagId>,
+ already_finalized_flags: &HashSet<FlagId>,
+) -> Result<Vec<FlagId>> {
+ let allow_list = get_allow_list()?;
+ let new_flags: Vec<FlagId> = all_flags
+ .difference(flags_used_with_flaggedapi_annotation)
+ .cloned()
+ .collect::<HashSet<_>>()
+ .difference(already_finalized_flags)
+ .cloned()
+ .collect::<HashSet<_>>()
+ .difference(&allow_list)
+ .cloned()
+ .collect();
+
+ Ok(new_flags.into_iter().collect())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_extract_flagged_api_flags() {
+ 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(),
+ ])
+ );
+ }
+
+ #[test]
+ fn test_read_finalized_flags() {
+ 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(),
+ ])
+ );
+ }
+
+ #[test]
+ fn test_disabled_or_read_write_flags_are_ignored() {
+ let bytes = include_bytes!("../tests/flags.protobuf");
+ let flags = get_exported_flags_from_binary_proto(&bytes[..]).unwrap();
+ assert_eq!(
+ flags,
+ HashSet::from_iter(vec![
+ "record_finalized_flags.test.foo".to_string(),
+ "record_finalized_flags.test.not_enabled".to_string()
+ ])
+ );
+ }
+}
diff --git a/tools/aconfig/exported_flag_check/tests/api-signature-file.txt b/tools/aconfig/exported_flag_check/tests/api-signature-file.txt
new file mode 100644
index 0000000..2ad559f
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/tests/api-signature-file.txt
@@ -0,0 +1,15 @@
+// Signature format: 2.0
+package android {
+
+ public final class C {
+ ctor public C();
+ }
+
+ public static final class C.inner {
+ ctor public C.inner();
+ field @FlaggedApi("record_finalized_flags.test.foo") public static final String FOO = "foo";
+ field @FlaggedApi("this.flag.is.not.used") public static final String BAR = "bar";
+ }
+
+}
+
diff --git a/tools/aconfig/exported_flag_check/tests/finalized-flags.txt b/tools/aconfig/exported_flag_check/tests/finalized-flags.txt
new file mode 100644
index 0000000..7fbcb3d
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/tests/finalized-flags.txt
@@ -0,0 +1,2 @@
+record_finalized_flags.test.bar
+record_finalized_flags.test.baz
diff --git a/tools/aconfig/exported_flag_check/tests/flags.declarations b/tools/aconfig/exported_flag_check/tests/flags.declarations
new file mode 100644
index 0000000..f86dbfa
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/tests/flags.declarations
@@ -0,0 +1,18 @@
+package: "record_finalized_flags.test"
+container: "system"
+
+flag {
+ name: "foo"
+ namespace: "test"
+ description: "FIXME"
+ bug: ""
+ is_exported:true
+}
+
+flag {
+ name: "not_enabled"
+ namespace: "test"
+ description: "FIXME"
+ bug: ""
+ is_exported:true
+}
diff --git a/tools/aconfig/exported_flag_check/tests/flags.protobuf b/tools/aconfig/exported_flag_check/tests/flags.protobuf
new file mode 100644
index 0000000..be64ef9
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/tests/flags.protobuf
Binary files differ
diff --git a/tools/aconfig/exported_flag_check/tests/flags.values b/tools/aconfig/exported_flag_check/tests/flags.values
new file mode 100644
index 0000000..ff6225d
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/tests/flags.values
@@ -0,0 +1,13 @@
+flag_value {
+ package: "record_finalized_flags.test"
+ name: "foo"
+ state: ENABLED
+ permission: READ_ONLY
+}
+
+flag_value {
+ package: "record_finalized_flags.test"
+ name: "not_enabled"
+ state: DISABLED
+ permission: READ_ONLY
+}
diff --git a/tools/aconfig/exported_flag_check/tests/generate-flags-protobuf.sh b/tools/aconfig/exported_flag_check/tests/generate-flags-protobuf.sh
new file mode 100755
index 0000000..701189c
--- /dev/null
+++ b/tools/aconfig/exported_flag_check/tests/generate-flags-protobuf.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+aconfig create-cache \
+ --package record_finalized_flags.test \
+ --container system \
+ --declarations flags.declarations \
+ --values flags.values \
+ --cache flags.protobuf