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