| // 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]() |
| |
| 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) |
| } |
| } |
| } |
| |
| 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) |
| } |
| }) |
| // 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, |
| }) |
| ctx.Module().base().aconfigFilePaths = getAconfigFilePaths(ctx.Module().base(), mergedAconfigFiles) |
| } |
| } |
| |
| 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) { |
| entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), 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 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 |
| } |