Add container property to aconfig_declarations.

Bug: 311155208
Test: Unit test
Change-Id: I7b187138856d0144203961e82b6dad5e2f8eed9d
This commit is contained in:
Yu Liu
2023-11-16 17:05:47 -08:00
parent 62093cf7fc
commit eae7b36699
26 changed files with 750 additions and 146 deletions

View File

@@ -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"],
}

View File

@@ -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
}

View File

@@ -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))
}

83
aconfig/codegen/init.go Normal file
View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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)
})