1package main 2 3import ( 4 "cmp" 5 "flag" 6 "fmt" 7 "os" 8 "path/filepath" 9 "slices" 10 "strings" 11 12 rc_lib "android/soong/cmd/release_config/release_config_lib" 13 rc_proto "android/soong/cmd/release_config/release_config_proto" 14 15 "google.golang.org/protobuf/proto" 16) 17 18type Flags struct { 19 // The path to the top of the workspace. Default: ".". 20 top string 21 22 // Pathlist of release config map textproto files. 23 // If not specified, then the value is (if present): 24 // - build/release/release_config_map.textproto 25 // - vendor/google_shared/build/release/release_config_map.textproto 26 // - vendor/google/release/release_config_map.textproto 27 // 28 // Additionally, any maps specified in the environment variable 29 // `PRODUCT_RELEASE_CONFIG_MAPS` are used. 30 maps rc_lib.StringList 31 32 // Output directory (relative to `top`). 33 outDir string 34 35 // Which $TARGET_RELEASE(s) should we use. Some commands will only 36 // accept one value, others also accept `--release --all`. 37 targetReleases rc_lib.StringList 38 39 // Disable warning messages 40 quiet bool 41 42 // Show all release configs 43 allReleases bool 44 45 // Call get_build_var PRODUCT_RELEASE_CONFIG_MAPS to get the 46 // product-specific map directories. 47 useGetBuildVar bool 48 49 // Panic on errors. 50 debug bool 51 52 // Allow missing release config. 53 // If true, and we cannot find the named release config, values for 54 // `trunk_staging` will be used. 55 allowMissing bool 56} 57 58type CommandFunc func(*rc_lib.ReleaseConfigs, Flags, string, []string) error 59 60var commandMap map[string]CommandFunc = map[string]CommandFunc{ 61 "get": GetCommand, 62 "set": SetCommand, 63 "trace": GetCommand, // Also handled by GetCommand 64} 65 66// Find the top of the release config contribution directory. 67// Returns the parent of the flag_declarations and flag_values directories. 68func GetMapDir(path string) (string, error) { 69 for p := path; p != "."; p = filepath.Dir(p) { 70 switch filepath.Base(p) { 71 case "flag_declarations": 72 return filepath.Dir(p), nil 73 case "flag_values": 74 return filepath.Dir(p), nil 75 } 76 } 77 return "", fmt.Errorf("Could not determine directory from %s", path) 78} 79 80func MarshalFlagDefaultValue(config *rc_lib.ReleaseConfig, name string) (ret string, err error) { 81 fa, ok := config.FlagArtifacts[name] 82 if !ok { 83 return "", fmt.Errorf("%s not found in %s", name, config.Name) 84 } 85 return rc_lib.MarshalValue(fa.Traces[0].Value), nil 86} 87 88func MarshalFlagValue(config *rc_lib.ReleaseConfig, name string) (ret string, err error) { 89 fa, ok := config.FlagArtifacts[name] 90 if !ok { 91 return "", fmt.Errorf("%s not found in %s", name, config.Name) 92 } 93 if fa.Redacted { 94 return "==REDACTED==", nil 95 } 96 return rc_lib.MarshalValue(fa.Value), nil 97} 98 99// Returns a list of ReleaseConfig objects for which to process flags. 100func GetReleaseArgs(configs *rc_lib.ReleaseConfigs, commonFlags Flags) ([]*rc_lib.ReleaseConfig, error) { 101 var all bool 102 relFlags := flag.NewFlagSet("releaseFlags", flag.ExitOnError) 103 relFlags.BoolVar(&all, "all", false, "Display all releases") 104 relFlags.Parse(commonFlags.targetReleases) 105 var ret []*rc_lib.ReleaseConfig 106 if all || commonFlags.allReleases { 107 sortMap := map[string]int{ 108 "trunk_staging": 0, 109 "trunk_food": 10, 110 "trunk": 20, 111 // Anything not listed above, uses this for key 1 in the sort. 112 "-default": 100, 113 } 114 115 for _, config := range configs.ReleaseConfigs { 116 ret = append(ret, config) 117 } 118 slices.SortFunc(ret, func(a, b *rc_lib.ReleaseConfig) int { 119 mapValue := func(v *rc_lib.ReleaseConfig) int { 120 if v, ok := sortMap[v.Name]; ok { 121 return v 122 } 123 return sortMap["-default"] 124 } 125 if n := cmp.Compare(mapValue(a), mapValue(b)); n != 0 { 126 return n 127 } 128 return cmp.Compare(a.Name, b.Name) 129 }) 130 return ret, nil 131 } 132 for _, arg := range relFlags.Args() { 133 // Return releases in the order that they were given. 134 config, err := configs.GetReleaseConfig(arg) 135 if err != nil { 136 return nil, err 137 } 138 ret = append(ret, config) 139 } 140 return ret, nil 141} 142 143func GetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error { 144 isTrace := cmd == "trace" 145 isSet := cmd == "set" 146 147 var all bool 148 getFlags := flag.NewFlagSet("get", flag.ExitOnError) 149 getFlags.BoolVar(&all, "all", false, "Display all flags") 150 getFlags.Parse(args) 151 args = getFlags.Args() 152 153 if isSet { 154 commonFlags.allReleases = true 155 } 156 releaseConfigList, err := GetReleaseArgs(configs, commonFlags) 157 if err != nil { 158 return err 159 } 160 if isTrace && len(releaseConfigList) > 1 { 161 return fmt.Errorf("trace command only allows one --release argument. Got: %s", strings.Join(commonFlags.targetReleases, " ")) 162 } 163 164 if all { 165 args = []string{} 166 for _, fa := range configs.FlagArtifacts { 167 args = append(args, *fa.FlagDeclaration.Name) 168 } 169 slices.Sort(args) 170 } 171 172 var maxVariableNameLen, maxReleaseNameLen int 173 var releaseNameFormat, variableNameFormat string 174 valueFormat := "%s" 175 showReleaseName := len(releaseConfigList) > 1 176 showVariableName := len(args) > 1 177 if showVariableName { 178 for _, arg := range args { 179 maxVariableNameLen = max(len(arg), maxVariableNameLen) 180 } 181 variableNameFormat = fmt.Sprintf("%%-%ds ", maxVariableNameLen) 182 valueFormat = "'%s'" 183 } 184 if showReleaseName { 185 for _, config := range releaseConfigList { 186 maxReleaseNameLen = max(len(config.Name), maxReleaseNameLen) 187 } 188 releaseNameFormat = fmt.Sprintf("%%-%ds ", maxReleaseNameLen) 189 valueFormat = "'%s'" 190 } 191 192 outputOneLine := func(variable, release, value, valueFormat string) { 193 var outStr string 194 if showVariableName { 195 outStr += fmt.Sprintf(variableNameFormat, variable) 196 } 197 if showReleaseName { 198 outStr += fmt.Sprintf(releaseNameFormat, release) 199 } 200 outStr += fmt.Sprintf(valueFormat, value) 201 fmt.Println(outStr) 202 } 203 204 for _, arg := range args { 205 if _, ok := configs.FlagArtifacts[arg]; !ok { 206 return fmt.Errorf("%s is not a defined build flag", arg) 207 } 208 } 209 210 for _, arg := range args { 211 for _, config := range releaseConfigList { 212 if isSet { 213 // If this is from the set command, format the output as: 214 // <default> "" 215 // trunk_staging "" 216 // trunk "" 217 // 218 // ap1a "" 219 // ... 220 switch { 221 case config.Name == "trunk_staging": 222 defaultValue, err := MarshalFlagDefaultValue(config, arg) 223 if err != nil { 224 return err 225 } 226 outputOneLine(arg, "<default>", defaultValue, valueFormat) 227 case config.AconfigFlagsOnly: 228 continue 229 case config.Name == "trunk": 230 fmt.Println() 231 } 232 } 233 val, err := MarshalFlagValue(config, arg) 234 if err == nil { 235 outputOneLine(arg, config.Name, val, valueFormat) 236 } else { 237 outputOneLine(arg, config.Name, "REDACTED", "%s") 238 } 239 if err == nil && isTrace { 240 for _, trace := range config.FlagArtifacts[arg].Traces { 241 fmt.Printf(" => \"%s\" in %s\n", rc_lib.MarshalValue(trace.Value), *trace.Source) 242 } 243 } 244 } 245 } 246 return nil 247} 248 249func SetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error { 250 var valueDir string 251 var redacted bool 252 var value string 253 if len(commonFlags.targetReleases) > 1 { 254 return fmt.Errorf("set command only allows one --release argument. Got: %s", strings.Join(commonFlags.targetReleases, " ")) 255 } 256 targetRelease := commonFlags.targetReleases[0] 257 258 setFlags := flag.NewFlagSet("set", flag.ExitOnError) 259 setFlags.StringVar(&valueDir, "dir", "", "Directory in which to place the value") 260 setFlags.BoolVar(&redacted, "redacted", false, "Whether the flag should be redacted") 261 setFlags.Parse(args) 262 setArgs := setFlags.Args() 263 if redacted { 264 if len(setArgs) != 1 { 265 return fmt.Errorf("set command expected '--redacted=true flag', got: --redacted=true %s", strings.Join(setArgs, " ")) 266 } 267 } else if len(setArgs) != 2 { 268 return fmt.Errorf("set command expected flag and value, got: %s", strings.Join(setArgs, " ")) 269 } 270 name := setArgs[0] 271 if !redacted { 272 value = setArgs[1] 273 } 274 release, err := configs.GetReleaseConfig(targetRelease) 275 targetRelease = release.Name 276 if err != nil { 277 return err 278 } 279 if release.AconfigFlagsOnly { 280 return fmt.Errorf("%s does not allow build flag overrides", targetRelease) 281 } 282 flagArtifact, ok := release.FlagArtifacts[name] 283 if !ok { 284 return fmt.Errorf("Unknown build flag %s", name) 285 } 286 if valueDir == "" { 287 mapDir, err := configs.GetFlagValueDirectory(release, flagArtifact) 288 if err != nil { 289 return err 290 } 291 valueDir = mapDir 292 } 293 294 var updatedFiles []string 295 rcPath := filepath.Join(valueDir, "release_configs", fmt.Sprintf("%s.textproto", targetRelease)) 296 // Create the release config declaration only if necessary. 297 if _, err = os.Stat(rcPath); err != nil { 298 if err = os.MkdirAll(filepath.Dir(rcPath), 0775); err != nil { 299 return err 300 } 301 rcValue := &rc_proto.ReleaseConfig{ 302 Name: proto.String(targetRelease), 303 } 304 err = rc_lib.WriteMessage(rcPath, rcValue) 305 if err != nil { 306 return err 307 } 308 updatedFiles = append(updatedFiles, rcPath) 309 } 310 311 flagValue := &rc_proto.FlagValue{ 312 Name: proto.String(name), 313 } 314 if redacted { 315 flagValue.Redacted = proto.Bool(true) 316 } else { 317 flagValue.Value = rc_lib.UnmarshalValue(value) 318 } 319 flagPath := filepath.Join(valueDir, "flag_values", targetRelease, fmt.Sprintf("%s.textproto", name)) 320 err = rc_lib.WriteMessage(flagPath, flagValue) 321 if err != nil { 322 return err 323 } 324 325 // Reload the release configs. 326 configs, err = rc_lib.ReadReleaseConfigMaps(commonFlags.maps, commonFlags.targetReleases[0], commonFlags.useGetBuildVar, commonFlags.allowMissing) 327 if err != nil { 328 return err 329 } 330 err = GetCommand(configs, commonFlags, cmd, []string{name}) 331 if err != nil { 332 return err 333 } 334 updatedFiles = append(updatedFiles, flagPath) 335 fmt.Printf("\033[1mAdded/Updated: %s\033[0m\n", strings.Join(updatedFiles, " ")) 336 return nil 337} 338 339func main() { 340 var commonFlags Flags 341 var configs *rc_lib.ReleaseConfigs 342 topDir, err := rc_lib.GetTopDir() 343 344 // Handle the common arguments 345 flag.StringVar(&commonFlags.top, "top", topDir, "path to top of workspace") 346 flag.BoolVar(&commonFlags.quiet, "quiet", false, "disable warning messages") 347 flag.Var(&commonFlags.maps, "map", "path to a release_config_map.textproto. may be repeated") 348 flag.StringVar(&commonFlags.outDir, "out-dir", rc_lib.GetDefaultOutDir(), "basepath for the output. Multiple formats are created") 349 flag.Var(&commonFlags.targetReleases, "release", "TARGET_RELEASE for this build") 350 flag.BoolVar(&commonFlags.allowMissing, "allow-missing", false, "Use trunk_staging values if release not found") 351 flag.BoolVar(&commonFlags.allReleases, "all-releases", false, "operate on all releases. (Ignored for set command)") 352 flag.BoolVar(&commonFlags.useGetBuildVar, "use-get-build-var", true, "use get_build_var PRODUCT_RELEASE_CONFIG_MAPS to get needed maps") 353 flag.BoolVar(&commonFlags.debug, "debug", false, "turn on debugging output for errors") 354 flag.Parse() 355 356 errorExit := func(err error) { 357 if commonFlags.debug { 358 panic(err) 359 } 360 fmt.Fprintf(os.Stderr, "%s\n", err) 361 os.Exit(1) 362 } 363 364 if commonFlags.quiet { 365 rc_lib.DisableWarnings() 366 } 367 368 if len(commonFlags.targetReleases) == 0 { 369 release, ok := os.LookupEnv("TARGET_RELEASE") 370 if ok { 371 commonFlags.targetReleases = rc_lib.StringList{release} 372 } else { 373 commonFlags.targetReleases = rc_lib.StringList{"trunk_staging"} 374 } 375 } 376 377 if err = os.Chdir(commonFlags.top); err != nil { 378 errorExit(err) 379 } 380 381 // Get the current state of flagging. 382 relName := commonFlags.targetReleases[0] 383 if relName == "--all" || relName == "-all" { 384 commonFlags.allReleases = true 385 } 386 configs, err = rc_lib.ReadReleaseConfigMaps(commonFlags.maps, relName, commonFlags.useGetBuildVar, commonFlags.allowMissing) 387 if err != nil { 388 errorExit(err) 389 } 390 391 if cmd, ok := commandMap[flag.Arg(0)]; ok { 392 args := flag.Args() 393 if err = cmd(configs, commonFlags, args[0], args[1:]); err != nil { 394 errorExit(err) 395 } 396 } 397} 398