Add container property to aconfig_declarations.
Bug: 311155208
Test: Unit test
Change-Id: I7b187138856d0144203961e82b6dad5e2f8eed9d
diff --git a/aconfig/codegen/Android.bp b/aconfig/codegen/Android.bp
new file mode 100644
index 0000000..494f7e6
--- /dev/null
+++ b/aconfig/codegen/Android.bp
@@ -0,0 +1,32 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+ name: "soong-aconfig-codegen",
+ pkgPath: "android/soong/aconfig/codegen",
+ deps: [
+ "blueprint",
+ "blueprint-pathtools",
+ "sbox_proto",
+ "soong",
+ "soong-aconfig",
+ "soong-android",
+ "soong-bazel",
+ "soong-java",
+ "soong-rust",
+ ],
+ srcs: [
+ "cc_aconfig_library.go",
+ "init.go",
+ "java_aconfig_library.go",
+ "rust_aconfig_library.go",
+ "testing.go",
+ ],
+ testSrcs: [
+ "java_aconfig_library_test.go",
+ "cc_aconfig_library_test.go",
+ "rust_aconfig_library_test.go",
+ ],
+ pluginFor: ["soong_build"],
+}
diff --git a/aconfig/codegen/cc_aconfig_library.go b/aconfig/codegen/cc_aconfig_library.go
new file mode 100644
index 0000000..7b68844
--- /dev/null
+++ b/aconfig/codegen/cc_aconfig_library.go
@@ -0,0 +1,184 @@
+// 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.
+
+package codegen
+
+import (
+ "android/soong/aconfig"
+ "android/soong/android"
+ "android/soong/bazel"
+ "android/soong/cc"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+
+ "fmt"
+ "strings"
+)
+
+type ccDeclarationsTagType struct {
+ blueprint.BaseDependencyTag
+}
+
+var ccDeclarationsTag = ccDeclarationsTagType{}
+
+const baseLibDep = "server_configurable_flags"
+
+type CcAconfigLibraryProperties struct {
+ // name of the aconfig_declarations module to generate a library for
+ Aconfig_declarations string
+
+ // default mode is "production", the other accepted modes are:
+ // "test": to generate test mode version of the library
+ // "exported": to generate exported mode version of the library
+ // an error will be thrown if the mode is not supported
+ Mode *string
+}
+
+type CcAconfigLibraryCallbacks struct {
+ properties *CcAconfigLibraryProperties
+
+ generatedDir android.WritablePath
+ headerDir android.WritablePath
+ generatedCpp android.WritablePath
+ generatedH android.WritablePath
+}
+
+func CcAconfigLibraryFactory() android.Module {
+ callbacks := &CcAconfigLibraryCallbacks{
+ properties: &CcAconfigLibraryProperties{},
+ }
+ return cc.GeneratedCcLibraryModuleFactory("cc_aconfig_library", callbacks)
+}
+
+func (this *CcAconfigLibraryCallbacks) GeneratorInit(ctx cc.BaseModuleContext) {
+}
+
+func (this *CcAconfigLibraryCallbacks) GeneratorProps() []interface{} {
+ return []interface{}{this.properties}
+}
+
+func (this *CcAconfigLibraryCallbacks) GeneratorDeps(ctx cc.DepsContext, deps cc.Deps) cc.Deps {
+ // Add a dependency for the declarations module
+ declarations := this.properties.Aconfig_declarations
+ if len(declarations) == 0 {
+ ctx.PropertyErrorf("aconfig_declarations", "aconfig_declarations property required")
+ } else {
+ ctx.AddDependency(ctx.Module(), ccDeclarationsTag, declarations)
+ }
+
+ // Add a dependency for the aconfig flags base library
+ deps.SharedLibs = append(deps.SharedLibs, baseLibDep)
+ // TODO: It'd be really nice if we could reexport this library and not make everyone do it.
+
+ return deps
+}
+
+func (this *CcAconfigLibraryCallbacks) GeneratorSources(ctx cc.ModuleContext) cc.GeneratedSource {
+ result := cc.GeneratedSource{}
+
+ // Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
+ declarationsModules := ctx.GetDirectDepsWithTag(ccDeclarationsTag)
+ if len(declarationsModules) != 1 {
+ panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
+ }
+ declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
+
+ // Figure out the generated file paths. This has to match aconfig's codegen_cpp.rs.
+ this.generatedDir = android.PathForModuleGen(ctx)
+
+ this.headerDir = android.PathForModuleGen(ctx, "include")
+ result.IncludeDirs = []android.Path{this.headerDir}
+ result.ReexportedDirs = []android.Path{this.headerDir}
+
+ basename := strings.ReplaceAll(declarations.Package, ".", "_")
+
+ this.generatedCpp = android.PathForModuleGen(ctx, basename+".cc")
+ result.Sources = []android.Path{this.generatedCpp}
+
+ this.generatedH = android.PathForModuleGen(ctx, "include", basename+".h")
+ result.Headers = []android.Path{this.generatedH}
+
+ return result
+}
+
+func (this *CcAconfigLibraryCallbacks) GeneratorFlags(ctx cc.ModuleContext, flags cc.Flags, deps cc.PathDeps) cc.Flags {
+ return flags
+}
+
+func (this *CcAconfigLibraryCallbacks) GeneratorBuildActions(ctx cc.ModuleContext, flags cc.Flags, deps cc.PathDeps) {
+ // Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
+ declarationsModules := ctx.GetDirectDepsWithTag(ccDeclarationsTag)
+ if len(declarationsModules) != 1 {
+ panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
+ }
+ declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
+
+ mode := proptools.StringDefault(this.properties.Mode, "production")
+ if !isModeSupported(mode) {
+ ctx.PropertyErrorf("mode", "%q is not a supported mode", mode)
+ }
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: cppRule,
+ Input: declarations.IntermediatePath,
+ Outputs: []android.WritablePath{
+ this.generatedCpp,
+ this.generatedH,
+ },
+ Description: "cc_aconfig_library",
+ Args: map[string]string{
+ "gendir": this.generatedDir.String(),
+ "mode": mode,
+ },
+ })
+}
+
+type bazelCcAconfigLibraryAttributes struct {
+ cc.SdkAttributes
+ Aconfig_declarations bazel.LabelAttribute
+ Dynamic_deps bazel.LabelListAttribute
+}
+
+// Convert the cc_aconfig_library module to bazel.
+//
+// This method is called from cc.ConvertWithBp2build to actually convert the
+// cc_aconfig_library module. This is necessary since the factory method of this
+// module type returns a cc library and the bp2build conversion is called on the
+// cc library type.
+
+func (this *CcAconfigLibraryCallbacks) GeneratorBp2build(ctx android.Bp2buildMutatorContext, module *cc.Module) bool {
+ if ctx.ModuleType() != "cc_aconfig_library" {
+ return false
+ }
+
+ attrs := bazelCcAconfigLibraryAttributes{
+ SdkAttributes: cc.Bp2BuildParseSdkAttributes(module),
+ Aconfig_declarations: *bazel.MakeLabelAttribute(android.BazelLabelForModuleDepSingle(ctx, this.properties.Aconfig_declarations).Label),
+ Dynamic_deps: bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, []string{baseLibDep})),
+ }
+ props := bazel.BazelTargetModuleProperties{
+ Rule_class: "cc_aconfig_library",
+ Bzl_load_location: "//build/bazel/rules/cc:cc_aconfig_library.bzl",
+ }
+
+ ctx.CreateBazelTargetModule(
+ props,
+ android.CommonAttributes{
+ Name: ctx.ModuleName(),
+ Tags: android.ApexAvailableTagsWithoutTestApexes(ctx, module),
+ },
+ &attrs)
+ return true
+}
diff --git a/aconfig/codegen/cc_aconfig_library_test.go b/aconfig/codegen/cc_aconfig_library_test.go
new file mode 100644
index 0000000..0c8a969
--- /dev/null
+++ b/aconfig/codegen/cc_aconfig_library_test.go
@@ -0,0 +1,106 @@
+// 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.
+
+package codegen
+
+import (
+ "fmt"
+ "testing"
+
+ "android/soong/android"
+ "android/soong/cc"
+)
+
+var ccCodegenModeTestData = []struct {
+ setting, expected string
+}{
+ {"", "production"},
+ {"mode: `production`,", "production"},
+ {"mode: `test`,", "test"},
+ {"mode: `exported`,", "exported"},
+}
+
+func TestCCCodegenMode(t *testing.T) {
+ for _, testData := range ccCodegenModeTestData {
+ testCCCodegenModeHelper(t, testData.setting, testData.expected)
+ }
+}
+
+func testCCCodegenModeHelper(t *testing.T, bpMode string, ruleMode string) {
+ t.Helper()
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ cc.PrepareForTestWithCcDefaultModules).
+ ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+ RunTestWithBp(t, fmt.Sprintf(`
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+
+ cc_library {
+ name: "server_configurable_flags",
+ srcs: ["server_configurable_flags.cc"],
+ }
+
+ cc_aconfig_library {
+ name: "my_cc_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ %s
+ }
+ `, bpMode))
+
+ module := result.ModuleForTests("my_cc_aconfig_library", "android_arm64_armv8-a_shared")
+ rule := module.Rule("cc_aconfig_library")
+ android.AssertStringEquals(t, "rule must contain test mode", rule.Args["mode"], ruleMode)
+}
+
+var incorrectCCCodegenModeTestData = []struct {
+ setting, expectedErr string
+}{
+ {"mode: `unsupported`,", "mode: \"unsupported\" is not a supported mode"},
+}
+
+func TestIncorrectCCCodegenMode(t *testing.T) {
+ for _, testData := range incorrectCCCodegenModeTestData {
+ testIncorrectCCCodegenModeHelper(t, testData.setting, testData.expectedErr)
+ }
+}
+
+func testIncorrectCCCodegenModeHelper(t *testing.T, bpMode string, err string) {
+ t.Helper()
+ android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ cc.PrepareForTestWithCcDefaultModules).
+ ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(err)).
+ RunTestWithBp(t, fmt.Sprintf(`
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+
+ cc_library {
+ name: "server_configurable_flags",
+ srcs: ["server_configurable_flags.cc"],
+ }
+
+ cc_aconfig_library {
+ name: "my_cc_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ %s
+ }
+ `, bpMode))
+}
diff --git a/aconfig/codegen/init.go b/aconfig/codegen/init.go
new file mode 100644
index 0000000..0bff9d2
--- /dev/null
+++ b/aconfig/codegen/init.go
@@ -0,0 +1,83 @@
+// 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.
+
+package codegen
+
+import (
+ "android/soong/android"
+
+ "github.com/google/blueprint"
+)
+
+var (
+ pctx = android.NewPackageContext("android/soong/aconfig/codegen")
+
+ // For java_aconfig_library: Generate java library
+ javaRule = pctx.AndroidStaticRule("java_aconfig_library",
+ blueprint.RuleParams{
+ Command: `rm -rf ${out}.tmp` +
+ ` && mkdir -p ${out}.tmp` +
+ ` && ${aconfig} create-java-lib` +
+ ` --mode ${mode}` +
+ ` --cache ${in}` +
+ ` --out ${out}.tmp` +
+ ` && $soong_zip -write_if_changed -jar -o ${out} -C ${out}.tmp -D ${out}.tmp` +
+ ` && rm -rf ${out}.tmp`,
+ CommandDeps: []string{
+ "$aconfig",
+ "$soong_zip",
+ },
+ Restat: true,
+ }, "mode")
+
+ // For cc_aconfig_library: Generate C++ library
+ cppRule = pctx.AndroidStaticRule("cc_aconfig_library",
+ blueprint.RuleParams{
+ Command: `rm -rf ${gendir}` +
+ ` && mkdir -p ${gendir}` +
+ ` && ${aconfig} create-cpp-lib` +
+ ` --mode ${mode}` +
+ ` --cache ${in}` +
+ ` --out ${gendir}`,
+ CommandDeps: []string{
+ "$aconfig",
+ },
+ }, "gendir", "mode")
+
+ // For rust_aconfig_library: Generate Rust library
+ rustRule = pctx.AndroidStaticRule("rust_aconfig_library",
+ blueprint.RuleParams{
+ Command: `rm -rf ${gendir}` +
+ ` && mkdir -p ${gendir}` +
+ ` && ${aconfig} create-rust-lib` +
+ ` --mode ${mode}` +
+ ` --cache ${in}` +
+ ` --out ${gendir}`,
+ CommandDeps: []string{
+ "$aconfig",
+ },
+ }, "gendir", "mode")
+)
+
+func init() {
+ RegisterBuildComponents(android.InitRegistrationContext)
+ pctx.HostBinToolVariable("aconfig", "aconfig")
+ pctx.HostBinToolVariable("soong_zip", "soong_zip")
+}
+
+func RegisterBuildComponents(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("cc_aconfig_library", CcAconfigLibraryFactory)
+ ctx.RegisterModuleType("java_aconfig_library", JavaDeclarationsLibraryFactory)
+ ctx.RegisterModuleType("rust_aconfig_library", RustAconfigLibraryFactory)
+}
diff --git a/aconfig/codegen/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
new file mode 100644
index 0000000..e2fb15b
--- /dev/null
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -0,0 +1,155 @@
+// 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.
+
+package codegen
+
+import (
+ "fmt"
+
+ "android/soong/aconfig"
+ "android/soong/android"
+ "android/soong/bazel"
+ "android/soong/java"
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+type declarationsTagType struct {
+ blueprint.BaseDependencyTag
+}
+
+var declarationsTag = declarationsTagType{}
+
+var aconfigSupportedModes = []string{"production", "test", "exported"}
+
+type JavaAconfigDeclarationsLibraryProperties struct {
+ // name of the aconfig_declarations module to generate a library for
+ Aconfig_declarations string
+
+ // default mode is "production", the other accepted modes are:
+ // "test": to generate test mode version of the library
+ // "exported": to generate exported mode version of the library
+ // an error will be thrown if the mode is not supported
+ Mode *string
+}
+
+type JavaAconfigDeclarationsLibraryCallbacks struct {
+ properties JavaAconfigDeclarationsLibraryProperties
+}
+
+func JavaDeclarationsLibraryFactory() android.Module {
+ callbacks := &JavaAconfigDeclarationsLibraryCallbacks{}
+ return java.GeneratedJavaLibraryModuleFactory("java_aconfig_library", callbacks, &callbacks.properties)
+}
+
+func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) DepsMutator(module *java.GeneratedJavaLibraryModule, ctx android.BottomUpMutatorContext) {
+ declarations := callbacks.properties.Aconfig_declarations
+ if len(declarations) == 0 {
+ // TODO: Add test for this case
+ ctx.PropertyErrorf("aconfig_declarations", "aconfig_declarations property required")
+ } else {
+ ctx.AddDependency(ctx.Module(), declarationsTag, declarations)
+ }
+
+ // Add aconfig-annotations-lib as a dependency for the optimization / code stripping annotations
+ module.AddSharedLibrary("aconfig-annotations-lib")
+ // TODO(b/303773055): Remove the annotation after access issue is resolved.
+ module.AddSharedLibrary("unsupportedappusage")
+}
+
+func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) GenerateSourceJarBuildActions(module *java.GeneratedJavaLibraryModule, ctx android.ModuleContext) android.Path {
+ // Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
+ declarationsModules := ctx.GetDirectDepsWithTag(declarationsTag)
+ if len(declarationsModules) != 1 {
+ panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
+ }
+ declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
+
+ // Generate the action to build the srcjar
+ srcJarPath := android.PathForModuleGen(ctx, ctx.ModuleName()+".srcjar")
+
+ mode := proptools.StringDefault(callbacks.properties.Mode, "production")
+ if !isModeSupported(mode) {
+ ctx.PropertyErrorf("mode", "%q is not a supported mode", mode)
+ }
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: javaRule,
+ Input: declarations.IntermediatePath,
+ Output: srcJarPath,
+ Description: "aconfig.srcjar",
+ Args: map[string]string{
+ "mode": mode,
+ },
+ })
+
+ return srcJarPath
+}
+
+func isModeSupported(mode string) bool {
+ return android.InList(mode, aconfigSupportedModes)
+}
+
+type bazelJavaAconfigLibraryAttributes struct {
+ Aconfig_declarations bazel.LabelAttribute
+ Sdk_version *string
+ Libs bazel.LabelListAttribute
+}
+
+func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) Bp2build(ctx android.Bp2buildMutatorContext, module *java.GeneratedJavaLibraryModule) {
+ if ctx.ModuleType() != "java_aconfig_library" {
+ return
+ }
+
+ // By default, soong builds the aconfig java library with private_current, however
+ // bazel currently doesn't support it so we default it to system_current. One reason
+ // is that the dependency of all java_aconfig_library aconfig-annotations-lib is
+ // built with system_current. For the java aconfig library itself it doesn't really
+ // matter whether it uses private API or system API because the only module it uses
+ // is DeviceConfig which is in system, and the rdeps of the java aconfig library
+ // won't change its sdk version either, so this should be fine.
+ // Ideally we should only use the default value if it is not set by the user, but
+ // bazel only supports a limited sdk versions, for example, the java_aconfig_library
+ // modules in framework/base use core_platform which is not supported by bazel yet.
+ // TODO(b/302148527): change soong to default to system_current as well.
+ sdkVersion := "system_current"
+
+ var libs bazel.LabelListAttribute
+ archVariantProps := module.GetArchVariantProperties(ctx, &java.CommonProperties{})
+ for axis, configToProps := range archVariantProps {
+ for config, p := range configToProps {
+ if archProps, ok := p.(*java.CommonProperties); ok {
+ var libLabels []bazel.Label
+ for _, d := range archProps.Libs {
+ neverlinkLabel := android.BazelLabelForModuleDepSingle(ctx, d)
+ neverlinkLabel.Label = neverlinkLabel.Label + "-neverlink"
+ libLabels = append(libLabels, neverlinkLabel)
+ }
+ libs.SetSelectValue(axis, config, (bazel.MakeLabelList(libLabels)))
+ }
+ }
+ }
+
+ attrs := bazelJavaAconfigLibraryAttributes{
+ Aconfig_declarations: *bazel.MakeLabelAttribute(android.BazelLabelForModuleDepSingle(ctx, callbacks.properties.Aconfig_declarations).Label),
+ Sdk_version: &sdkVersion,
+ Libs: libs,
+ }
+ props := bazel.BazelTargetModuleProperties{
+ Rule_class: "java_aconfig_library",
+ Bzl_load_location: "//build/bazel/rules/java:java_aconfig_library.bzl",
+ }
+
+ ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: ctx.ModuleName()}, &attrs)
+}
diff --git a/aconfig/codegen/java_aconfig_library_test.go b/aconfig/codegen/java_aconfig_library_test.go
new file mode 100644
index 0000000..cbfdc21
--- /dev/null
+++ b/aconfig/codegen/java_aconfig_library_test.go
@@ -0,0 +1,233 @@
+// 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.
+
+package codegen
+
+import (
+ "fmt"
+ "testing"
+
+ "android/soong/android"
+ "android/soong/java"
+)
+
+// Note: These tests cover the code in the java package. It'd be ideal of that code could
+// be in the aconfig package.
+
+// With the bp parameter that defines a my_module, make sure it has the LOCAL_ACONFIG_FILES entries
+func runJavaAndroidMkTest(t *testing.T, bp string) {
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ java.PrepareForTestWithJavaDefaultModules).
+ ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+ RunTestWithBp(t, bp+`
+ aconfig_declarations {
+ name: "my_aconfig_declarations_foo",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+
+ java_aconfig_library {
+ name: "my_java_aconfig_library_foo",
+ aconfig_declarations: "my_aconfig_declarations_foo",
+ }
+
+ aconfig_declarations {
+ name: "my_aconfig_declarations_bar",
+ package: "com.example.package",
+ srcs: ["bar.aconfig"],
+ }
+
+ java_aconfig_library {
+ name: "my_java_aconfig_library_bar",
+ aconfig_declarations: "my_aconfig_declarations_bar",
+ }
+ `)
+
+ module := result.ModuleForTests("my_module", "android_common").Module()
+
+ entry := android.AndroidMkEntriesForTest(t, result.TestContext, module)[0]
+
+ makeVar := entry.EntryMap["LOCAL_ACONFIG_FILES"]
+ android.AssertIntEquals(t, "len(LOCAL_ACONFIG_FILES)", 2, len(makeVar))
+ android.EnsureListContainsSuffix(t, makeVar, "my_aconfig_declarations_foo/intermediate.pb")
+ android.EnsureListContainsSuffix(t, makeVar, "my_aconfig_declarations_bar/intermediate.pb")
+}
+
+func TestAndroidMkJavaLibrary(t *testing.T) {
+ bp := `
+ java_library {
+ name: "my_module",
+ srcs: [
+ "src/foo.java",
+ ],
+ static_libs: [
+ "my_java_aconfig_library_foo",
+ "my_java_aconfig_library_bar",
+ ],
+ platform_apis: true,
+ }
+ `
+
+ runJavaAndroidMkTest(t, bp)
+}
+
+func TestAndroidMkAndroidApp(t *testing.T) {
+ bp := `
+ android_app {
+ name: "my_module",
+ srcs: [
+ "src/foo.java",
+ ],
+ static_libs: [
+ "my_java_aconfig_library_foo",
+ "my_java_aconfig_library_bar",
+ ],
+ platform_apis: true,
+ }
+ `
+
+ runJavaAndroidMkTest(t, bp)
+}
+
+func TestAndroidMkBinary(t *testing.T) {
+ bp := `
+ java_binary {
+ name: "my_module",
+ srcs: [
+ "src/foo.java",
+ ],
+ static_libs: [
+ "my_java_aconfig_library_foo",
+ "my_java_aconfig_library_bar",
+ ],
+ platform_apis: true,
+ main_class: "foo",
+ }
+ `
+
+ runJavaAndroidMkTest(t, bp)
+}
+
+func TestAndroidMkAndroidLibrary(t *testing.T) {
+ bp := `
+ android_library {
+ name: "my_module",
+ srcs: [
+ "src/foo.java",
+ ],
+ static_libs: [
+ "my_java_aconfig_library_foo",
+ "my_java_aconfig_library_bar",
+ ],
+ platform_apis: true,
+ }
+ `
+
+ runJavaAndroidMkTest(t, bp)
+}
+
+func TestAndroidMkBinaryThatLinksAgainstAar(t *testing.T) {
+ // Tests AndroidLibrary's propagation of flags through JavaInfo
+ bp := `
+ android_library {
+ name: "some_library",
+ srcs: [
+ "src/foo.java",
+ ],
+ static_libs: [
+ "my_java_aconfig_library_foo",
+ "my_java_aconfig_library_bar",
+ ],
+ platform_apis: true,
+ }
+ java_binary {
+ name: "my_module",
+ srcs: [
+ "src/bar.java",
+ ],
+ static_libs: [
+ "some_library",
+ ],
+ platform_apis: true,
+ main_class: "foo",
+ }
+ `
+
+ runJavaAndroidMkTest(t, bp)
+}
+
+func testCodegenMode(t *testing.T, bpMode string, ruleMode string) {
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ java.PrepareForTestWithJavaDefaultModules).
+ ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+ RunTestWithBp(t, fmt.Sprintf(`
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+
+ java_aconfig_library {
+ name: "my_java_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ %s
+ }
+ `, bpMode))
+
+ module := result.ModuleForTests("my_java_aconfig_library", "android_common")
+ rule := module.Rule("java_aconfig_library")
+ android.AssertStringEquals(t, "rule must contain test mode", rule.Args["mode"], ruleMode)
+}
+
+func testCodegenModeWithError(t *testing.T, bpMode string, err string) {
+ android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ java.PrepareForTestWithJavaDefaultModules).
+ ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(err)).
+ RunTestWithBp(t, fmt.Sprintf(`
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+
+ java_aconfig_library {
+ name: "my_java_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ %s
+ }
+ `, bpMode))
+}
+
+func TestDefaultProdMode(t *testing.T) {
+ testCodegenMode(t, "", "production")
+}
+
+func TestProdMode(t *testing.T) {
+ testCodegenMode(t, "mode: `production`,", "production")
+}
+
+func TestTestMode(t *testing.T) {
+ testCodegenMode(t, "mode: `test`,", "test")
+}
+
+func TestExportedMode(t *testing.T) {
+ testCodegenMode(t, "mode: `exported`,", "exported")
+}
+
+func TestUnsupportedMode(t *testing.T) {
+ testCodegenModeWithError(t, "mode: `unsupported`,", "mode: \"unsupported\" is not a supported mode")
+}
diff --git a/aconfig/codegen/rust_aconfig_library.go b/aconfig/codegen/rust_aconfig_library.go
new file mode 100644
index 0000000..3525de1
--- /dev/null
+++ b/aconfig/codegen/rust_aconfig_library.go
@@ -0,0 +1,97 @@
+package codegen
+
+import (
+ "fmt"
+
+ "android/soong/aconfig"
+ "android/soong/android"
+ "android/soong/rust"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+type rustDeclarationsTagType struct {
+ blueprint.BaseDependencyTag
+}
+
+var rustDeclarationsTag = rustDeclarationsTagType{}
+
+type RustAconfigLibraryProperties struct {
+ // name of the aconfig_declarations module to generate a library for
+ Aconfig_declarations string
+
+ // default mode is "production", the other accepted modes are:
+ // "test": to generate test mode version of the library
+ // "exported": to generate exported mode version of the library
+ // an error will be thrown if the mode is not supported
+ Mode *string
+}
+
+type aconfigDecorator struct {
+ *rust.BaseSourceProvider
+
+ Properties RustAconfigLibraryProperties
+}
+
+func NewRustAconfigLibrary(hod android.HostOrDeviceSupported) (*rust.Module, *aconfigDecorator) {
+ aconfig := &aconfigDecorator{
+ BaseSourceProvider: rust.NewSourceProvider(),
+ Properties: RustAconfigLibraryProperties{},
+ }
+
+ module := rust.NewSourceProviderModule(android.HostAndDeviceSupported, aconfig, false, false)
+ return module, aconfig
+}
+
+// rust_aconfig_library generates aconfig rust code from the provided aconfig declaration. This module type will
+// create library variants that can be used as a crate dependency by adding it to the rlibs, dylibs, and rustlibs
+// properties of other modules.
+func RustAconfigLibraryFactory() android.Module {
+ module, _ := NewRustAconfigLibrary(android.HostAndDeviceSupported)
+ return module.Init()
+}
+
+func (a *aconfigDecorator) SourceProviderProps() []interface{} {
+ return append(a.BaseSourceProvider.SourceProviderProps(), &a.Properties)
+}
+
+func (a *aconfigDecorator) GenerateSource(ctx rust.ModuleContext, deps rust.PathDeps) android.Path {
+ generatedDir := android.PathForModuleGen(ctx)
+ generatedSource := android.PathForModuleGen(ctx, "src", "lib.rs")
+
+ declarationsModules := ctx.GetDirectDepsWithTag(rustDeclarationsTag)
+
+ if len(declarationsModules) != 1 {
+ panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
+ }
+ declarations := ctx.OtherModuleProvider(declarationsModules[0], aconfig.DeclarationsProviderKey).(aconfig.DeclarationsProviderData)
+
+ mode := proptools.StringDefault(a.Properties.Mode, "production")
+ if !isModeSupported(mode) {
+ ctx.PropertyErrorf("mode", "%q is not a supported mode", mode)
+ }
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: rustRule,
+ Input: declarations.IntermediatePath,
+ Outputs: []android.WritablePath{
+ generatedSource,
+ },
+ Description: "rust_aconfig_library",
+ Args: map[string]string{
+ "gendir": generatedDir.String(),
+ "mode": mode,
+ },
+ })
+ a.BaseSourceProvider.OutputFiles = android.Paths{generatedSource}
+ return generatedSource
+}
+
+func (a *aconfigDecorator) SourceProviderDeps(ctx rust.DepsContext, deps rust.Deps) rust.Deps {
+ deps = a.BaseSourceProvider.SourceProviderDeps(ctx, deps)
+ deps.Rustlibs = append(deps.Rustlibs, "libflags_rust")
+ deps.Rustlibs = append(deps.Rustlibs, "liblazy_static")
+ ctx.AddDependency(ctx.Module(), rustDeclarationsTag, a.Properties.Aconfig_declarations)
+ return deps
+}
diff --git a/aconfig/codegen/rust_aconfig_library_test.go b/aconfig/codegen/rust_aconfig_library_test.go
new file mode 100644
index 0000000..c09f701
--- /dev/null
+++ b/aconfig/codegen/rust_aconfig_library_test.go
@@ -0,0 +1,159 @@
+package codegen
+
+import (
+ "fmt"
+ "testing"
+
+ "android/soong/android"
+ "android/soong/rust"
+)
+
+func TestRustAconfigLibrary(t *testing.T) {
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ rust.PrepareForTestWithRustIncludeVndk,
+ android.PrepareForTestWithArchMutator,
+ android.PrepareForTestWithDefaults,
+ android.PrepareForTestWithPrebuilts,
+ ).
+ ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+ RunTestWithBp(t, fmt.Sprintf(`
+ rust_library {
+ name: "libflags_rust", // test mock
+ crate_name: "flags_rust",
+ srcs: ["lib.rs"],
+ }
+ rust_library {
+ name: "liblazy_static", // test mock
+ crate_name: "lazy_static",
+ srcs: ["src/lib.rs"],
+ }
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+
+ rust_aconfig_library {
+ name: "libmy_rust_aconfig_library",
+ crate_name: "my_rust_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ }
+ `))
+
+ sourceVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
+ rule := sourceVariant.Rule("rust_aconfig_library")
+ android.AssertStringEquals(t, "rule must contain production mode", rule.Args["mode"], "production")
+
+ dylibVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_dylib")
+ rlibRlibStdVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_rlib-std")
+ rlibDylibStdVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_dylib-std")
+
+ variants := []android.TestingModule{
+ dylibVariant,
+ rlibDylibStdVariant,
+ rlibRlibStdVariant,
+ }
+
+ for _, variant := range variants {
+ android.AssertStringEquals(
+ t,
+ "dylib variant builds from generated rust code",
+ "out/soong/.intermediates/libmy_rust_aconfig_library/android_arm64_armv8-a_source/gen/src/lib.rs",
+ variant.Rule("rustc").Inputs[0].RelativeToTop().String(),
+ )
+ }
+}
+
+var rustCodegenModeTestData = []struct {
+ setting, expected string
+}{
+ {"", "production"},
+ {"mode: `production`,", "production"},
+ {"mode: `test`,", "test"},
+ {"mode: `exported`,", "exported"},
+}
+
+func TestRustCodegenMode(t *testing.T) {
+ for _, testData := range rustCodegenModeTestData {
+ testRustCodegenModeHelper(t, testData.setting, testData.expected)
+ }
+}
+
+func testRustCodegenModeHelper(t *testing.T, bpMode string, ruleMode string) {
+ t.Helper()
+ result := android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ rust.PrepareForTestWithRustIncludeVndk).
+ ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+ RunTestWithBp(t, fmt.Sprintf(`
+ rust_library {
+ name: "libflags_rust", // test mock
+ crate_name: "flags_rust",
+ srcs: ["lib.rs"],
+ }
+ rust_library {
+ name: "liblazy_static", // test mock
+ crate_name: "lazy_static",
+ srcs: ["src/lib.rs"],
+ }
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+ rust_aconfig_library {
+ name: "libmy_rust_aconfig_library",
+ crate_name: "my_rust_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ %s
+ }
+ `, bpMode))
+
+ module := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
+ rule := module.Rule("rust_aconfig_library")
+ android.AssertStringEquals(t, "rule must contain test mode", rule.Args["mode"], ruleMode)
+}
+
+var incorrectRustCodegenModeTestData = []struct {
+ setting, expectedErr string
+}{
+ {"mode: `unsupported`,", "mode: \"unsupported\" is not a supported mode"},
+}
+
+func TestIncorrectRustCodegenMode(t *testing.T) {
+ for _, testData := range incorrectRustCodegenModeTestData {
+ testIncorrectRustCodegenModeHelper(t, testData.setting, testData.expectedErr)
+ }
+}
+
+func testIncorrectRustCodegenModeHelper(t *testing.T, bpMode string, err string) {
+ t.Helper()
+ android.GroupFixturePreparers(
+ PrepareForTestWithAconfigBuildComponents,
+ rust.PrepareForTestWithRustIncludeVndk).
+ ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(err)).
+ RunTestWithBp(t, fmt.Sprintf(`
+ rust_library {
+ name: "libflags_rust", // test mock
+ crate_name: "flags_rust",
+ srcs: ["lib.rs"],
+ }
+ rust_library {
+ name: "liblazy_static", // test mock
+ crate_name: "lazy_static",
+ srcs: ["src/lib.rs"],
+ }
+ aconfig_declarations {
+ name: "my_aconfig_declarations",
+ package: "com.example.package",
+ srcs: ["foo.aconfig"],
+ }
+ rust_aconfig_library {
+ name: "libmy_rust_aconfig_library",
+ crate_name: "my_rust_aconfig_library",
+ aconfig_declarations: "my_aconfig_declarations",
+ %s
+ }
+ `, bpMode))
+}
diff --git a/aconfig/codegen/testing.go b/aconfig/codegen/testing.go
new file mode 100644
index 0000000..3e1c22e
--- /dev/null
+++ b/aconfig/codegen/testing.go
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 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.
+
+package codegen
+
+import (
+ "android/soong/aconfig"
+ "android/soong/android"
+)
+
+var PrepareForTestWithAconfigBuildComponents = android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+ ctx.RegisterModuleType("aconfig_declarations", aconfig.DeclarationsFactory)
+ RegisterBuildComponents(ctx)
+})