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 "io/fs" 21 "os" 22 "path/filepath" 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// A single release_config_map.textproto and its associated data. 32// Used primarily for debugging. 33type ReleaseConfigMap struct { 34 // The path to this release_config_map file. 35 path string 36 37 // Data received 38 proto rc_proto.ReleaseConfigMap 39 40 // Map of name:contribution for release config contributions. 41 ReleaseConfigContributions map[string]*ReleaseConfigContribution 42 43 // Flags declared this directory's flag_declarations/*.textproto 44 FlagDeclarations []rc_proto.FlagDeclaration 45} 46 47type ReleaseConfigDirMap map[string]int 48 49// The generated release configs. 50type ReleaseConfigs struct { 51 // Ordered list of release config maps processed. 52 ReleaseConfigMaps []*ReleaseConfigMap 53 54 // Aliases 55 Aliases map[string]*string 56 57 // Dictionary of flag_name:FlagDeclaration, with no overrides applied. 58 FlagArtifacts FlagArtifacts 59 60 // Generated release configs artifact 61 Artifact rc_proto.ReleaseConfigsArtifact 62 63 // Dictionary of name:ReleaseConfig 64 // Use `GetReleaseConfigs(name)` to get a release config. 65 ReleaseConfigs map[string]*ReleaseConfig 66 67 // Map of directory to *ReleaseConfigMap 68 releaseConfigMapsMap map[string]*ReleaseConfigMap 69 70 // The files used by all release configs 71 FilesUsedMap map[string]bool 72 73 // The list of config directories used. 74 configDirs []string 75 76 // A map from the config directory to its order in the list of config 77 // directories. 78 configDirIndexes ReleaseConfigDirMap 79 80 // True if we should allow a missing primary release config. In this 81 // case, we will substitute `trunk_staging` values, but the release 82 // config will not be in ALL_RELEASE_CONFIGS_FOR_PRODUCT. 83 allowMissing bool 84} 85 86func (configs *ReleaseConfigs) WriteInheritanceGraph(outFile string) error { 87 data := []string{} 88 usedAliases := make(map[string]bool) 89 priorStages := make(map[string][]string) 90 for _, config := range configs.ReleaseConfigs { 91 if config.Name == "root" { 92 continue 93 } 94 var fillColor string 95 inherits := []string{} 96 for _, inherit := range config.InheritNames { 97 if inherit == "root" { 98 continue 99 } 100 data = append(data, fmt.Sprintf(`"%s" -> "%s"`, config.Name, inherit)) 101 inherits = append(inherits, inherit) 102 // If inheriting an alias, add a link from the alias to that release config. 103 if name, found := configs.Aliases[inherit]; found { 104 if !usedAliases[inherit] { 105 usedAliases[inherit] = true 106 data = append(data, fmt.Sprintf(`"%s" -> "%s"`, inherit, *name)) 107 data = append(data, 108 fmt.Sprintf(`"%s" [ label="%s\ncurrently: %s" shape=oval ]`, 109 inherit, inherit, *name)) 110 } 111 } 112 } 113 // Add links for all of the advancement progressions. 114 for priorStage := range config.PriorStagesMap { 115 data = append(data, fmt.Sprintf(`"%s" -> "%s" [ style=dashed color="#81c995" ]`, 116 priorStage, config.Name)) 117 priorStages[config.Name] = append(priorStages[config.Name], priorStage) 118 } 119 label := config.Name 120 if len(inherits) > 0 { 121 label += "\\ninherits: " + strings.Join(inherits, " ") 122 } 123 if len(config.OtherNames) > 0 { 124 label += "\\nother names: " + strings.Join(config.OtherNames, " ") 125 } 126 switch config.Name { 127 case *configs.Artifact.ReleaseConfig.Name: 128 // The active release config has a light blue fill. 129 fillColor = `fillcolor="#d2e3fc" ` 130 case "trunk", "trunk_staging": 131 // Certain workflow stages have a light green fill. 132 fillColor = `fillcolor="#ceead6" ` 133 default: 134 // Look for "next" and "*_next", make them light green as well. 135 for _, n := range config.OtherNames { 136 if n == "next" || strings.HasSuffix(n, "_next") { 137 fillColor = `fillcolor="#ceead6" ` 138 } 139 } 140 } 141 data = append(data, 142 fmt.Sprintf(`"%s" [ label="%s" %s]`, config.Name, label, fillColor)) 143 } 144 slices.Sort(data) 145 data = append([]string{ 146 "digraph {", 147 "graph [ ratio=.5 ]", 148 "node [ shape=box style=filled fillcolor=white colorscheme=svg fontcolor=black ]", 149 }, data...) 150 data = append(data, "}") 151 return os.WriteFile(outFile, []byte(strings.Join(data, "\n")), 0644) 152} 153 154// Write the "all_release_configs" artifact. 155// 156// The file will be in "{outDir}/all_release_configs-{product}.{format}" 157// 158// Args: 159// 160// outDir string: directory path. Will be created if not present. 161// product string: TARGET_PRODUCT for the release_configs. 162// format string: one of "json", "pb", or "textproto" 163// 164// Returns: 165// 166// error: Any error encountered. 167func (configs *ReleaseConfigs) WriteArtifact(outDir, product, format string) error { 168 return WriteMessage( 169 filepath.Join(outDir, fmt.Sprintf("all_release_configs-%s.%s", product, format)), 170 &configs.Artifact) 171} 172 173func ReleaseConfigsFactory() (c *ReleaseConfigs) { 174 configs := ReleaseConfigs{ 175 Aliases: make(map[string]*string), 176 FlagArtifacts: make(map[string]*FlagArtifact), 177 ReleaseConfigs: make(map[string]*ReleaseConfig), 178 releaseConfigMapsMap: make(map[string]*ReleaseConfigMap), 179 configDirs: []string{}, 180 configDirIndexes: make(ReleaseConfigDirMap), 181 FilesUsedMap: make(map[string]bool), 182 } 183 workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL) 184 releaseAconfigValueSets := FlagArtifact{ 185 FlagDeclaration: &rc_proto.FlagDeclaration{ 186 Name: proto.String("RELEASE_ACONFIG_VALUE_SETS"), 187 Namespace: proto.String("android_UNKNOWN"), 188 Description: proto.String("Aconfig value sets assembled by release-config"), 189 Workflow: &workflowManual, 190 Containers: []string{"system", "system_ext", "product", "vendor"}, 191 Value: &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}, 192 }, 193 DeclarationIndex: -1, 194 Traces: []*rc_proto.Tracepoint{}, 195 } 196 configs.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"] = &releaseAconfigValueSets 197 return &configs 198} 199 200func (configs *ReleaseConfigs) GetSortedReleaseConfigs() (ret []*ReleaseConfig) { 201 for _, config := range configs.ReleaseConfigs { 202 ret = append(ret, config) 203 } 204 slices.SortFunc(ret, func(a, b *ReleaseConfig) int { 205 return cmp.Compare(a.Name, b.Name) 206 }) 207 return ret 208} 209 210func ReleaseConfigMapFactory(protoPath string) (m *ReleaseConfigMap) { 211 m = &ReleaseConfigMap{ 212 path: protoPath, 213 ReleaseConfigContributions: make(map[string]*ReleaseConfigContribution), 214 } 215 if protoPath != "" { 216 LoadMessage(protoPath, &m.proto) 217 } 218 return m 219} 220 221// Find the top of the release config contribution directory. 222// Returns the parent of the flag_declarations and flag_values directories. 223func (configs *ReleaseConfigs) GetDirIndex(path string) (int, error) { 224 for p := path; p != "."; p = filepath.Dir(p) { 225 if idx, ok := configs.configDirIndexes[p]; ok { 226 return idx, nil 227 } 228 } 229 return -1, fmt.Errorf("Could not determine release config directory from %s", path) 230} 231 232// Determine the default directory for writing a flag value. 233// 234// Returns the path of the highest-Indexed one of: 235// - Where the flag is declared 236// - Where the release config is first declared 237// - The last place the value is being written. 238func (configs *ReleaseConfigs) GetFlagValueDirectory(config *ReleaseConfig, flag *FlagArtifact) (string, error) { 239 current, err := configs.GetDirIndex(*flag.Traces[len(flag.Traces)-1].Source) 240 if err != nil { 241 return "", err 242 } 243 index := max(flag.DeclarationIndex, config.DeclarationIndex, current) 244 return configs.configDirs[index], nil 245} 246 247func (configs *ReleaseConfigs) LoadReleaseConfigMap(path string, ConfigDirIndex int) error { 248 if _, err := os.Stat(path); err != nil { 249 return fmt.Errorf("%s does not exist\n", path) 250 } 251 m := ReleaseConfigMapFactory(path) 252 if m.proto.DefaultContainers == nil { 253 return fmt.Errorf("Release config map %s lacks default_containers", path) 254 } 255 for _, container := range m.proto.DefaultContainers { 256 if !validContainer(container) { 257 return fmt.Errorf("Release config map %s has invalid container %s", path, container) 258 } 259 } 260 configs.FilesUsedMap[path] = true 261 dir := filepath.Dir(path) 262 // Record any aliases, checking for duplicates. 263 for _, alias := range m.proto.Aliases { 264 name := *alias.Name 265 oldTarget, ok := configs.Aliases[name] 266 if ok { 267 if *oldTarget != *alias.Target { 268 return fmt.Errorf("Conflicting alias declarations: %s vs %s", 269 *oldTarget, *alias.Target) 270 } 271 } 272 configs.Aliases[name] = alias.Target 273 } 274 var err error 275 err = WalkTextprotoFiles(dir, "flag_declarations", func(path string, d fs.DirEntry, err error) error { 276 flagDeclaration := FlagDeclarationFactory(path) 277 // Container must be specified. 278 if flagDeclaration.Containers == nil { 279 flagDeclaration.Containers = m.proto.DefaultContainers 280 } else { 281 for _, container := range flagDeclaration.Containers { 282 if !validContainer(container) { 283 return fmt.Errorf("Flag declaration %s has invalid container %s", path, container) 284 } 285 } 286 } 287 288 // TODO: once we have namespaces initialized, we can throw an error here. 289 if flagDeclaration.Namespace == nil { 290 flagDeclaration.Namespace = proto.String("android_UNKNOWN") 291 } 292 // If the input didn't specify a value, create one (== UnspecifiedValue). 293 if flagDeclaration.Value == nil { 294 flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}} 295 } 296 m.FlagDeclarations = append(m.FlagDeclarations, *flagDeclaration) 297 name := *flagDeclaration.Name 298 if name == "RELEASE_ACONFIG_VALUE_SETS" { 299 return fmt.Errorf("%s: %s is a reserved build flag", path, name) 300 } 301 if def, ok := configs.FlagArtifacts[name]; !ok { 302 configs.FlagArtifacts[name] = &FlagArtifact{FlagDeclaration: flagDeclaration, DeclarationIndex: ConfigDirIndex} 303 } else if !proto.Equal(def.FlagDeclaration, flagDeclaration) { 304 return fmt.Errorf("Duplicate definition of %s", *flagDeclaration.Name) 305 } 306 // Set the initial value in the flag artifact. 307 configs.FilesUsedMap[path] = true 308 configs.FlagArtifacts[name].UpdateValue( 309 FlagValue{path: path, proto: rc_proto.FlagValue{ 310 Name: proto.String(name), Value: flagDeclaration.Value}}) 311 if configs.FlagArtifacts[name].Redacted { 312 return fmt.Errorf("%s may not be redacted by default.", name) 313 } 314 return nil 315 }) 316 if err != nil { 317 return err 318 } 319 320 err = WalkTextprotoFiles(dir, "release_configs", func(path string, d fs.DirEntry, err error) error { 321 releaseConfigContribution := &ReleaseConfigContribution{path: path, DeclarationIndex: ConfigDirIndex} 322 LoadMessage(path, &releaseConfigContribution.proto) 323 name := *releaseConfigContribution.proto.Name 324 if fmt.Sprintf("%s.textproto", name) != filepath.Base(path) { 325 return fmt.Errorf("%s incorrectly declares release config %s", path, name) 326 } 327 if _, ok := configs.ReleaseConfigs[name]; !ok { 328 configs.ReleaseConfigs[name] = ReleaseConfigFactory(name, ConfigDirIndex) 329 } 330 config := configs.ReleaseConfigs[name] 331 config.FilesUsedMap[path] = true 332 inheritNames := make(map[string]bool) 333 for _, inh := range config.InheritNames { 334 inheritNames[inh] = true 335 } 336 // If this contribution says to inherit something we already inherited, we do not want the duplicate. 337 for _, cInh := range releaseConfigContribution.proto.Inherits { 338 if !inheritNames[cInh] { 339 config.InheritNames = append(config.InheritNames, cInh) 340 inheritNames[cInh] = true 341 } 342 } 343 344 // Only walk flag_values/{RELEASE} for defined releases. 345 err2 := WalkTextprotoFiles(dir, filepath.Join("flag_values", name), func(path string, d fs.DirEntry, err error) error { 346 flagValue := FlagValueFactory(path) 347 if fmt.Sprintf("%s.textproto", *flagValue.proto.Name) != filepath.Base(path) { 348 return fmt.Errorf("%s incorrectly sets value for flag %s", path, *flagValue.proto.Name) 349 } 350 if *flagValue.proto.Name == "RELEASE_ACONFIG_VALUE_SETS" { 351 return fmt.Errorf("%s: %s is a reserved build flag", path, *flagValue.proto.Name) 352 } 353 config.FilesUsedMap[path] = true 354 releaseConfigContribution.FlagValues = append(releaseConfigContribution.FlagValues, flagValue) 355 return nil 356 }) 357 if err2 != nil { 358 return err2 359 } 360 if releaseConfigContribution.proto.GetAconfigFlagsOnly() { 361 config.AconfigFlagsOnly = true 362 } 363 m.ReleaseConfigContributions[name] = releaseConfigContribution 364 config.Contributions = append(config.Contributions, releaseConfigContribution) 365 return nil 366 }) 367 if err != nil { 368 return err 369 } 370 configs.ReleaseConfigMaps = append(configs.ReleaseConfigMaps, m) 371 configs.releaseConfigMapsMap[dir] = m 372 return nil 373} 374 375func (configs *ReleaseConfigs) GetReleaseConfig(name string) (*ReleaseConfig, error) { 376 trace := []string{name} 377 for target, ok := configs.Aliases[name]; ok; target, ok = configs.Aliases[name] { 378 name = *target 379 trace = append(trace, name) 380 } 381 if config, ok := configs.ReleaseConfigs[name]; ok { 382 return config, nil 383 } 384 if configs.allowMissing { 385 if config, ok := configs.ReleaseConfigs["trunk_staging"]; ok { 386 return config, nil 387 } 388 } 389 return nil, fmt.Errorf("Missing config %s. Trace=%v", name, trace) 390} 391 392func (configs *ReleaseConfigs) GetAllReleaseNames() []string { 393 var allReleaseNames []string 394 for _, v := range configs.ReleaseConfigs { 395 allReleaseNames = append(allReleaseNames, v.Name) 396 allReleaseNames = append(allReleaseNames, v.OtherNames...) 397 } 398 slices.Sort(allReleaseNames) 399 return allReleaseNames 400} 401 402func (configs *ReleaseConfigs) GenerateReleaseConfigs(targetRelease string) error { 403 otherNames := make(map[string][]string) 404 for aliasName, aliasTarget := range configs.Aliases { 405 if _, ok := configs.ReleaseConfigs[aliasName]; ok { 406 return fmt.Errorf("Alias %s is a declared release config", aliasName) 407 } 408 if _, ok := configs.ReleaseConfigs[*aliasTarget]; !ok { 409 if _, ok2 := configs.Aliases[*aliasTarget]; !ok2 { 410 return fmt.Errorf("Alias %s points to non-existing config %s", aliasName, *aliasTarget) 411 } 412 } 413 otherNames[*aliasTarget] = append(otherNames[*aliasTarget], aliasName) 414 } 415 for name, aliases := range otherNames { 416 configs.ReleaseConfigs[name].OtherNames = aliases 417 } 418 419 sortedReleaseConfigs := configs.GetSortedReleaseConfigs() 420 for _, c := range sortedReleaseConfigs { 421 err := c.GenerateReleaseConfig(configs) 422 if err != nil { 423 return err 424 } 425 } 426 427 releaseConfig, err := configs.GetReleaseConfig(targetRelease) 428 if err != nil { 429 return err 430 } 431 orc := []*rc_proto.ReleaseConfigArtifact{} 432 for _, c := range sortedReleaseConfigs { 433 if c.Name != releaseConfig.Name { 434 orc = append(orc, c.ReleaseConfigArtifact) 435 } 436 } 437 438 configs.Artifact = rc_proto.ReleaseConfigsArtifact{ 439 ReleaseConfig: releaseConfig.ReleaseConfigArtifact, 440 OtherReleaseConfigs: orc, 441 ReleaseConfigMapsMap: func() map[string]*rc_proto.ReleaseConfigMap { 442 ret := make(map[string]*rc_proto.ReleaseConfigMap) 443 for k, v := range configs.releaseConfigMapsMap { 444 ret[k] = &v.proto 445 } 446 return ret 447 }(), 448 } 449 return nil 450} 451 452func ReadReleaseConfigMaps(releaseConfigMapPaths StringList, targetRelease string, useBuildVar, allowMissing bool) (*ReleaseConfigs, error) { 453 var err error 454 455 if len(releaseConfigMapPaths) == 0 { 456 releaseConfigMapPaths, err = GetDefaultMapPaths(useBuildVar) 457 if err != nil { 458 return nil, err 459 } 460 if len(releaseConfigMapPaths) == 0 { 461 return nil, fmt.Errorf("No maps found") 462 } 463 if !useBuildVar { 464 warnf("No --map argument provided. Using: --map %s\n", strings.Join(releaseConfigMapPaths, " --map ")) 465 } 466 } 467 468 configs := ReleaseConfigsFactory() 469 configs.allowMissing = allowMissing 470 mapsRead := make(map[string]bool) 471 var idx int 472 for _, releaseConfigMapPath := range releaseConfigMapPaths { 473 // Maintain an ordered list of release config directories. 474 configDir := filepath.Dir(releaseConfigMapPath) 475 if mapsRead[configDir] { 476 continue 477 } 478 mapsRead[configDir] = true 479 configs.configDirIndexes[configDir] = idx 480 configs.configDirs = append(configs.configDirs, configDir) 481 // Force the path to be the textproto path, so that both the scl and textproto formats can coexist. 482 releaseConfigMapPath = filepath.Join(configDir, "release_config_map.textproto") 483 err = configs.LoadReleaseConfigMap(releaseConfigMapPath, idx) 484 if err != nil { 485 return nil, err 486 } 487 idx += 1 488 } 489 490 // Now that we have all of the release config maps, can meld them and generate the artifacts. 491 err = configs.GenerateReleaseConfigs(targetRelease) 492 return configs, err 493} 494