platform_build_soong/android/aconfig_providers.go
Spandan Das 87f5ee4cb6 Fix some non-determinism in aconfigMerge operation
Sorted iteration ensures that `mergeAconfigFiles` generates the ninja
rules in a deterministic fashion.

Test: mv out/soong/build.ninja; regenerate; compare; bit-identical
after this change

Change-Id: Ic9bee14374fa1eb7adee670eaed42ad6ffdd9010
2024-03-28 21:26:49 +00:00

288 lines
10 KiB
Go

// 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 android
import (
"fmt"
"io"
"maps"
"reflect"
"github.com/google/blueprint"
)
var (
mergeAconfigFilesRule = pctx.AndroidStaticRule("mergeAconfigFilesRule",
blueprint.RuleParams{
Command: `${aconfig} dump --dedup --format protobuf --out $out $flags`,
CommandDeps: []string{"${aconfig}"},
}, "flags")
_ = pctx.HostBinToolVariable("aconfig", "aconfig")
)
// Provider published by aconfig_value_set
type AconfigDeclarationsProviderData struct {
Package string
Container string
Exportable bool
IntermediateCacheOutputPath WritablePath
IntermediateDumpOutputPath WritablePath
}
var AconfigDeclarationsProviderKey = blueprint.NewProvider[AconfigDeclarationsProviderData]()
// This is used to collect the aconfig declarations info on the transitive closure,
// the data is keyed on the container.
type AconfigTransitiveDeclarationsInfo struct {
AconfigFiles map[string]Paths
}
var AconfigTransitiveDeclarationsInfoProvider = blueprint.NewProvider[AconfigTransitiveDeclarationsInfo]()
type ModeInfo struct {
Container string
Mode string
}
type CodegenInfo struct {
// AconfigDeclarations is the name of the aconfig_declarations modules that
// the codegen module is associated with
AconfigDeclarations []string
// Paths to the cache files of the associated aconfig_declaration modules
IntermediateCacheOutputPaths Paths
// Paths to the srcjar files generated from the java_aconfig_library modules
Srcjars Paths
ModeInfos map[string]ModeInfo
}
var CodegenInfoProvider = blueprint.NewProvider[CodegenInfo]()
func propagateModeInfos(ctx ModuleContext, module Module, to, from map[string]ModeInfo) {
if len(from) > 0 {
depTag := ctx.OtherModuleDependencyTag(module)
if tag, ok := depTag.(PropagateAconfigValidationDependencyTag); ok && tag.PropagateAconfigValidation() {
maps.Copy(to, from)
}
}
}
// CollectDependencyAconfigFiles is used by some module types to provide finer dependency graphing than
// we can do in ModuleBase.
func CollectDependencyAconfigFiles(ctx ModuleContext, mergedAconfigFiles *map[string]Paths) {
if *mergedAconfigFiles == nil {
*mergedAconfigFiles = make(map[string]Paths)
}
ctx.VisitDirectDepsIgnoreBlueprint(func(module Module) {
if dep, _ := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); dep.IntermediateCacheOutputPath != nil {
(*mergedAconfigFiles)[dep.Container] = append((*mergedAconfigFiles)[dep.Container], dep.IntermediateCacheOutputPath)
return
}
if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
for container, v := range dep.AconfigFiles {
(*mergedAconfigFiles)[container] = append((*mergedAconfigFiles)[container], v...)
}
}
// We process these last, so that they determine the final value, eliminating any duplicates that we picked up
// from UpdateAndroidBuildActions.
if dep, ok := OtherModuleProvider(ctx, module, AconfigTransitiveDeclarationsInfoProvider); ok {
for container, v := range dep.AconfigFiles {
(*mergedAconfigFiles)[container] = append((*mergedAconfigFiles)[container], v...)
}
}
})
for _, container := range SortedKeys(*mergedAconfigFiles) {
aconfigFiles := (*mergedAconfigFiles)[container]
(*mergedAconfigFiles)[container] = mergeAconfigFiles(ctx, container, aconfigFiles, false)
}
SetProvider(ctx, AconfigTransitiveDeclarationsInfoProvider, AconfigTransitiveDeclarationsInfo{
AconfigFiles: *mergedAconfigFiles,
})
}
func SetAconfigFileMkEntries(m *ModuleBase, entries *AndroidMkEntries, aconfigFiles map[string]Paths) {
setAconfigFileMkEntries(m, entries, aconfigFiles)
}
type aconfigPropagatingDeclarationsInfo struct {
AconfigFiles map[string]Paths
ModeInfos map[string]ModeInfo
}
var aconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]()
func VerifyAconfigBuildMode(ctx ModuleContext, container string, module blueprint.Module, asError bool) {
if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
for k, v := range dep.ModeInfos {
msg := fmt.Sprintf("%s/%s depends on %s/%s/%s across containers\n",
module.Name(), container, k, v.Container, v.Mode)
if v.Container != container && v.Mode != "exported" && v.Mode != "force-read-only" {
if asError {
ctx.ModuleErrorf(msg)
} else {
fmt.Printf("WARNING: " + msg)
}
} else {
if !asError {
fmt.Printf("PASSED: " + msg)
}
}
}
}
}
func aconfigUpdateAndroidBuildActions(ctx ModuleContext) {
mergedAconfigFiles := make(map[string]Paths)
mergedModeInfos := make(map[string]ModeInfo)
ctx.VisitDirectDepsIgnoreBlueprint(func(module Module) {
if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 {
maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos)
}
// If any of our dependencies have aconfig declarations (directly or propagated), then merge those and provide them.
if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok {
mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath)
}
if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
for container, v := range dep.AconfigFiles {
mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
}
propagateModeInfos(ctx, module, mergedModeInfos, dep.ModeInfos)
}
if dep, ok := OtherModuleProvider(ctx, module, AconfigTransitiveDeclarationsInfoProvider); ok {
for container, v := range dep.AconfigFiles {
mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
}
}
})
// We only need to set the provider if we have aconfig files.
if len(mergedAconfigFiles) > 0 {
for _, container := range SortedKeys(mergedAconfigFiles) {
aconfigFiles := mergedAconfigFiles[container]
mergedAconfigFiles[container] = mergeAconfigFiles(ctx, container, aconfigFiles, true)
}
SetProvider(ctx, aconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{
AconfigFiles: mergedAconfigFiles,
ModeInfos: mergedModeInfos,
})
}
}
func aconfigUpdateAndroidMkData(ctx fillInEntriesContext, mod Module, data *AndroidMkData) {
info, ok := SingletonModuleProvider(ctx, mod, aconfigPropagatingProviderKey)
// If there is no aconfigPropagatingProvider, or there are no AconfigFiles, then we are done.
if !ok || len(info.AconfigFiles) == 0 {
return
}
data.Extra = append(data.Extra, func(w io.Writer, outputFile Path) {
AndroidMkEmitAssignList(w, "LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles).Strings())
})
// If there is a Custom writer, it needs to support this provider.
if data.Custom != nil {
switch reflect.TypeOf(mod).String() {
case "*aidl.aidlApi": // writes non-custom before adding .phony
case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
case "*apex.apexBundle": // aconfig_file properties written
case "*bpf.bpf": // properties written (both for module and objs)
case "*genrule.Module": // writes non-custom before adding .phony
case "*java.SystemModules": // doesn't go through base_rules
case "*phony.phony": // properties written
case "*phony.PhonyRule": // writes phony deps and acts like `.PHONY`
case "*sysprop.syspropLibrary": // properties written
default:
panic(fmt.Errorf("custom make rules do not handle aconfig files for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), mod))
}
}
}
func aconfigUpdateAndroidMkEntries(ctx fillInEntriesContext, mod Module, entries *[]AndroidMkEntries) {
// If there are no entries, then we can ignore this module, even if it has aconfig files.
if len(*entries) == 0 {
return
}
info, ok := SingletonModuleProvider(ctx, mod, aconfigPropagatingProviderKey)
if !ok || len(info.AconfigFiles) == 0 {
return
}
// All of the files in the module potentially depend on the aconfig flag values.
for idx, _ := range *entries {
(*entries)[idx].ExtraEntries = append((*entries)[idx].ExtraEntries,
func(ctx AndroidMkExtraEntriesContext, entries *AndroidMkEntries) {
setAconfigFileMkEntries(mod.base(), entries, info.AconfigFiles)
},
)
}
}
func mergeAconfigFiles(ctx ModuleContext, container string, inputs Paths, generateRule bool) Paths {
inputs = SortedUniquePaths(inputs)
if len(inputs) == 1 {
return Paths{inputs[0]}
}
output := PathForModuleOut(ctx, container, "aconfig_merged.pb")
if generateRule {
ctx.Build(pctx, BuildParams{
Rule: mergeAconfigFilesRule,
Description: "merge aconfig files",
Inputs: inputs,
Output: output,
Args: map[string]string{
"flags": JoinWithPrefix(inputs.Strings(), "--cache "),
},
})
}
return Paths{output}
}
func setAconfigFileMkEntries(m *ModuleBase, entries *AndroidMkEntries, aconfigFiles map[string]Paths) {
entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(m, aconfigFiles))
}
func getAconfigFilePaths(m *ModuleBase, aconfigFiles map[string]Paths) (paths Paths) {
// TODO(b/311155208): The default container here should be system.
container := "system"
if m.SocSpecific() {
container = "vendor"
} else if m.ProductSpecific() {
container = "product"
} else if m.SystemExtSpecific() {
container = "system_ext"
}
paths = append(paths, aconfigFiles[container]...)
if container == "system" {
// TODO(b/311155208): Once the default container is system, we can drop this.
paths = append(paths, aconfigFiles[""]...)
}
if container != "system" {
if len(aconfigFiles[container]) == 0 && len(aconfigFiles[""]) > 0 {
// TODO(b/308625757): Either we guessed the container wrong, or the flag is misdeclared.
// For now, just include the system (aka "") container if we get here.
//fmt.Printf("container_mismatch: module=%v container=%v files=%v\n", m, container, aconfigFiles)
}
paths = append(paths, aconfigFiles[""]...)
}
return
}