1// Copyright 2024 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 release_config_lib 16 17import ( 18 "cmp" 19 "fmt" 20 "os" 21 "path/filepath" 22 "regexp" 23 "slices" 24 "strings" 25 26 rc_proto "android/soong/cmd/release_config/release_config_proto" 27 28 "google.golang.org/protobuf/proto" 29) 30 31// One directory's contribution to the a release config. 32type ReleaseConfigContribution struct { 33 // Path of the file providing this config contribution. 34 path string 35 36 // The index of the config directory where this release config 37 // contribution was declared. 38 // Flag values cannot be set in a location with a lower index. 39 DeclarationIndex int 40 41 // Protobufs relevant to the config. 42 proto rc_proto.ReleaseConfig 43 44 FlagValues []*FlagValue 45} 46 47// A generated release config. 48type ReleaseConfig struct { 49 // the Name of the release config 50 Name string 51 52 // The index of the config directory where this release config was 53 // first declared. 54 // Flag values cannot be set in a location with a lower index. 55 DeclarationIndex int 56 57 // What contributes to this config. 58 Contributions []*ReleaseConfigContribution 59 60 // Aliases for this release 61 OtherNames []string 62 63 // The names of release configs that we inherit 64 InheritNames []string 65 66 // True if this release config only allows inheritance and aconfig flag 67 // overrides. Build flag value overrides are an error. 68 AconfigFlagsOnly bool 69 70 // True if this release config is not allowed as TARGET_RELEASE. 71 DisallowLunchUse bool 72 73 // Unmarshalled flag artifacts 74 FlagArtifacts FlagArtifacts 75 76 // The files used by this release config 77 FilesUsedMap map[string]bool 78 79 // Generated release config 80 ReleaseConfigArtifact *rc_proto.ReleaseConfigArtifact 81 82 // We have begun compiling this release config. 83 compileInProgress bool 84 85 // Partitioned artifacts for {partition}/etc/build_flags.json 86 PartitionBuildFlags map[string]*rc_proto.FlagArtifacts 87 88 // Prior stage(s) for flag advancement (during development). 89 // Once a flag has met criteria in a prior stage, it can advance to this one. 90 PriorStagesMap map[string]bool 91 92 // What type of release config is this? This should never be 93 // ReleaseConfigType_CONFIG_TYPE_UNSPECIFIED. 94 ReleaseConfigType rc_proto.ReleaseConfigType 95} 96 97// If true, this is a proper release config that can be used in "lunch". 98func (config *ReleaseConfig) isConfigListable() bool { 99 // Do not list disallowed release configs. 100 if config.DisallowLunchUse { 101 return false 102 } 103 // Logic based on ReleaseConfigType. 104 switch config.ReleaseConfigType { 105 case rc_proto.ReleaseConfigType_RELEASE_CONFIG: 106 return true 107 } 108 109 return false 110} 111 112// If true, this ReleaseConfigType may only inherit from a ReleaseConfig of the 113// same ReleaseConfigType. 114var ReleaseConfigInheritanceDenyMap = map[rc_proto.ReleaseConfigType]bool{ 115 rc_proto.ReleaseConfigType_BUILD_VARIANT: true, 116} 117 118func ReleaseConfigFactory(name string, index int) (c *ReleaseConfig) { 119 return &ReleaseConfig{ 120 Name: name, 121 DeclarationIndex: index, 122 FilesUsedMap: make(map[string]bool), 123 PriorStagesMap: make(map[string]bool), 124 } 125} 126 127func (config *ReleaseConfig) InheritConfig(iConfig *ReleaseConfig) error { 128 if config.ReleaseConfigType != iConfig.ReleaseConfigType && ReleaseConfigInheritanceDenyMap[config.ReleaseConfigType] { 129 return fmt.Errorf("Release config %s (type '%s') cannot inherit from %s (type '%s')", 130 config.Name, config.ReleaseConfigType, iConfig.Name, iConfig.ReleaseConfigType) 131 } 132 for f := range iConfig.FilesUsedMap { 133 config.FilesUsedMap[f] = true 134 } 135 for _, fa := range iConfig.FlagArtifacts { 136 name := *fa.FlagDeclaration.Name 137 myFa, ok := config.FlagArtifacts[name] 138 if !ok { 139 return fmt.Errorf("Could not inherit flag %s from %s", name, iConfig.Name) 140 } 141 if fa.Redacted { 142 myFa.Redact() 143 } 144 if name == "RELEASE_ACONFIG_VALUE_SETS" { 145 // If there is a value assigned, add the trace. 146 if len(fa.Value.GetStringValue()) > 0 { 147 myFa.Traces = append(myFa.Traces, fa.Traces...) 148 myFa.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{ 149 myFa.Value.GetStringValue() + " " + fa.Value.GetStringValue()}} 150 } 151 } else if len(fa.Traces) > 1 { 152 // A value was assigned. Set our value. 153 myFa.Traces = append(myFa.Traces, fa.Traces[1:]...) 154 myFa.Value = fa.Value 155 } 156 } 157 return nil 158} 159 160func (config *ReleaseConfig) GetSortedFileList() []string { 161 return SortedMapKeys(config.FilesUsedMap) 162} 163 164func (config *ReleaseConfig) GenerateReleaseConfig(configs *ReleaseConfigs) error { 165 if config.ReleaseConfigArtifact != nil { 166 return nil 167 } 168 if config.compileInProgress { 169 return fmt.Errorf("Loop detected for release config %s", config.Name) 170 } 171 config.compileInProgress = true 172 isRoot := config.Name == "root" 173 174 // Is this a build-prefix release config, such as 'ap3a'? 175 isBuildPrefix, err := regexp.MatchString("^[a-z][a-z][0-9][0-9a-z]$", config.Name) 176 if err != nil { 177 return err 178 } 179 // Start with only the flag declarations. 180 config.FlagArtifacts = configs.FlagArtifacts.Clone() 181 releaseAconfigValueSets := config.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"] 182 releasePlatformVersion := config.FlagArtifacts["RELEASE_PLATFORM_VERSION"] 183 184 // Generate any configs we need to inherit. This will detect loops in 185 // the config. 186 contributionsToApply := []*ReleaseConfigContribution{} 187 myInherits := []string{} 188 myInheritsSet := make(map[string]bool) 189 if config.ReleaseConfigType == rc_proto.ReleaseConfigType_RELEASE_CONFIG { 190 if _, err = configs.GetReleaseConfigStrict("root"); err == nil { 191 config.InheritNames = append([]string{"root"}, config.InheritNames...) 192 } 193 } 194 for _, inherit := range config.InheritNames { 195 if _, ok := myInheritsSet[inherit]; ok { 196 continue 197 } 198 if isBuildPrefix && configs.Aliases[inherit] != nil { 199 return fmt.Errorf("%s cannot inherit from alias %s", config.Name, inherit) 200 } 201 myInherits = append(myInherits, inherit) 202 myInheritsSet[inherit] = true 203 // TODO: there are some configs that rely on vgsbr being 204 // present on branches where it isn't. Once the broken configs 205 // are fixed, we can be more strict. In the meantime, they 206 // will wind up inheriting `trunk_stable` instead of the 207 // non-existent (alias) that they reference today. Once fixed, 208 // this becomes: 209 // iConfig, err := configs.GetReleaseConfigStrict(inherit) 210 iConfig, err := configs.GetReleaseConfig(inherit) 211 if err != nil { 212 return err 213 } 214 err = iConfig.GenerateReleaseConfig(configs) 215 if err != nil { 216 return err 217 } 218 err = config.InheritConfig(iConfig) 219 if err != nil { 220 return err 221 } 222 } 223 224 // If we inherited nothing, then we need to mark the global files as used for this 225 // config. If we inherited, then we already marked them as part of inheritance. 226 if len(config.InheritNames) == 0 { 227 for f := range configs.FilesUsedMap { 228 config.FilesUsedMap[f] = true 229 } 230 } 231 232 contributionsToApply = append(contributionsToApply, config.Contributions...) 233 234 workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL) 235 myDirsMap := make(map[int]bool) 236 myValueDirsMap := make(map[int]bool) 237 if isBuildPrefix && releasePlatformVersion != nil { 238 if MarshalValue(releasePlatformVersion.Value) != strings.ToUpper(config.Name) { 239 value := FlagValue{ 240 path: config.Contributions[0].path, 241 proto: rc_proto.FlagValue{ 242 Name: releasePlatformVersion.FlagDeclaration.Name, 243 Value: UnmarshalValue(strings.ToUpper(config.Name)), 244 }, 245 } 246 if err := releasePlatformVersion.UpdateValue(value); err != nil { 247 return err 248 } 249 } 250 } 251 for _, contrib := range contributionsToApply { 252 contribAconfigValueSets := []string{} 253 // Gather the aconfig_value_sets from this contribution, allowing duplicates for simplicity. 254 for _, v := range contrib.proto.AconfigValueSets { 255 contribAconfigValueSets = append(contribAconfigValueSets, v) 256 } 257 contribAconfigValueSetsString := strings.Join(contribAconfigValueSets, " ") 258 releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{ 259 releaseAconfigValueSets.Value.GetStringValue() + " " + contribAconfigValueSetsString}} 260 releaseAconfigValueSets.Traces = append( 261 releaseAconfigValueSets.Traces, 262 &rc_proto.Tracepoint{ 263 Source: proto.String(contrib.path), 264 Value: &rc_proto.Value{Val: &rc_proto.Value_StringValue{contribAconfigValueSetsString}}, 265 }) 266 267 for _, priorStage := range contrib.proto.PriorStages { 268 config.PriorStagesMap[priorStage] = true 269 } 270 myDirsMap[contrib.DeclarationIndex] = true 271 // This path *could* provide a value for this release config. 272 myValueDirsMap[contrib.DeclarationIndex] = true 273 if config.AconfigFlagsOnly { 274 // AconfigFlagsOnly allows very very few build flag values, all of them are part of aconfig flags. 275 allowedFlags := map[string]bool{ 276 "RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS": true, 277 } 278 for _, fv := range contrib.FlagValues { 279 if !allowedFlags[*fv.proto.Name] { 280 return fmt.Errorf("%s does not allow build flag overrides", config.Name) 281 } 282 } 283 } 284 for _, value := range contrib.FlagValues { 285 name := *value.proto.Name 286 fa, ok := config.FlagArtifacts[name] 287 if !ok { 288 return fmt.Errorf("Setting value for undefined flag %s in %s\n", name, value.path) 289 } 290 // Record that flag declarations from fa.DeclarationIndex were included in this release config. 291 myDirsMap[fa.DeclarationIndex] = true 292 // Do not set myValueDirsMap, since it just records that we *could* provide values here. 293 if fa.DeclarationIndex > contrib.DeclarationIndex { 294 // Setting location is to the left of declaration. 295 return fmt.Errorf("Setting value for flag %s (declared in %s) not allowed in %s\n", 296 name, filepath.Dir(configs.ReleaseConfigMaps[fa.DeclarationIndex].path), value.path) 297 } 298 if isRoot && *fa.FlagDeclaration.Workflow != workflowManual { 299 // The "root" release config can only contain workflow: MANUAL flags. 300 return fmt.Errorf("Setting value for non-MANUAL flag %s is not allowed in %s", name, value.path) 301 } 302 if err := fa.UpdateValue(*value); err != nil { 303 return err 304 } 305 } 306 } 307 308 if config.ReleaseConfigType == rc_proto.ReleaseConfigType_RELEASE_CONFIG { 309 inheritBuildVariant := func() error { 310 build_variant := os.Getenv("TARGET_BUILD_VARIANT") 311 if build_variant == "" || config.Name == build_variant { 312 return nil 313 } 314 variant, err := configs.GetReleaseConfigStrict(build_variant) 315 if err != nil { 316 // Failure to find the build-variant release config is 317 // not an error. 318 return nil 319 } 320 if variant.ReleaseConfigType != rc_proto.ReleaseConfigType_BUILD_VARIANT { 321 return nil 322 } 323 if err = variant.GenerateReleaseConfig(configs); err != nil { 324 return err 325 } 326 return config.InheritConfig(variant) 327 } 328 329 useVariant, ok := config.FlagArtifacts["RELEASE_BUILD_USE_VARIANT_FLAGS"] 330 if ok && MarshalValue(useVariant.Value) != "" { 331 if err = inheritBuildVariant(); err != nil { 332 return err 333 } 334 } 335 } 336 337 // Now remove any duplicates from the actual value of RELEASE_ACONFIG_VALUE_SETS 338 myAconfigValueSets := []string{} 339 myAconfigValueSetsMap := map[string]bool{} 340 for _, v := range strings.Split(releaseAconfigValueSets.Value.GetStringValue(), " ") { 341 if v == "" || myAconfigValueSetsMap[v] { 342 continue 343 } 344 myAconfigValueSetsMap[v] = true 345 myAconfigValueSets = append(myAconfigValueSets, v) 346 } 347 releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.TrimSpace(strings.Join(myAconfigValueSets, " "))}} 348 349 directories := []string{} 350 valueDirectories := []string{} 351 // These path prefixes are exclusive for a release config. 352 // "A release config shall exist in at most one of these." 353 // If we find a benefit to generalizing this, we can do so at that time. 354 exclusiveDirPrefixes := []string{ 355 "build/release", 356 "vendor/google_shared/build/release", 357 } 358 var exclusiveDir string 359 for idx, confDir := range configs.configDirs { 360 if _, ok := myDirsMap[idx]; ok { 361 directories = append(directories, confDir) 362 } 363 if _, ok := myValueDirsMap[idx]; ok { 364 for _, dir := range exclusiveDirPrefixes { 365 if strings.HasPrefix(confDir, dir) { 366 if exclusiveDir != "" && !strings.HasPrefix(exclusiveDir, dir) { 367 return fmt.Errorf("%s is declared in both %s and %s", 368 config.Name, exclusiveDir, confDir) 369 } 370 exclusiveDir = confDir 371 } 372 } 373 valueDirectories = append(valueDirectories, confDir) 374 } 375 } 376 377 // Now build the per-partition artifacts 378 config.PartitionBuildFlags = make(map[string]*rc_proto.FlagArtifacts) 379 for _, v := range config.FlagArtifacts { 380 artifact, err := v.MarshalWithoutTraces() 381 if err != nil { 382 return err 383 } 384 // Redacted flags return nil when rendered. 385 if artifact == nil { 386 continue 387 } 388 for _, container := range v.FlagDeclaration.Containers { 389 if _, ok := config.PartitionBuildFlags[container]; !ok { 390 config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{} 391 } 392 config.PartitionBuildFlags[container].Flags = append(config.PartitionBuildFlags[container].Flags, artifact) 393 } 394 } 395 config.ReleaseConfigArtifact = &rc_proto.ReleaseConfigArtifact{ 396 Name: proto.String(config.Name), 397 OtherNames: config.OtherNames, 398 Flags: func() []*rc_proto.FlagArtifact { 399 ret := []*rc_proto.FlagArtifact{} 400 for _, flagName := range config.FlagArtifacts.SortedFlagNames() { 401 flag := config.FlagArtifacts[flagName] 402 ret = append(ret, &rc_proto.FlagArtifact{ 403 FlagDeclaration: flag.FlagDeclaration, 404 Traces: flag.Traces, 405 Value: flag.Value, 406 }) 407 } 408 return ret 409 }(), 410 AconfigValueSets: myAconfigValueSets, 411 Inherits: myInherits, 412 Directories: directories, 413 ValueDirectories: valueDirectories, 414 PriorStages: SortedMapKeys(config.PriorStagesMap), 415 ReleaseConfigType: config.ReleaseConfigType.Enum(), 416 DisallowLunchUse: proto.Bool(config.DisallowLunchUse), 417 } 418 419 config.compileInProgress = false 420 return nil 421} 422 423// Write the makefile for this targetRelease. 424func (config *ReleaseConfig) WriteMakefile(outFile, targetRelease string, configs *ReleaseConfigs) error { 425 makeVars := make(map[string]string) 426 427 myFlagArtifacts := config.FlagArtifacts.Clone() 428 429 // Add any RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS variables. 430 var extraAconfigReleaseConfigs []string 431 if extraAconfigValueSetsValue, ok := config.FlagArtifacts["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok { 432 if val := MarshalValue(extraAconfigValueSetsValue.Value); len(val) > 0 { 433 extraAconfigReleaseConfigs = strings.Split(val, " ") 434 } 435 } 436 for _, rcName := range extraAconfigReleaseConfigs { 437 rc, err := configs.GetReleaseConfigStrict(rcName) 438 if err != nil { 439 return err 440 } 441 myFlagArtifacts["RELEASE_ACONFIG_VALUE_SETS_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"] 442 myFlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION"] 443 } 444 445 // Sort the flags by name first. 446 names := myFlagArtifacts.SortedFlagNames() 447 partitions := make(map[string][]string) 448 449 vNames := []string{} 450 addVar := func(name, suffix, value string) { 451 fullName := fmt.Sprintf("_ALL_RELEASE_FLAGS.%s.%s", name, suffix) 452 vNames = append(vNames, fullName) 453 makeVars[fullName] = value 454 } 455 456 for _, name := range names { 457 flag := myFlagArtifacts[name] 458 decl := flag.FlagDeclaration 459 460 for _, container := range decl.Containers { 461 partitions[container] = append(partitions[container], name) 462 } 463 value := MarshalValue(flag.Value) 464 makeVars[name] = value 465 addVar(name, "TYPE", ValueType(flag.Value)) 466 addVar(name, "PARTITIONS", strings.Join(decl.Containers, " ")) 467 addVar(name, "DEFAULT", MarshalValue(decl.Value)) 468 addVar(name, "VALUE", value) 469 addVar(name, "DECLARED_IN", *flag.Traces[0].Source) 470 addVar(name, "SET_IN", *flag.Traces[len(flag.Traces)-1].Source) 471 addVar(name, "NAMESPACE", *decl.Namespace) 472 } 473 pNames := []string{} 474 for k := range partitions { 475 pNames = append(pNames, k) 476 } 477 slices.Sort(pNames) 478 479 // Now sort the make variables, and output them. 480 slices.Sort(vNames) 481 482 // Write the flags as: 483 // _ALL_RELELASE_FLAGS 484 // _ALL_RELEASE_FLAGS.PARTITIONS.* 485 // all _ALL_RELEASE_FLAGS.*, sorted by name 486 // Final flag values, sorted by name. 487 data := fmt.Sprintf("# TARGET_RELEASE=%s\n", config.Name) 488 if targetRelease != config.Name { 489 data += fmt.Sprintf("# User specified TARGET_RELEASE=%s\n", targetRelease) 490 } 491 // As it stands this list is not per-product, but conceptually it is, and will be. 492 data += fmt.Sprintf("ALL_RELEASE_CONFIGS_FOR_PRODUCT :=$= %s\n", strings.Join(configs.GetAllReleaseNames(), " ")) 493 if config.DisallowLunchUse { 494 data += fmt.Sprintf("_disallow_lunch_use :=$= true\n") 495 } 496 data += fmt.Sprintf("_used_files := %s\n", strings.Join(config.GetSortedFileList(), " ")) 497 data += fmt.Sprintf("_ALL_RELEASE_FLAGS :=$= %s\n", strings.Join(names, " ")) 498 for _, pName := range pNames { 499 data += fmt.Sprintf("_ALL_RELEASE_FLAGS.PARTITIONS.%s :=$= %s\n", pName, strings.Join(partitions[pName], " ")) 500 } 501 for _, vName := range vNames { 502 data += fmt.Sprintf("%s :=$= %s\n", vName, makeVars[vName]) 503 } 504 data += "\n\n# Values for all build flags\n" 505 for _, name := range names { 506 data += fmt.Sprintf("%s :=$= %s\n", name, makeVars[name]) 507 } 508 return os.WriteFile(outFile, []byte(data), 0644) 509} 510 511func (config *ReleaseConfig) WritePartitionBuildFlags(outDir string) error { 512 var err error 513 for partition, flags := range config.PartitionBuildFlags { 514 slices.SortFunc(flags.Flags, func(a, b *rc_proto.FlagArtifact) int { 515 return cmp.Compare(*a.FlagDeclaration.Name, *b.FlagDeclaration.Name) 516 }) 517 // The json file name must not be modified as this is read from 518 // build_flags_json module 519 if err = WriteMessage(filepath.Join(outDir, fmt.Sprintf("build_flags_%s.json", partition)), flags); err != nil { 520 return err 521 } 522 } 523 return nil 524} 525