blob: 8a80a027162e834d74d0b140a74efb3c03ba23dd [file] [log] [blame]
LaMont Jonesb9014c72024-04-11 17:41:15 -07001package main
2
3import (
4 "flag"
5 "fmt"
6 "io/fs"
7 "os"
8 "path/filepath"
9 "regexp"
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 "google.golang.org/protobuf/encoding/prototext"
15 "google.golang.org/protobuf/proto"
16)
17
LaMont Jonesdc868192024-04-30 09:06:20 -070018var (
LaMont Jones2efc8e22024-05-16 15:45:57 -070019 // When a flag declaration has an initial value that is a string, the default workflow is WorkflowPrebuilt.
20 // If the flag name starts with any of prefixes in manualFlagNamePrefixes, it is WorkflowManual.
LaMont Jonesdc868192024-04-30 09:06:20 -070021 manualFlagNamePrefixes []string = []string{
22 "RELEASE_ACONFIG_",
23 "RELEASE_PLATFORM_",
LaMont Jones15902f22024-05-01 10:06:32 -070024 "RELEASE_BUILD_FLAGS_",
LaMont Jonesdc868192024-04-30 09:06:20 -070025 }
LaMont Jonesb9014c72024-04-11 17:41:15 -070026
LaMont Jonesdc868192024-04-30 09:06:20 -070027 // Set `aconfig_flags_only: true` in these release configs.
28 aconfigFlagsOnlyConfigs map[string]bool = map[string]bool{
29 "trunk_food": true,
30 }
31
32 // Default namespace value. This is intentionally invalid.
33 defaultFlagNamespace string = "android_UNKNOWN"
34
35 // What is the current name for "next".
36 nextName string = "ap3a"
37)
LaMont Jonesb9014c72024-04-11 17:41:15 -070038
39func RenameNext(name string) string {
40 if name == "next" {
LaMont Jonesdc868192024-04-30 09:06:20 -070041 return nextName
LaMont Jonesb9014c72024-04-11 17:41:15 -070042 }
43 return name
44}
45
46func WriteFile(path string, message proto.Message) error {
47 data, err := prototext.MarshalOptions{Multiline: true}.Marshal(message)
48 if err != nil {
49 return err
50 }
51
52 err = os.MkdirAll(filepath.Dir(path), 0775)
53 if err != nil {
54 return err
55 }
56 return os.WriteFile(path, data, 0644)
57}
58
59func WalkValueFiles(dir string, Func fs.WalkDirFunc) error {
60 valPath := filepath.Join(dir, "build_config")
61 if _, err := os.Stat(valPath); err != nil {
62 fmt.Printf("%s not found, ignoring.\n", valPath)
63 return nil
64 }
65
66 return filepath.WalkDir(valPath, func(path string, d fs.DirEntry, err error) error {
67 if err != nil {
68 return err
69 }
70 if strings.HasSuffix(d.Name(), ".scl") && d.Type().IsRegular() {
71 return Func(path, d, err)
72 }
73 return nil
74 })
75}
76
77func ProcessBuildFlags(dir string, namespaceMap map[string]string) error {
78 var rootAconfigModule string
79
80 path := filepath.Join(dir, "build_flags.scl")
81 if _, err := os.Stat(path); err != nil {
82 fmt.Printf("%s not found, ignoring.\n", path)
83 return nil
84 } else {
85 fmt.Printf("Processing %s\n", path)
86 }
87 commentRegexp, err := regexp.Compile("^[[:space:]]*#(?<comment>.+)")
88 if err != nil {
89 return err
90 }
91 declRegexp, err := regexp.Compile("^[[:space:]]*flag.\"(?<name>[A-Z_0-9]+)\",[[:space:]]*(?<container>[_A-Z]*),[[:space:]]*(?<value>(\"[^\"]*\"|[^\",)]*))")
92 if err != nil {
93 return err
94 }
95 declIn, err := os.ReadFile(path)
96 if err != nil {
97 return err
98 }
99 lines := strings.Split(string(declIn), "\n")
100 var description string
101 for _, line := range lines {
102 if comment := commentRegexp.FindStringSubmatch(commentRegexp.FindString(line)); comment != nil {
103 // Description is the text from any contiguous series of lines before a `flag()` call.
LaMont Jones11209e12024-04-19 17:26:27 -0700104 descLine := strings.TrimSpace(comment[commentRegexp.SubexpIndex("comment")])
105 if !strings.HasPrefix(descLine, "keep-sorted") {
106 description += fmt.Sprintf(" %s", descLine)
107 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700108 continue
109 }
110 matches := declRegexp.FindStringSubmatch(declRegexp.FindString(line))
111 if matches == nil {
112 // The line is neither a comment nor a `flag()` call.
113 // Discard any description we have gathered and process the next line.
114 description = ""
115 continue
116 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700117 declName := matches[declRegexp.SubexpIndex("name")]
LaMont Jonesdb600992024-04-26 14:19:19 -0700118 declValue := matches[declRegexp.SubexpIndex("value")]
LaMont Jonesb9014c72024-04-11 17:41:15 -0700119 description = strings.TrimSpace(description)
LaMont Jonesdb600992024-04-26 14:19:19 -0700120 containers := []string{strings.ToLower(matches[declRegexp.SubexpIndex("container")])}
121 if containers[0] == "all" {
122 containers = []string{"product", "system", "system_ext", "vendor"}
123 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700124 var namespace string
125 var ok bool
126 if namespace, ok = namespaceMap[declName]; !ok {
127 namespace = defaultFlagNamespace
128 }
129 flagDeclaration := &rc_proto.FlagDeclaration{
130 Name: proto.String(declName),
131 Namespace: proto.String(namespace),
132 Description: proto.String(description),
LaMont Jonesdb600992024-04-26 14:19:19 -0700133 Containers: containers,
LaMont Jonesb9014c72024-04-11 17:41:15 -0700134 }
135 description = ""
LaMont Jones2efc8e22024-05-16 15:45:57 -0700136 // Most build flags are `workflow: WorkflowPrebuilt`.
137 workflow := rc_proto.Workflow(rc_proto.Workflow_WorkflowPrebuilt)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700138 switch {
139 case declName == "RELEASE_ACONFIG_VALUE_SETS":
LaMont Jones6f3ca9e2024-05-08 17:24:51 -0700140 if strings.HasPrefix(declValue, "\"") {
141 rootAconfigModule = declValue[1 : len(declValue)-1]
142 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700143 continue
144 case strings.HasPrefix(declValue, "\""):
LaMont Jones2efc8e22024-05-16 15:45:57 -0700145 // String values mean that the flag workflow is (most likely) either WorkflowManual or WorkflowPrebuilt.
LaMont Jonesb9014c72024-04-11 17:41:15 -0700146 declValue = declValue[1 : len(declValue)-1]
147 flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{declValue}}
148 for _, prefix := range manualFlagNamePrefixes {
149 if strings.HasPrefix(declName, prefix) {
LaMont Jones2efc8e22024-05-16 15:45:57 -0700150 workflow = rc_proto.Workflow(rc_proto.Workflow_WorkflowManual)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700151 break
152 }
153 }
154 case declValue == "False" || declValue == "True":
LaMont Jones2efc8e22024-05-16 15:45:57 -0700155 // Boolean values are WorkflowLaunch flags.
LaMont Jonesb9014c72024-04-11 17:41:15 -0700156 flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{declValue == "True"}}
LaMont Jones2efc8e22024-05-16 15:45:57 -0700157 workflow = rc_proto.Workflow(rc_proto.Workflow_WorkflowLaunch)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700158 case declValue == "None":
LaMont Jones2efc8e22024-05-16 15:45:57 -0700159 // Use WorkflowPrebuilt workflow with no initial value.
LaMont Jonesb9014c72024-04-11 17:41:15 -0700160 default:
161 fmt.Printf("%s: Unexpected value %s=%s\n", path, declName, declValue)
162 }
163 flagDeclaration.Workflow = &workflow
164 if flagDeclaration != nil {
165 declPath := filepath.Join(dir, "flag_declarations", fmt.Sprintf("%s.textproto", declName))
166 err := WriteFile(declPath, flagDeclaration)
167 if err != nil {
168 return err
169 }
170 }
171 }
172 if rootAconfigModule != "" {
173 rootProto := &rc_proto.ReleaseConfig{
174 Name: proto.String("root"),
175 AconfigValueSets: []string{rootAconfigModule},
176 }
177 return WriteFile(filepath.Join(dir, "release_configs", "root.textproto"), rootProto)
178 }
179 return nil
180}
181
182func ProcessBuildConfigs(dir, name string, paths []string, releaseProto *rc_proto.ReleaseConfig) error {
LaMont Jones15788822024-04-24 16:01:44 -0700183 valRegexp, err := regexp.Compile("[[:space:]]+value.\"(?<name>[A-Z_0-9]+)\",[[:space:]]*(?<value>(\"[^\"]*\"|[^\",)]*))")
LaMont Jonesb9014c72024-04-11 17:41:15 -0700184 if err != nil {
185 return err
186 }
187 for _, path := range paths {
188 fmt.Printf("Processing %s\n", path)
189 valIn, err := os.ReadFile(path)
190 if err != nil {
191 fmt.Printf("%s: error: %v\n", path, err)
192 return err
193 }
194 vals := valRegexp.FindAllString(string(valIn), -1)
195 for _, val := range vals {
196 matches := valRegexp.FindStringSubmatch(val)
197 valValue := matches[valRegexp.SubexpIndex("value")]
198 valName := matches[valRegexp.SubexpIndex("name")]
199 flagValue := &rc_proto.FlagValue{
200 Name: proto.String(valName),
201 }
202 switch {
203 case valName == "RELEASE_ACONFIG_VALUE_SETS":
204 flagValue = nil
205 if releaseProto.AconfigValueSets == nil {
206 releaseProto.AconfigValueSets = []string{}
207 }
208 releaseProto.AconfigValueSets = append(releaseProto.AconfigValueSets, valValue[1:len(valValue)-1])
209 case strings.HasPrefix(valValue, "\""):
210 valValue = valValue[1 : len(valValue)-1]
211 flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{valValue}}
212 case valValue == "None":
213 // nothing to do here.
214 case valValue == "True":
215 flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{true}}
216 case valValue == "False":
217 flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{false}}
218 default:
219 fmt.Printf("%s: Unexpected value %s=%s\n", path, valName, valValue)
220 }
221 if flagValue != nil {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700222 if releaseProto.GetAconfigFlagsOnly() {
LaMont Jonesdc868192024-04-30 09:06:20 -0700223 return fmt.Errorf("%s does not allow build flag overrides", RenameNext(name))
224 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700225 valPath := filepath.Join(dir, "flag_values", RenameNext(name), fmt.Sprintf("%s.textproto", valName))
226 err := WriteFile(valPath, flagValue)
227 if err != nil {
228 return err
229 }
230 }
231 }
232 }
233 return err
234}
235
LaMont Jonesdb600992024-04-26 14:19:19 -0700236var (
237 allContainers = func() []string {
238 return []string{"product", "system", "system_ext", "vendor"}
239 }()
240)
241
LaMont Jonesb9014c72024-04-11 17:41:15 -0700242func ProcessReleaseConfigMap(dir string, descriptionMap map[string]string) error {
243 path := filepath.Join(dir, "release_config_map.mk")
244 if _, err := os.Stat(path); err != nil {
245 fmt.Printf("%s not found, ignoring.\n", path)
246 return nil
247 } else {
248 fmt.Printf("Processing %s\n", path)
249 }
LaMont Jones11209e12024-04-19 17:26:27 -0700250 configRegexp, err := regexp.Compile("^..call[[:space:]]+declare-release-config,[[:space:]]+(?<name>[_a-z0-9A-Z]+),[[:space:]]+(?<files>[^,]*)(,[[:space:]]*(?<inherits>.*)|[[:space:]]*)[)]$")
LaMont Jonesb9014c72024-04-11 17:41:15 -0700251 if err != nil {
252 return err
253 }
254 aliasRegexp, err := regexp.Compile("^..call[[:space:]]+alias-release-config,[[:space:]]+(?<name>[_a-z0-9A-Z]+),[[:space:]]+(?<target>[_a-z0-9A-Z]+)")
255 if err != nil {
256 return err
257 }
258
259 mapIn, err := os.ReadFile(path)
260 if err != nil {
261 return err
262 }
263 cleanDir := strings.TrimLeft(dir, "../")
LaMont Jonesdb600992024-04-26 14:19:19 -0700264 var defaultContainers []string
LaMont Jonesb9014c72024-04-11 17:41:15 -0700265 switch {
266 case strings.HasPrefix(cleanDir, "build/") || cleanDir == "vendor/google_shared/build":
LaMont Jonesdb600992024-04-26 14:19:19 -0700267 defaultContainers = allContainers
LaMont Jonesb9014c72024-04-11 17:41:15 -0700268 case cleanDir == "vendor/google/release":
LaMont Jonesdb600992024-04-26 14:19:19 -0700269 defaultContainers = allContainers
LaMont Jonesb9014c72024-04-11 17:41:15 -0700270 default:
LaMont Jonesdb600992024-04-26 14:19:19 -0700271 defaultContainers = []string{"vendor"}
LaMont Jonesb9014c72024-04-11 17:41:15 -0700272 }
LaMont Jonesdb600992024-04-26 14:19:19 -0700273 releaseConfigMap := &rc_proto.ReleaseConfigMap{DefaultContainers: defaultContainers}
LaMont Jonesb9014c72024-04-11 17:41:15 -0700274 // If we find a description for the directory, include it.
275 if description, ok := descriptionMap[cleanDir]; ok {
276 releaseConfigMap.Description = proto.String(description)
277 }
278 lines := strings.Split(string(mapIn), "\n")
279 for _, line := range lines {
280 alias := aliasRegexp.FindStringSubmatch(aliasRegexp.FindString(line))
281 if alias != nil {
282 fmt.Printf("processing alias %s\n", line)
283 name := alias[aliasRegexp.SubexpIndex("name")]
284 target := alias[aliasRegexp.SubexpIndex("target")]
285 if target == "next" {
286 if RenameNext(target) != name {
287 return fmt.Errorf("Unexpected name for next (%s)", RenameNext(target))
288 }
289 target, name = name, target
290 }
291 releaseConfigMap.Aliases = append(releaseConfigMap.Aliases,
292 &rc_proto.ReleaseAlias{
293 Name: proto.String(name),
294 Target: proto.String(target),
295 })
296 }
297 config := configRegexp.FindStringSubmatch(configRegexp.FindString(line))
298 if config == nil {
299 continue
300 }
301 name := config[configRegexp.SubexpIndex("name")]
302 releaseConfig := &rc_proto.ReleaseConfig{
303 Name: proto.String(RenameNext(name)),
304 }
LaMont Jonesdc868192024-04-30 09:06:20 -0700305 if aconfigFlagsOnlyConfigs[name] {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700306 releaseConfig.AconfigFlagsOnly = proto.Bool(true)
LaMont Jonesdc868192024-04-30 09:06:20 -0700307 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700308 configFiles := config[configRegexp.SubexpIndex("files")]
309 files := strings.Split(strings.ReplaceAll(configFiles, "$(local_dir)", dir+"/"), " ")
310 configInherits := config[configRegexp.SubexpIndex("inherits")]
311 if len(configInherits) > 0 {
312 releaseConfig.Inherits = strings.Split(configInherits, " ")
313 }
314 err := ProcessBuildConfigs(dir, name, files, releaseConfig)
315 if err != nil {
316 return err
317 }
318
319 releasePath := filepath.Join(dir, "release_configs", fmt.Sprintf("%s.textproto", RenameNext(name)))
320 err = WriteFile(releasePath, releaseConfig)
321 if err != nil {
322 return err
323 }
324 }
325 return WriteFile(filepath.Join(dir, "release_config_map.textproto"), releaseConfigMap)
326}
327
328func main() {
329 var err error
330 var top string
331 var dirs rc_lib.StringList
332 var namespacesFile string
333 var descriptionsFile string
LaMont Jonesff387ea2024-04-29 16:20:59 -0700334 var debug bool
LaMont Jonese41ea1e2024-04-29 14:16:19 -0700335 defaultTopDir, err := rc_lib.GetTopDir()
LaMont Jonesb9014c72024-04-11 17:41:15 -0700336
LaMont Jonese41ea1e2024-04-29 14:16:19 -0700337 flag.StringVar(&top, "top", defaultTopDir, "path to top of workspace")
LaMont Jonesb9014c72024-04-11 17:41:15 -0700338 flag.Var(&dirs, "dir", "directory to process, relative to the top of the workspace")
339 flag.StringVar(&namespacesFile, "namespaces", "", "location of file with 'flag_name namespace' information")
340 flag.StringVar(&descriptionsFile, "descriptions", "", "location of file with 'directory description' information")
LaMont Jonesff387ea2024-04-29 16:20:59 -0700341 flag.BoolVar(&debug, "debug", false, "turn on debugging output for errors")
LaMont Jonesb9014c72024-04-11 17:41:15 -0700342 flag.Parse()
343
LaMont Jonesff387ea2024-04-29 16:20:59 -0700344 errorExit := func(err error) {
345 if debug {
346 panic(err)
347 }
348 fmt.Fprintf(os.Stderr, "%s\n", err)
349 os.Exit(1)
350 }
351
LaMont Jonesb9014c72024-04-11 17:41:15 -0700352 if err = os.Chdir(top); err != nil {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700353 errorExit(err)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700354 }
355 if len(dirs) == 0 {
356 dirs = rc_lib.StringList{"build/release", "vendor/google_shared/build/release", "vendor/google/release"}
357 }
358
359 namespaceMap := make(map[string]string)
360 if namespacesFile != "" {
361 data, err := os.ReadFile(namespacesFile)
362 if err != nil {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700363 errorExit(err)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700364 }
365 for idx, line := range strings.Split(string(data), "\n") {
366 fields := strings.Split(line, " ")
367 if len(fields) > 2 {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700368 errorExit(fmt.Errorf("line %d: too many fields: %s", idx, line))
LaMont Jonesb9014c72024-04-11 17:41:15 -0700369 }
370 namespaceMap[fields[0]] = fields[1]
371 }
372
373 }
374
375 descriptionMap := make(map[string]string)
376 descriptionMap["build/release"] = "Published open-source flags and declarations"
377 if descriptionsFile != "" {
378 data, err := os.ReadFile(descriptionsFile)
379 if err != nil {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700380 errorExit(err)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700381 }
382 for _, line := range strings.Split(string(data), "\n") {
383 if strings.TrimSpace(line) != "" {
384 fields := strings.SplitN(line, " ", 2)
385 descriptionMap[fields[0]] = fields[1]
386 }
387 }
388
389 }
390
391 for _, dir := range dirs {
392 err = ProcessBuildFlags(dir, namespaceMap)
393 if err != nil {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700394 errorExit(err)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700395 }
396
397 err = ProcessReleaseConfigMap(dir, descriptionMap)
398 if err != nil {
LaMont Jonesff387ea2024-04-29 16:20:59 -0700399 errorExit(err)
LaMont Jonesb9014c72024-04-11 17:41:15 -0700400 }
401 }
402}