diff --git a/android/config.go b/android/config.go index 9e94e0517..acadb3ff3 100644 --- a/android/config.go +++ b/android/config.go @@ -183,6 +183,16 @@ func (c Config) MaxPageSizeSupported() string { return String(c.config.productVariables.DeviceMaxPageSizeSupported) } +// The release version passed to aconfig, derived from RELEASE_VERSION +func (c Config) ReleaseVersion() string { + return c.config.productVariables.ReleaseVersion +} + +// The flag values files passed to aconfig, derived from RELEASE_VERSION +func (c Config) ReleaseDeviceConfigValueSets() []string { + return c.config.productVariables.ReleaseDeviceConfigValueSets +} + // A DeviceConfig object represents the configuration for a particular device // being built. For now there will only be one of these, but in the future there // may be multiple devices being built. diff --git a/android/variable.go b/android/variable.go index bf66135b5..97171e7b9 100644 --- a/android/variable.go +++ b/android/variable.go @@ -473,6 +473,9 @@ type productVariables struct { ProductManufacturer string `json:",omitempty"` ProductBrand string `json:",omitempty"` BuildVersionTags []string `json:",omitempty"` + + ReleaseVersion string `json:",omitempty"` + ReleaseDeviceConfigValueSets []string `json:",omitempty"` } func boolPtr(v bool) *bool { diff --git a/device_config/Android.bp b/device_config/Android.bp new file mode 100644 index 000000000..360b3898a --- /dev/null +++ b/device_config/Android.bp @@ -0,0 +1,30 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +bootstrap_go_package { + name: "soong-device_config", + pkgPath: "android/soong/device_config", + deps: [ + "blueprint", + "blueprint-pathtools", + "sbox_proto", + "soong", + "soong-android", + "soong-bazel", + "soong-shared", + ], + srcs: [ + "device_config_definitions.go", + "device_config_values.go", + "device_config_value_set.go", + "init.go", + //"testing.go" + ], + /* + testSrcs: [ + "device_config_test.go", + ], + */ + pluginFor: ["soong_build"], +} diff --git a/device_config/device_config_definitions.go b/device_config/device_config_definitions.go new file mode 100644 index 000000000..c5657666c --- /dev/null +++ b/device_config/device_config_definitions.go @@ -0,0 +1,150 @@ +// 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 device_config + +import ( + "android/soong/android" + "fmt" + "github.com/google/blueprint" + "strings" +) + +type DefinitionsModule struct { + android.ModuleBase + android.DefaultableModuleBase + + // Properties for "device_config_definitions" + properties struct { + // aconfig files, relative to this Android.bp file + Srcs []string `android:"path"` + + // Release config flag namespace + Namespace string + + // Values from TARGET_RELEASE / RELEASE_DEVICE_CONFIG_VALUE_SETS + Values []string `blueprint:"mutated"` + } + + intermediatePath android.WritablePath + srcJarPath android.WritablePath +} + +func DefinitionsFactory() android.Module { + module := &DefinitionsModule{} + + android.InitAndroidModule(module) + android.InitDefaultableModule(module) + module.AddProperties(&module.properties) + // TODO: bp2build + //android.InitBazelModule(module) + + return module +} + +type implicitValuesTagType struct { + blueprint.BaseDependencyTag +} + +var implicitValuesTag = implicitValuesTagType{} + +func (module *DefinitionsModule) DepsMutator(ctx android.BottomUpMutatorContext) { + // Validate Properties + if len(module.properties.Srcs) == 0 { + ctx.PropertyErrorf("srcs", "missing source files") + return + } + if len(module.properties.Namespace) == 0 { + ctx.PropertyErrorf("namespace", "missing namespace property") + } + + // Add a dependency on the device_config_value_sets defined in + // RELEASE_DEVICE_CONFIG_VALUE_SETS, and add any device_config_values that + // match our namespace. + valuesFromConfig := ctx.Config().ReleaseDeviceConfigValueSets() + ctx.AddDependency(ctx.Module(), implicitValuesTag, valuesFromConfig...) +} + +func (module *DefinitionsModule) OutputFiles(tag string) (android.Paths, error) { + switch tag { + case ".srcjar": + return []android.Path{module.srcJarPath}, nil + case "": + // The default output of this module is the intermediates format, which is + // not installable and in a private format that no other rules can handle + // correctly. + return []android.Path{module.intermediatePath}, nil + default: + return nil, fmt.Errorf("unsupported device_config_definitions module reference tag %q", tag) + } +} + +func joinAndPrefix(prefix string, values []string) string { + var sb strings.Builder + for _, v := range values { + sb.WriteString(prefix) + sb.WriteString(v) + } + return sb.String() +} + +func (module *DefinitionsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Get the values that came from the global RELEASE_DEVICE_CONFIG_VALUE_SETS flag + ctx.VisitDirectDeps(func(dep android.Module) { + if !ctx.OtherModuleHasProvider(dep, valueSetProviderKey) { + // Other modules get injected as dependencies too, for example the license modules + return + } + depData := ctx.OtherModuleProvider(dep, valueSetProviderKey).(valueSetProviderData) + valuesFiles, ok := depData.AvailableNamespaces[module.properties.Namespace] + if ok { + for _, path := range valuesFiles { + module.properties.Values = append(module.properties.Values, path.String()) + } + } + }) + + // Intermediate format + inputFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs) + module.intermediatePath = android.PathForModuleOut(ctx, "intermediate.json") + ctx.Build(pctx, android.BuildParams{ + Rule: aconfigRule, + Inputs: inputFiles, + Output: module.intermediatePath, + Description: "device_config_definitions", + Args: map[string]string{ + "release_version": ctx.Config().ReleaseVersion(), + "namespace": module.properties.Namespace, + "values": joinAndPrefix(" --values ", module.properties.Values), + }, + }) + + // Generated java inside a srcjar + module.srcJarPath = android.PathForModuleGen(ctx, ctx.ModuleName()+".srcjar") + ctx.Build(pctx, android.BuildParams{ + Rule: srcJarRule, + Input: module.intermediatePath, + Output: module.srcJarPath, + Description: "device_config.srcjar", + }) + + // TODO: C++ + + // Phony target for debugging convenience + ctx.Build(pctx, android.BuildParams{ + Rule: blueprint.Phony, + Output: android.PathForPhony(ctx, ctx.ModuleName()), + Inputs: []android.Path{module.srcJarPath}, // TODO: C++ + }) +} diff --git a/device_config/device_config_test.go b/device_config/device_config_test.go new file mode 100644 index 000000000..91a06a781 --- /dev/null +++ b/device_config/device_config_test.go @@ -0,0 +1,61 @@ +// Copyright (C) 2019 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 device_config + +import ( + "os" + "testing" + + "android/soong/android" + "android/soong/java" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func test(t *testing.T, bp string) *android.TestResult { + t.Helper() + + mockFS := android.MockFS{ + "config.aconfig": nil, + } + + result := android.GroupFixturePreparers( + java.PrepareForTestWithJavaDefaultModules, + PrepareForTestWithSyspropBuildComponents, + // TODO: Consider values files, although maybe in its own test + // android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { + // variables.ReleaseConfigValuesBasePaths = ... + //}) + mockFS.AddToFixture(), + android.FixtureWithRootAndroidBp(bp), + ).RunTest(t) + + return result +} + +func TestOutputs(t *testing.T) { + /*result := */ test(t, ` + device_config { + name: "my_device_config", + srcs: ["config.aconfig"], + } + `) + + // TODO: Make sure it exports a .srcjar, which is used by java libraries + // TODO: Make sure it exports an intermediates file + // TODO: Make sure the intermediates file is propagated to the Android.mk file +} diff --git a/device_config/device_config_value_set.go b/device_config/device_config_value_set.go new file mode 100644 index 000000000..e406d20e0 --- /dev/null +++ b/device_config/device_config_value_set.go @@ -0,0 +1,92 @@ +// 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 device_config + +import ( + "android/soong/android" + "github.com/google/blueprint" +) + +// Properties for "device_config_value_set" +type ValueSetModule struct { + android.ModuleBase + android.DefaultableModuleBase + + properties struct { + // device_config_values modules + Values []string + } +} + +func ValueSetFactory() android.Module { + module := &ValueSetModule{} + + android.InitAndroidModule(module) + android.InitDefaultableModule(module) + module.AddProperties(&module.properties) + // TODO: bp2build + //android.InitBazelModule(module) + + return module +} + +// Dependency tag for values property +type valueSetType struct { + blueprint.BaseDependencyTag +} + +var valueSetTag = valueSetType{} + +// Provider published by device_config_value_set +type valueSetProviderData struct { + // The namespace of each of the + // (map of namespace --> device_config_module) + AvailableNamespaces map[string]android.Paths +} + +var valueSetProviderKey = blueprint.NewProvider(valueSetProviderData{}) + +func (module *ValueSetModule) DepsMutator(ctx android.BottomUpMutatorContext) { + deps := ctx.AddDependency(ctx.Module(), valueSetTag, module.properties.Values...) + for _, dep := range deps { + _, ok := dep.(*ValuesModule) + if !ok { + ctx.PropertyErrorf("values", "values must be a device_config_values module") + return + } + } +} + +func (module *ValueSetModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Accumulate the namespaces of the values modules listed, and set that as an + // valueSetProviderKey provider that device_config modules can read and use + // to append values to their aconfig actions. + namespaces := make(map[string]android.Paths) + ctx.VisitDirectDeps(func(dep android.Module) { + if !ctx.OtherModuleHasProvider(dep, valuesProviderKey) { + // Other modules get injected as dependencies too, for example the license modules + return + } + depData := ctx.OtherModuleProvider(dep, valuesProviderKey).(valuesProviderData) + + srcs := make([]android.Path, len(depData.Values)) + copy(srcs, depData.Values) + namespaces[depData.Namespace] = srcs + + }) + ctx.SetProvider(valueSetProviderKey, valueSetProviderData{ + AvailableNamespaces: namespaces, + }) +} diff --git a/device_config/device_config_values.go b/device_config/device_config_values.go new file mode 100644 index 000000000..110f12a1c --- /dev/null +++ b/device_config/device_config_values.go @@ -0,0 +1,70 @@ +// 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 device_config + +import ( + "android/soong/android" + "github.com/google/blueprint" +) + +// Properties for "device_config_value" +type ValuesModule struct { + android.ModuleBase + android.DefaultableModuleBase + + properties struct { + // aconfig files, relative to this Android.bp file + Srcs []string `android:"path"` + + // Release config flag namespace + Namespace string + } +} + +func ValuesFactory() android.Module { + module := &ValuesModule{} + + android.InitAndroidModule(module) + android.InitDefaultableModule(module) + module.AddProperties(&module.properties) + // TODO: bp2build + //android.InitBazelModule(module) + + return module +} + +// Provider published by device_config_value_set +type valuesProviderData struct { + // The namespace that this values module values + Namespace string + + // The values aconfig files, relative to the root of the tree + Values android.Paths +} + +var valuesProviderKey = blueprint.NewProvider(valuesProviderData{}) + +func (module *ValuesModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { + if len(module.properties.Namespace) == 0 { + ctx.PropertyErrorf("namespace", "missing namespace property") + } + + // Provide the our source files list to the device_config_value_set as a list of files + providerData := valuesProviderData{ + Namespace: module.properties.Namespace, + Values: android.PathsForModuleSrc(ctx, module.properties.Srcs), + } + ctx.SetProvider(valuesProviderKey, providerData) +} diff --git a/device_config/init.go b/device_config/init.go new file mode 100644 index 000000000..0a4645b2b --- /dev/null +++ b/device_config/init.go @@ -0,0 +1,70 @@ +// 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 device_config + +import ( + "android/soong/android" + "github.com/google/blueprint" +) + +var ( + pctx = android.NewPackageContext("android/soong/device_config") + + // For device_config_definitions: Generate cache file + // TODO: Restat + aconfigRule = pctx.AndroidStaticRule("aconfig", + blueprint.RuleParams{ + Command: `${aconfig} create-cache` + + ` --namespace ${namespace}` + + ` --declarations ${in}` + + ` ${values}` + + ` --cache ${out}.tmp` + + ` && ( if cmp -s ${out}.tmp ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`, + // ` --build-id ${release_version}` + + CommandDeps: []string{ + "${aconfig}", + }, + Restat: true, + }, "release_version", "namespace", "values") + + // For device_config_definitions: Generate java file + srcJarRule = pctx.AndroidStaticRule("aconfig_srcjar", + blueprint.RuleParams{ + Command: `rm -rf ${out}.tmp` + + ` && mkdir -p ${out}.tmp` + + ` && ${aconfig} create-java-lib` + + ` --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, + }) +) + +func init() { + registerBuildComponents(android.InitRegistrationContext) +} + +func registerBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("device_config_definitions", DefinitionsFactory) + ctx.RegisterModuleType("device_config_values", ValuesFactory) + ctx.RegisterModuleType("device_config_value_set", ValueSetFactory) + pctx.HostBinToolVariable("aconfig", "aconfig") + pctx.HostBinToolVariable("soong_zip", "soong_zip") +} diff --git a/device_config/testing.go b/device_config/testing.go new file mode 100644 index 000000000..f0544766b --- /dev/null +++ b/device_config/testing.go @@ -0,0 +1,19 @@ +// 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 device_config + +import "android/soong/android" + +var PrepareForTestWithSyspropBuildComponents = android.FixtureRegisterWithContext(registerBuildComponents)