blob: e107b9fbb65bdd35c256ef6b40593ec419910c9d [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 (
19 // When a flag declaration has an initial value that is a string, the default workflow is PREBUILT.
20 // If the flag name starts with any of prefixes in manualFlagNamePrefixes, it is MANUAL.
21 manualFlagNamePrefixes []string = []string{
22 "RELEASE_ACONFIG_",
23 "RELEASE_PLATFORM_",
24 }
LaMont Jonesb9014c72024-04-11 17:41:15 -070025
LaMont Jonesdc868192024-04-30 09:06:20 -070026 // Set `aconfig_flags_only: true` in these release configs.
27 aconfigFlagsOnlyConfigs map[string]bool = map[string]bool{
28 "trunk_food": true,
29 }
30
31 // Default namespace value. This is intentionally invalid.
32 defaultFlagNamespace string = "android_UNKNOWN"
33
34 // What is the current name for "next".
35 nextName string = "ap3a"
36)
LaMont Jonesb9014c72024-04-11 17:41:15 -070037
38func RenameNext(name string) string {
39 if name == "next" {
LaMont Jonesdc868192024-04-30 09:06:20 -070040 return nextName
LaMont Jonesb9014c72024-04-11 17:41:15 -070041 }
42 return name
43}
44
45func WriteFile(path string, message proto.Message) error {
46 data, err := prototext.MarshalOptions{Multiline: true}.Marshal(message)
47 if err != nil {
48 return err
49 }
50
51 err = os.MkdirAll(filepath.Dir(path), 0775)
52 if err != nil {
53 return err
54 }
55 return os.WriteFile(path, data, 0644)
56}
57
58func WalkValueFiles(dir string, Func fs.WalkDirFunc) error {
59 valPath := filepath.Join(dir, "build_config")
60 if _, err := os.Stat(valPath); err != nil {
61 fmt.Printf("%s not found, ignoring.\n", valPath)
62 return nil
63 }
64
65 return filepath.WalkDir(valPath, func(path string, d fs.DirEntry, err error) error {
66 if err != nil {
67 return err
68 }
69 if strings.HasSuffix(d.Name(), ".scl") && d.Type().IsRegular() {
70 return Func(path, d, err)
71 }
72 return nil
73 })
74}
75
76func ProcessBuildFlags(dir string, namespaceMap map[string]string) error {
77 var rootAconfigModule string
78
79 path := filepath.Join(dir, "build_flags.scl")
80 if _, err := os.Stat(path); err != nil {
81 fmt.Printf("%s not found, ignoring.\n", path)
82 return nil
83 } else {
84 fmt.Printf("Processing %s\n", path)
85 }
86 commentRegexp, err := regexp.Compile("^[[:space:]]*#(?<comment>.+)")
87 if err != nil {
88 return err
89 }
90 declRegexp, err := regexp.Compile("^[[:space:]]*flag.\"(?<name>[A-Z_0-9]+)\",[[:space:]]*(?<container>[_A-Z]*),[[:space:]]*(?<value>(\"[^\"]*\"|[^\",)]*))")
91 if err != nil {
92 return err
93 }
94 declIn, err := os.ReadFile(path)
95 if err != nil {
96 return err
97 }
98 lines := strings.Split(string(declIn), "\n")
99 var description string
100 for _, line := range lines {
101 if comment := commentRegexp.FindStringSubmatch(commentRegexp.FindString(line)); comment != nil {
102 // Description is the text from any contiguous series of lines before a `flag()` call.
LaMont Jones11209e12024-04-19 17:26:27 -0700103 descLine := strings.TrimSpace(comment[commentRegexp.SubexpIndex("comment")])
104 if !strings.HasPrefix(descLine, "keep-sorted") {
105 description += fmt.Sprintf(" %s", descLine)
106 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700107 continue
108 }
109 matches := declRegexp.FindStringSubmatch(declRegexp.FindString(line))
110 if matches == nil {
111 // The line is neither a comment nor a `flag()` call.
112 // Discard any description we have gathered and process the next line.
113 description = ""
114 continue
115 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700116 declName := matches[declRegexp.SubexpIndex("name")]
LaMont Jonesdb600992024-04-26 14:19:19 -0700117 declValue := matches[declRegexp.SubexpIndex("value")]
LaMont Jonesb9014c72024-04-11 17:41:15 -0700118 description = strings.TrimSpace(description)
LaMont Jonesdb600992024-04-26 14:19:19 -0700119 containers := []string{strings.ToLower(matches[declRegexp.SubexpIndex("container")])}
120 if containers[0] == "all" {
121 containers = []string{"product", "system", "system_ext", "vendor"}
122 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700123 var namespace string
124 var ok bool
125 if namespace, ok = namespaceMap[declName]; !ok {
126 namespace = defaultFlagNamespace
127 }
128 flagDeclaration := &rc_proto.FlagDeclaration{
129 Name: proto.String(declName),
130 Namespace: proto.String(namespace),
131 Description: proto.String(description),
LaMont Jonesdb600992024-04-26 14:19:19 -0700132 Containers: containers,
LaMont Jonesb9014c72024-04-11 17:41:15 -0700133 }
134 description = ""
135 // Most build flags are `workflow: PREBUILT`.
136 workflow := rc_proto.Workflow(rc_proto.Workflow_PREBUILT)
137 switch {
138 case declName == "RELEASE_ACONFIG_VALUE_SETS":
139 rootAconfigModule = declValue[1 : len(declValue)-1]
140 continue
141 case strings.HasPrefix(declValue, "\""):
142 // String values mean that the flag workflow is (most likely) either MANUAL or PREBUILT.
143 declValue = declValue[1 : len(declValue)-1]
144 flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{declValue}}
145 for _, prefix := range manualFlagNamePrefixes {
146 if strings.HasPrefix(declName, prefix) {
147 workflow = rc_proto.Workflow(rc_proto.Workflow_MANUAL)
148 break
149 }
150 }
151 case declValue == "False" || declValue == "True":
152 // Boolean values are LAUNCH flags.
153 flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{declValue == "True"}}
154 workflow = rc_proto.Workflow(rc_proto.Workflow_LAUNCH)
155 case declValue == "None":
156 // Use PREBUILT workflow with no initial value.
157 default:
158 fmt.Printf("%s: Unexpected value %s=%s\n", path, declName, declValue)
159 }
160 flagDeclaration.Workflow = &workflow
161 if flagDeclaration != nil {
162 declPath := filepath.Join(dir, "flag_declarations", fmt.Sprintf("%s.textproto", declName))
163 err := WriteFile(declPath, flagDeclaration)
164 if err != nil {
165 return err
166 }
167 }
168 }
169 if rootAconfigModule != "" {
170 rootProto := &rc_proto.ReleaseConfig{
171 Name: proto.String("root"),
172 AconfigValueSets: []string{rootAconfigModule},
173 }
174 return WriteFile(filepath.Join(dir, "release_configs", "root.textproto"), rootProto)
175 }
176 return nil
177}
178
179func ProcessBuildConfigs(dir, name string, paths []string, releaseProto *rc_proto.ReleaseConfig) error {
LaMont Jones15788822024-04-24 16:01:44 -0700180 valRegexp, err := regexp.Compile("[[:space:]]+value.\"(?<name>[A-Z_0-9]+)\",[[:space:]]*(?<value>(\"[^\"]*\"|[^\",)]*))")
LaMont Jonesb9014c72024-04-11 17:41:15 -0700181 if err != nil {
182 return err
183 }
184 for _, path := range paths {
185 fmt.Printf("Processing %s\n", path)
186 valIn, err := os.ReadFile(path)
187 if err != nil {
188 fmt.Printf("%s: error: %v\n", path, err)
189 return err
190 }
191 vals := valRegexp.FindAllString(string(valIn), -1)
192 for _, val := range vals {
193 matches := valRegexp.FindStringSubmatch(val)
194 valValue := matches[valRegexp.SubexpIndex("value")]
195 valName := matches[valRegexp.SubexpIndex("name")]
196 flagValue := &rc_proto.FlagValue{
197 Name: proto.String(valName),
198 }
199 switch {
200 case valName == "RELEASE_ACONFIG_VALUE_SETS":
201 flagValue = nil
202 if releaseProto.AconfigValueSets == nil {
203 releaseProto.AconfigValueSets = []string{}
204 }
205 releaseProto.AconfigValueSets = append(releaseProto.AconfigValueSets, valValue[1:len(valValue)-1])
206 case strings.HasPrefix(valValue, "\""):
207 valValue = valValue[1 : len(valValue)-1]
208 flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{valValue}}
209 case valValue == "None":
210 // nothing to do here.
211 case valValue == "True":
212 flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{true}}
213 case valValue == "False":
214 flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{false}}
215 default:
216 fmt.Printf("%s: Unexpected value %s=%s\n", path, valName, valValue)
217 }
218 if flagValue != nil {
LaMont Jonesdc868192024-04-30 09:06:20 -0700219 if releaseProto.AconfigFlagsOnly {
220 return fmt.Errorf("%s does not allow build flag overrides", RenameNext(name))
221 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700222 valPath := filepath.Join(dir, "flag_values", RenameNext(name), fmt.Sprintf("%s.textproto", valName))
223 err := WriteFile(valPath, flagValue)
224 if err != nil {
225 return err
226 }
227 }
228 }
229 }
230 return err
231}
232
LaMont Jonesdb600992024-04-26 14:19:19 -0700233var (
234 allContainers = func() []string {
235 return []string{"product", "system", "system_ext", "vendor"}
236 }()
237)
238
LaMont Jonesb9014c72024-04-11 17:41:15 -0700239func ProcessReleaseConfigMap(dir string, descriptionMap map[string]string) error {
240 path := filepath.Join(dir, "release_config_map.mk")
241 if _, err := os.Stat(path); err != nil {
242 fmt.Printf("%s not found, ignoring.\n", path)
243 return nil
244 } else {
245 fmt.Printf("Processing %s\n", path)
246 }
LaMont Jones11209e12024-04-19 17:26:27 -0700247 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 -0700248 if err != nil {
249 return err
250 }
251 aliasRegexp, err := regexp.Compile("^..call[[:space:]]+alias-release-config,[[:space:]]+(?<name>[_a-z0-9A-Z]+),[[:space:]]+(?<target>[_a-z0-9A-Z]+)")
252 if err != nil {
253 return err
254 }
255
256 mapIn, err := os.ReadFile(path)
257 if err != nil {
258 return err
259 }
260 cleanDir := strings.TrimLeft(dir, "../")
LaMont Jonesdb600992024-04-26 14:19:19 -0700261 var defaultContainers []string
LaMont Jonesb9014c72024-04-11 17:41:15 -0700262 switch {
263 case strings.HasPrefix(cleanDir, "build/") || cleanDir == "vendor/google_shared/build":
LaMont Jonesdb600992024-04-26 14:19:19 -0700264 defaultContainers = allContainers
LaMont Jonesb9014c72024-04-11 17:41:15 -0700265 case cleanDir == "vendor/google/release":
LaMont Jonesdb600992024-04-26 14:19:19 -0700266 defaultContainers = allContainers
LaMont Jonesb9014c72024-04-11 17:41:15 -0700267 default:
LaMont Jonesdb600992024-04-26 14:19:19 -0700268 defaultContainers = []string{"vendor"}
LaMont Jonesb9014c72024-04-11 17:41:15 -0700269 }
LaMont Jonesdb600992024-04-26 14:19:19 -0700270 releaseConfigMap := &rc_proto.ReleaseConfigMap{DefaultContainers: defaultContainers}
LaMont Jonesb9014c72024-04-11 17:41:15 -0700271 // If we find a description for the directory, include it.
272 if description, ok := descriptionMap[cleanDir]; ok {
273 releaseConfigMap.Description = proto.String(description)
274 }
275 lines := strings.Split(string(mapIn), "\n")
276 for _, line := range lines {
277 alias := aliasRegexp.FindStringSubmatch(aliasRegexp.FindString(line))
278 if alias != nil {
279 fmt.Printf("processing alias %s\n", line)
280 name := alias[aliasRegexp.SubexpIndex("name")]
281 target := alias[aliasRegexp.SubexpIndex("target")]
282 if target == "next" {
283 if RenameNext(target) != name {
284 return fmt.Errorf("Unexpected name for next (%s)", RenameNext(target))
285 }
286 target, name = name, target
287 }
288 releaseConfigMap.Aliases = append(releaseConfigMap.Aliases,
289 &rc_proto.ReleaseAlias{
290 Name: proto.String(name),
291 Target: proto.String(target),
292 })
293 }
294 config := configRegexp.FindStringSubmatch(configRegexp.FindString(line))
295 if config == nil {
296 continue
297 }
298 name := config[configRegexp.SubexpIndex("name")]
299 releaseConfig := &rc_proto.ReleaseConfig{
300 Name: proto.String(RenameNext(name)),
301 }
LaMont Jonesdc868192024-04-30 09:06:20 -0700302 if aconfigFlagsOnlyConfigs[name] {
303 releaseConfig.AconfigFlagsOnly = true
304 }
LaMont Jonesb9014c72024-04-11 17:41:15 -0700305 configFiles := config[configRegexp.SubexpIndex("files")]
306 files := strings.Split(strings.ReplaceAll(configFiles, "$(local_dir)", dir+"/"), " ")
307 configInherits := config[configRegexp.SubexpIndex("inherits")]
308 if len(configInherits) > 0 {
309 releaseConfig.Inherits = strings.Split(configInherits, " ")
310 }
311 err := ProcessBuildConfigs(dir, name, files, releaseConfig)
312 if err != nil {
313 return err
314 }
315
316 releasePath := filepath.Join(dir, "release_configs", fmt.Sprintf("%s.textproto", RenameNext(name)))
317 err = WriteFile(releasePath, releaseConfig)
318 if err != nil {
319 return err
320 }
321 }
322 return WriteFile(filepath.Join(dir, "release_config_map.textproto"), releaseConfigMap)
323}
324
325func main() {
326 var err error
327 var top string
328 var dirs rc_lib.StringList
329 var namespacesFile string
330 var descriptionsFile string
LaMont Jonese41ea1e2024-04-29 14:16:19 -0700331 defaultTopDir, err := rc_lib.GetTopDir()
LaMont Jonesb9014c72024-04-11 17:41:15 -0700332
LaMont Jonese41ea1e2024-04-29 14:16:19 -0700333 flag.StringVar(&top, "top", defaultTopDir, "path to top of workspace")
LaMont Jonesb9014c72024-04-11 17:41:15 -0700334 flag.Var(&dirs, "dir", "directory to process, relative to the top of the workspace")
335 flag.StringVar(&namespacesFile, "namespaces", "", "location of file with 'flag_name namespace' information")
336 flag.StringVar(&descriptionsFile, "descriptions", "", "location of file with 'directory description' information")
337 flag.Parse()
338
339 if err = os.Chdir(top); err != nil {
340 panic(err)
341 }
342 if len(dirs) == 0 {
343 dirs = rc_lib.StringList{"build/release", "vendor/google_shared/build/release", "vendor/google/release"}
344 }
345
346 namespaceMap := make(map[string]string)
347 if namespacesFile != "" {
348 data, err := os.ReadFile(namespacesFile)
349 if err != nil {
350 panic(err)
351 }
352 for idx, line := range strings.Split(string(data), "\n") {
353 fields := strings.Split(line, " ")
354 if len(fields) > 2 {
355 panic(fmt.Errorf("line %d: too many fields: %s", idx, line))
356 }
357 namespaceMap[fields[0]] = fields[1]
358 }
359
360 }
361
362 descriptionMap := make(map[string]string)
363 descriptionMap["build/release"] = "Published open-source flags and declarations"
364 if descriptionsFile != "" {
365 data, err := os.ReadFile(descriptionsFile)
366 if err != nil {
367 panic(err)
368 }
369 for _, line := range strings.Split(string(data), "\n") {
370 if strings.TrimSpace(line) != "" {
371 fields := strings.SplitN(line, " ", 2)
372 descriptionMap[fields[0]] = fields[1]
373 }
374 }
375
376 }
377
378 for _, dir := range dirs {
379 err = ProcessBuildFlags(dir, namespaceMap)
380 if err != nil {
381 panic(err)
382 }
383
384 err = ProcessReleaseConfigMap(dir, descriptionMap)
385 if err != nil {
386 panic(err)
387 }
388 }
389}