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 aconfig 16 17import ( 18 "android/soong/android" 19 "path/filepath" 20 "slices" 21 "strconv" 22 "strings" 23 24 "github.com/google/blueprint" 25) 26 27type AconfigReleaseConfigValue struct { 28 ReleaseConfig string 29 Values []string `blueprint:"mutated"` 30} 31 32type DeclarationsModule struct { 33 android.ModuleBase 34 android.DefaultableModuleBase 35 blueprint.IncrementalModule 36 37 // Properties for "aconfig_declarations" 38 properties struct { 39 // aconfig files, relative to this Android.bp file 40 Srcs []string `android:"path"` 41 42 // Release config flag package 43 Package string 44 45 // Values for release configs / RELEASE_ACONFIG_VALUE_SETS 46 // The current release config is `ReleaseConfig: ""`, others 47 // are from RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS. 48 ReleaseConfigValues []AconfigReleaseConfigValue 49 50 // Container(system/system_ext/vendor/apex) that this module belongs to 51 Container string 52 53 // The flags will only be repackaged if this prop is true. 54 Exportable bool 55 } 56} 57 58func DeclarationsFactory() android.Module { 59 module := &DeclarationsModule{} 60 61 android.InitAndroidModule(module) 62 android.InitDefaultableModule(module) 63 module.AddProperties(&module.properties) 64 65 return module 66} 67 68type implicitValuesTagType struct { 69 blueprint.BaseDependencyTag 70 71 // The release config name for these values. 72 // Empty string for the actual current release config. 73 ReleaseConfig string 74} 75 76var implicitValuesTag = implicitValuesTagType{} 77 78func (module *DeclarationsModule) DepsMutator(ctx android.BottomUpMutatorContext) { 79 // Validate Properties 80 if len(module.properties.Srcs) == 0 { 81 ctx.PropertyErrorf("srcs", "missing source files") 82 return 83 } 84 if len(module.properties.Package) == 0 { 85 ctx.PropertyErrorf("package", "missing package property") 86 } 87 if len(module.properties.Container) == 0 { 88 ctx.PropertyErrorf("container", "missing container property") 89 } 90 91 // Add a dependency on the aconfig_value_sets defined in 92 // RELEASE_ACONFIG_VALUE_SETS, and add any aconfig_values that 93 // match our package. 94 valuesFromConfig := ctx.Config().ReleaseAconfigValueSets() 95 if len(valuesFromConfig) > 0 { 96 ctx.AddDependency(ctx.Module(), implicitValuesTag, valuesFromConfig...) 97 } 98 for rcName, valueSets := range ctx.Config().ReleaseAconfigExtraReleaseConfigsValueSets() { 99 if len(valueSets) > 0 { 100 ctx.AddDependency(ctx.Module(), implicitValuesTagType{ReleaseConfig: rcName}, valueSets...) 101 } 102 } 103} 104 105func joinAndPrefix(prefix string, values []string) string { 106 var sb strings.Builder 107 for _, v := range values { 108 sb.WriteString(prefix) 109 sb.WriteString(v) 110 } 111 return sb.String() 112} 113 114func optionalVariable(prefix string, value string) string { 115 var sb strings.Builder 116 if value != "" { 117 sb.WriteString(prefix) 118 sb.WriteString(value) 119 } 120 return sb.String() 121} 122 123// Assemble the actual filename. 124// If `rcName` is not empty, then insert "-{rcName}" into the path before the 125// file extension. 126func assembleFileName(rcName, path string) string { 127 if rcName == "" { 128 return path 129 } 130 dir, file := filepath.Split(path) 131 rcName = "-" + rcName 132 ext := filepath.Ext(file) 133 base := file[:len(file)-len(ext)] 134 return dir + base + rcName + ext 135} 136 137func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 138 // Determine which release configs we are processing. 139 // 140 // We always process the current release config (empty string). 141 // We may have been told to also create artifacts for some others. 142 configs := append([]string{""}, ctx.Config().ReleaseAconfigExtraReleaseConfigs()...) 143 slices.Sort(configs) 144 145 values := make(map[string][]string) 146 valuesFiles := make(map[string][]android.Path, 0) 147 providerData := android.AconfigReleaseDeclarationsProviderData{} 148 ctx.VisitDirectDeps(func(dep android.Module) { 149 if depData, ok := android.OtherModuleProvider(ctx, dep, valueSetProviderKey); ok { 150 depTag := ctx.OtherModuleDependencyTag(dep) 151 for _, config := range configs { 152 tag := implicitValuesTagType{ReleaseConfig: config} 153 if depTag == tag { 154 paths, ok := depData.AvailablePackages[module.properties.Package] 155 if ok { 156 valuesFiles[config] = append(valuesFiles[config], paths...) 157 for _, path := range paths { 158 values[config] = append(values[config], path.String()) 159 } 160 } 161 } 162 } 163 } 164 }) 165 for _, config := range configs { 166 module.properties.ReleaseConfigValues = append(module.properties.ReleaseConfigValues, AconfigReleaseConfigValue{ 167 ReleaseConfig: config, 168 Values: values[config], 169 }) 170 171 // Intermediate format 172 declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs) 173 intermediateCacheFilePath := android.PathForModuleOut(ctx, assembleFileName(config, "intermediate.pb")) 174 var defaultPermission string 175 defaultPermission = ctx.Config().ReleaseAconfigFlagDefaultPermission() 176 if config != "" { 177 if confPerm, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_" + config); ok { 178 defaultPermission = confPerm 179 } 180 } 181 var allowReadWrite bool 182 if requireAllReadOnly, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_REQUIRE_ALL_READ_ONLY"); ok { 183 // The build flag (RELEASE_ACONFIG_REQUIRE_ALL_READ_ONLY) is the negation of the aconfig flag 184 // (allow-read-write) for historical reasons. 185 // Bool build flags are always "" for false, and generally "true" for true. 186 allowReadWrite = requireAllReadOnly == "" 187 } 188 inputFiles := make([]android.Path, len(declarationFiles)) 189 copy(inputFiles, declarationFiles) 190 inputFiles = append(inputFiles, valuesFiles[config]...) 191 args := map[string]string{ 192 "release_version": ctx.Config().ReleaseVersion(), 193 "package": module.properties.Package, 194 "declarations": android.JoinPathsWithPrefix(declarationFiles, "--declarations "), 195 "values": joinAndPrefix(" --values ", values[config]), 196 "default-permission": optionalVariable(" --default-permission ", defaultPermission), 197 "allow-read-write": optionalVariable(" --allow-read-write ", strconv.FormatBool(allowReadWrite)), 198 } 199 if len(module.properties.Container) > 0 { 200 args["container"] = "--container " + module.properties.Container 201 } 202 ctx.Build(pctx, android.BuildParams{ 203 Rule: aconfigRule, 204 Output: intermediateCacheFilePath, 205 Inputs: inputFiles, 206 Description: "aconfig_declarations", 207 Args: args, 208 }) 209 210 intermediateDumpFilePath := android.PathForModuleOut(ctx, assembleFileName(config, "intermediate.txt")) 211 ctx.Build(pctx, android.BuildParams{ 212 Rule: aconfigTextRule, 213 Output: intermediateDumpFilePath, 214 Inputs: android.Paths{intermediateCacheFilePath}, 215 Description: "aconfig_text", 216 }) 217 218 providerData[config] = android.AconfigDeclarationsProviderData{ 219 Package: module.properties.Package, 220 Container: module.properties.Container, 221 Exportable: module.properties.Exportable, 222 IntermediateCacheOutputPath: intermediateCacheFilePath, 223 IntermediateDumpOutputPath: intermediateDumpFilePath, 224 } 225 } 226 android.SetProvider(ctx, android.AconfigDeclarationsProviderKey, providerData[""]) 227 android.SetProvider(ctx, android.AconfigReleaseDeclarationsProviderKey, providerData) 228} 229 230var _ blueprint.Incremental = &DeclarationsModule{} 231