Merge "Replace --revert-annotation with a flags config file" into main
diff --git a/apex/apex_test.go b/apex/apex_test.go
index a0a42a1..36a6974 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11667,7 +11667,7 @@
// Arguments passed to aconfig to retrieve the state of the flags defined in the
// textproto files
- aconfigFlagArgs := m.Output("released-flagged-apis-exportable.txt").Args["flags_path"]
+ aconfigFlagArgs := m.Output("released-flags-exportable.pb").Args["flags_path"]
// "bar-lib" is a static_lib of "foo" and is passed to metalava as classpath. Thus the
// cache file provided by the associated aconfig_declarations module "bar" should be passed
diff --git a/java/builder.go b/java/builder.go
index ade9784..dff0032 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -310,7 +310,7 @@
gatherReleasedFlaggedApisRule = pctx.AndroidStaticRule("gatherReleasedFlaggedApisRule",
blueprint.RuleParams{
- Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}' ` +
+ Command: `${aconfig} dump-cache --dedup --format=protobuf ` +
`--out ${out} ` +
`${flags_path} ` +
`${filter_args} `,
@@ -320,8 +320,8 @@
generateMetalavaRevertAnnotationsRule = pctx.AndroidStaticRule("generateMetalavaRevertAnnotationsRule",
blueprint.RuleParams{
- Command: `${keep-flagged-apis} ${in} > ${out}`,
- CommandDeps: []string{"${keep-flagged-apis}"},
+ Command: `${aconfig-to-metalava-flags} ${in} > ${out}`,
+ CommandDeps: []string{"${aconfig-to-metalava-flags}"},
})
generateApiXMLRule = pctx.AndroidStaticRule("generateApiXMLRule",
@@ -339,7 +339,7 @@
pctx.HostBinToolVariable("aconfig", "aconfig")
pctx.HostBinToolVariable("ravenizer", "ravenizer")
pctx.HostBinToolVariable("apimapper", "apimapper")
- pctx.HostBinToolVariable("keep-flagged-apis", "keep-flagged-apis")
+ pctx.HostBinToolVariable("aconfig-to-metalava-flags", "aconfig-to-metalava-flags")
}
type javaBuilderFlags struct {
diff --git a/java/droidstubs.go b/java/droidstubs.go
index caad688..b3241cc 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -821,19 +821,17 @@
}
}
- if len(aconfigFlagsPaths) == 0 {
- // This argument should not be added for "everything" stubs
- cmd.Flag("--revert-annotation android.annotation.FlaggedApi")
- return
- }
+ // If aconfigFlagsPaths is empty then it is still important to generate the
+ // Metalava flags config file, albeit with an empty set of flags, so that all
+ // flagged APIs will be reverted.
- releasedFlaggedApisFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flagged-apis-%s.txt", stubsType.String()))
- revertAnnotationsFile := android.PathForModuleOut(ctx, fmt.Sprintf("revert-annotations-%s.txt", stubsType.String()))
+ releasedFlagsFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flags-%s.pb", stubsType.String()))
+ metalavaFlagsConfigFile := android.PathForModuleOut(ctx, fmt.Sprintf("flags-config-%s.xml", stubsType.String()))
ctx.Build(pctx, android.BuildParams{
Rule: gatherReleasedFlaggedApisRule,
Inputs: aconfigFlagsPaths,
- Output: releasedFlaggedApisFile,
+ Output: releasedFlagsFile,
Description: fmt.Sprintf("%s gather aconfig flags", stubsType),
Args: map[string]string{
"flags_path": android.JoinPathsWithPrefix(aconfigFlagsPaths, "--cache "),
@@ -843,12 +841,12 @@
ctx.Build(pctx, android.BuildParams{
Rule: generateMetalavaRevertAnnotationsRule,
- Input: releasedFlaggedApisFile,
- Output: revertAnnotationsFile,
- Description: fmt.Sprintf("%s revert annotations", stubsType),
+ Input: releasedFlagsFile,
+ Output: metalavaFlagsConfigFile,
+ Description: fmt.Sprintf("%s metalava flags config", stubsType),
})
- cmd.FlagWithInput("@", revertAnnotationsFile)
+ cmd.FlagWithInput("--config-file ", metalavaFlagsConfigFile)
}
func (d *Droidstubs) commonMetalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index dfdf877..1f9d223 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -410,12 +410,12 @@
m := result.ModuleForTests(t, "foo", "android_common")
android.AssertStringDoesContain(t, "foo generates revert annotations file",
- strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+ strings.Join(m.AllOutputs(), ""), "flags-config-exportable.xml")
// revert-annotations.txt passed to exportable stubs generation metalava command
manifest := m.Output("metalava_exportable.sbox.textproto")
cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
- android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
+ android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "flags-config-exportable.xml")
android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
@@ -460,7 +460,7 @@
m := result.ModuleForTests(t, "foo", "android_common")
- rule := m.Output("released-flagged-apis-exportable.txt")
+ rule := m.Output("released-flags-exportable.pb")
exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"])
}
diff --git a/java/java_test.go b/java/java_test.go
index a6290a6..6df81e5 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2912,12 +2912,12 @@
m := result.ModuleForTests(t, "foo", "android_common")
android.AssertStringDoesContain(t, "foo generates revert annotations file",
- strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+ strings.Join(m.AllOutputs(), ""), "flags-config-exportable.xml")
// revert-annotations.txt passed to exportable stubs generation metalava command
manifest := m.Output("metalava.sbox.textproto")
cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
- android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
+ android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "flags-config-exportable.xml")
}
func TestTestOnly(t *testing.T) {
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 94163a5..c0e13d5 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -287,9 +287,13 @@
],
}
-sh_binary_host {
- name: "keep-flagged-apis",
- src: "keep-flagged-apis.sh",
+python_binary_host {
+ name: "aconfig-to-metalava-flags",
+ main: "aconfig-to-metalava-flags.py",
+ srcs: ["aconfig-to-metalava-flags.py"],
+ libs: [
+ "libaconfig_python_proto",
+ ],
}
python_binary_host {
diff --git a/scripts/aconfig-to-metalava-flags.py b/scripts/aconfig-to-metalava-flags.py
new file mode 100644
index 0000000..efa85ec
--- /dev/null
+++ b/scripts/aconfig-to-metalava-flags.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# 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.
+
+"""Converts a set of aconfig protobuf flags to a Metalava config file."""
+
+# Formatted using `pyformat -i scripts/aconfig-to-metalava-flags.py`
+
+import argparse
+import sys
+import xml.etree.ElementTree as ET
+
+from protos import aconfig_pb2
+
+_READ_ONLY = aconfig_pb2.flag_permission.READ_ONLY
+_ENABLED = aconfig_pb2.flag_state.ENABLED
+_DISABLED = aconfig_pb2.flag_state.DISABLED
+
+# The namespace of the Metalava config file.
+CONFIG_NS = 'http://www.google.com/tools/metalava/config'
+
+
+def config_name(tag: str):
+ """Create a QName in the config namespace.
+
+ :param:tag the name of the entity in the config namespace.
+ """
+ return f'{{{CONFIG_NS}}}{tag}'
+
+
+def main():
+ """Program entry point."""
+ args_parser = argparse.ArgumentParser(
+ description='Generate Metalava flags config from aconfig protobuf',
+ )
+ args_parser.add_argument(
+ 'input',
+ help='The path to the aconfig protobuf file',
+ )
+ args = args_parser.parse_args(sys.argv[1:])
+
+ # Read the parsed_flags from the protobuf file.
+ with open(args.input, 'rb') as f:
+ parsed_flags = aconfig_pb2.parsed_flags.FromString(f.read())
+
+ # Create the structure of the XML config file.
+ config = ET.Element(config_name('config'))
+ api_flags = ET.SubElement(config, config_name('api-flags'))
+ # Create an <api-flag> element for each parsed_flag.
+ for flag in parsed_flags.parsed_flag:
+ if flag.permission == _READ_ONLY:
+ # Ignore any read only disabled flags as Metalava assumes that as the
+ # default when an <api-flags/> element is provided so this reduces the
+ # size of the file.
+ if flag.state == _DISABLED:
+ continue
+ mutability = 'immutable'
+ else:
+ mutability = 'mutable'
+ if flag.state == _ENABLED:
+ status = 'enabled'
+ else:
+ status = 'disabled'
+ attributes = {
+ 'package': flag.package,
+ 'name': flag.name,
+ 'mutability': mutability,
+ 'status': status,
+ }
+ # Convert the attribute names into qualified names in, what will become, the
+ # default namespace for the XML file. This is needed to ensure that the
+ # attribute will be written in the XML file without a prefix, e.g.
+ # `name="flag_name"`. Without it, a namespace prefix, e.g. `ns1`, will be
+ # synthesized for the attribute when writing the XML file, i.e. it
+ # will be written as `ns1:name="flag_name"`. Strictly speaking, that is
+ # unnecessary as the "Namespaces in XML 1.0 (Third Edition)" specification
+ # says that unprefixed attribute names have no namespace.
+ qualified_attributes = {config_name(k): v for (k, v) in attributes.items()}
+ ET.SubElement(api_flags, config_name('api-flag'), qualified_attributes)
+
+ # Create a tree and add white space so it will pretty print when written out.
+ tree = ET.ElementTree(config)
+ ET.indent(tree)
+
+ # Write the tree using the config namespace as the default.
+ tree.write(sys.stdout, encoding='unicode', default_namespace=CONFIG_NS)
+ sys.stdout.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/keep-flagged-apis.sh b/scripts/keep-flagged-apis.sh
deleted file mode 100755
index 48efb7a..0000000
--- a/scripts/keep-flagged-apis.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash -e
-#
-# Copyright 2023 Google Inc. All rights reserved.
-#
-# 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.
-
-# Convert a list of flags in the input file to a list of metalava options
-# that will keep the APIs for those flags will hiding all other flagged
-# APIs.
-
-FLAGS="$1"
-
-FLAGGED="android.annotation.FlaggedApi"
-
-# Convert the list of feature flags in the input file to Metalava options
-# of the form `--revert-annotation !android.annotation.FlaggedApi("<flag>")`
-# to prevent the annotated APIs from being hidden, i.e. include the annotated
-# APIs in the SDK snapshots.
-while read -r line; do
- # Escape and quote the key for sed
- escaped_line=$(echo "$line" | sed "s/'/\\\'/g; s/ /\\ /g")
-
- echo "--revert-annotation '!$FLAGGED(\"$escaped_line\")'"
-done < "$FLAGS"
-
-# Revert all flagged APIs, unless listed above.
-echo "--revert-annotation $FLAGGED"