1// Copyright 2023 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "fmt" 19 "io" 20 "maps" 21 "reflect" 22 23 "github.com/google/blueprint" 24) 25 26var ( 27 mergeAconfigFilesRule = pctx.AndroidStaticRule("mergeAconfigFilesRule", 28 blueprint.RuleParams{ 29 Command: `${aconfig} dump --dedup --format protobuf --out $out $flags`, 30 CommandDeps: []string{"${aconfig}"}, 31 }, "flags") 32 _ = pctx.HostBinToolVariable("aconfig", "aconfig") 33) 34 35// Provider published by aconfig_value_set 36type AconfigDeclarationsProviderData struct { 37 Package string 38 Container string 39 Exportable bool 40 IntermediateCacheOutputPath WritablePath 41 IntermediateDumpOutputPath WritablePath 42} 43 44var AconfigDeclarationsProviderKey = blueprint.NewProvider[AconfigDeclarationsProviderData]() 45 46type AconfigReleaseDeclarationsProviderData map[string]AconfigDeclarationsProviderData 47 48var AconfigReleaseDeclarationsProviderKey = blueprint.NewProvider[AconfigReleaseDeclarationsProviderData]() 49 50type ModeInfo struct { 51 Container string 52 Mode string 53} 54type CodegenInfo struct { 55 // AconfigDeclarations is the name of the aconfig_declarations modules that 56 // the codegen module is associated with 57 AconfigDeclarations []string 58 59 // Paths to the cache files of the associated aconfig_declaration modules 60 IntermediateCacheOutputPaths Paths 61 62 // Paths to the srcjar files generated from the java_aconfig_library modules 63 Srcjars Paths 64 65 ModeInfos map[string]ModeInfo 66} 67 68var CodegenInfoProvider = blueprint.NewProvider[CodegenInfo]() 69 70func propagateModeInfos(ctx ModuleContext, module Module, to, from map[string]ModeInfo) { 71 if len(from) > 0 { 72 depTag := ctx.OtherModuleDependencyTag(module) 73 if tag, ok := depTag.(PropagateAconfigValidationDependencyTag); ok && tag.PropagateAconfigValidation() { 74 maps.Copy(to, from) 75 } 76 } 77} 78 79type aconfigPropagatingDeclarationsInfo struct { 80 AconfigFiles map[string]Paths 81 ModeInfos map[string]ModeInfo 82} 83 84var AconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]() 85 86func VerifyAconfigBuildMode(ctx ModuleContext, container string, module blueprint.Module, asError bool) { 87 if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok { 88 for k, v := range dep.ModeInfos { 89 msg := fmt.Sprintf("%s/%s depends on %s/%s/%s across containers\n", 90 module.Name(), container, k, v.Container, v.Mode) 91 if v.Container != container && v.Mode != "exported" && v.Mode != "force-read-only" { 92 if asError { 93 ctx.ModuleErrorf(msg) 94 } else { 95 fmt.Printf("WARNING: " + msg) 96 } 97 } else { 98 if !asError { 99 fmt.Printf("PASSED: " + msg) 100 } 101 } 102 } 103 } 104} 105 106func aconfigUpdateAndroidBuildActions(ctx ModuleContext) { 107 mergedAconfigFiles := make(map[string]Paths) 108 mergedModeInfos := make(map[string]ModeInfo) 109 110 ctx.VisitDirectDepsProxy(func(module ModuleProxy) { 111 if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 { 112 maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos) 113 } 114 115 // If any of our dependencies have aconfig declarations (directly or propagated), then merge those and provide them. 116 if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok { 117 mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath) 118 } 119 // If we were generating on-device artifacts for other release configs, we would need to add code here to propagate 120 // those artifacts as well. See also b/298444886. 121 if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok { 122 for container, v := range dep.AconfigFiles { 123 mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...) 124 } 125 propagateModeInfos(ctx, module, mergedModeInfos, dep.ModeInfos) 126 } 127 }) 128 // We only need to set the provider if we have aconfig files. 129 if len(mergedAconfigFiles) > 0 { 130 for _, container := range SortedKeys(mergedAconfigFiles) { 131 aconfigFiles := mergedAconfigFiles[container] 132 mergedAconfigFiles[container] = mergeAconfigFiles(ctx, container, aconfigFiles, true) 133 } 134 135 SetProvider(ctx, AconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{ 136 AconfigFiles: mergedAconfigFiles, 137 ModeInfos: mergedModeInfos, 138 }) 139 ctx.setAconfigPaths(getAconfigFilePaths(ctx.Module().base(), mergedAconfigFiles)) 140 } 141} 142 143func aconfigUpdateAndroidMkData(ctx fillInEntriesContext, mod Module, data *AndroidMkData) { 144 info, ok := OtherModuleProvider(ctx, mod, AconfigPropagatingProviderKey) 145 // If there is no aconfigPropagatingProvider, or there are no AconfigFiles, then we are done. 146 if !ok || len(info.AconfigFiles) == 0 { 147 return 148 } 149 data.Extra = append(data.Extra, func(w io.Writer, outputFile Path) { 150 AndroidMkEmitAssignList(w, "LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles).Strings()) 151 }) 152 // If there is a Custom writer, it needs to support this provider. 153 if data.Custom != nil { 154 switch reflect.TypeOf(mod).String() { 155 case "*aidl.aidlApi": // writes non-custom before adding .phony 156 case "*android_sdk.sdkRepoHost": // doesn't go through base_rules 157 case "*apex.apexBundle": // aconfig_file properties written 158 case "*bpf.bpf": // properties written (both for module and objs) 159 case "*genrule.Module": // writes non-custom before adding .phony 160 case "*java.SystemModules": // doesn't go through base_rules 161 case "*phony.phony": // properties written 162 case "*phony.PhonyRule": // writes phony deps and acts like `.PHONY` 163 case "*sysprop.syspropLibrary": // properties written 164 default: 165 panic(fmt.Errorf("custom make rules do not handle aconfig files for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), mod)) 166 } 167 } 168} 169 170func aconfigUpdateAndroidMkEntries(ctx fillInEntriesContext, mod Module, entries *[]AndroidMkEntries) { 171 // If there are no entries, then we can ignore this module, even if it has aconfig files. 172 if len(*entries) == 0 { 173 return 174 } 175 info, ok := OtherModuleProvider(ctx, mod, AconfigPropagatingProviderKey) 176 if !ok || len(info.AconfigFiles) == 0 { 177 return 178 } 179 // All of the files in the module potentially depend on the aconfig flag values. 180 for idx, _ := range *entries { 181 (*entries)[idx].ExtraEntries = append((*entries)[idx].ExtraEntries, 182 func(ctx AndroidMkExtraEntriesContext, entries *AndroidMkEntries) { 183 entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles)) 184 }, 185 ) 186 187 } 188} 189 190func aconfigUpdateAndroidMkInfos(ctx fillInEntriesContext, mod Module, infos *AndroidMkProviderInfo) { 191 info, ok := OtherModuleProvider(ctx, mod, AconfigPropagatingProviderKey) 192 if !ok || len(info.AconfigFiles) == 0 { 193 return 194 } 195 // All of the files in the module potentially depend on the aconfig flag values. 196 infos.PrimaryInfo.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles)) 197 if len(infos.ExtraInfo) > 0 { 198 for _, ei := range (*infos).ExtraInfo { 199 ei.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles)) 200 } 201 } 202} 203 204func mergeAconfigFiles(ctx ModuleContext, container string, inputs Paths, generateRule bool) Paths { 205 inputs = SortedUniquePaths(inputs) 206 if len(inputs) == 1 { 207 return Paths{inputs[0]} 208 } 209 210 output := PathForModuleOut(ctx, container, "aconfig_merged.pb") 211 212 if generateRule { 213 ctx.Build(pctx, BuildParams{ 214 Rule: mergeAconfigFilesRule, 215 Description: "merge aconfig files", 216 Inputs: inputs, 217 Output: output, 218 Args: map[string]string{ 219 "flags": JoinWithPrefix(inputs.Strings(), "--cache "), 220 }, 221 }) 222 } 223 224 return Paths{output} 225} 226 227func getAconfigFilePaths(m *ModuleBase, aconfigFiles map[string]Paths) (paths Paths) { 228 // TODO(b/311155208): The default container here should be system. 229 container := "system" 230 231 if m.SocSpecific() { 232 container = "vendor" 233 } else if m.ProductSpecific() { 234 container = "product" 235 } else if m.SystemExtSpecific() { 236 // system_ext and system partitions should be treated as one container 237 container = "system" 238 } 239 240 paths = append(paths, aconfigFiles[container]...) 241 if container == "system" { 242 // TODO(b/311155208): Once the default container is system, we can drop this. 243 paths = append(paths, aconfigFiles[""]...) 244 } 245 if container != "system" { 246 if len(aconfigFiles[container]) == 0 && len(aconfigFiles[""]) > 0 { 247 // TODO(b/308625757): Either we guessed the container wrong, or the flag is misdeclared. 248 // For now, just include the system (aka "") container if we get here. 249 //fmt.Printf("container_mismatch: module=%v container=%v files=%v\n", m, container, aconfigFiles) 250 } 251 paths = append(paths, aconfigFiles[""]...) 252 } 253 return 254} 255