xref: /aosp_15_r20/build/soong/cmd/release_config/release_config_lib/release_config.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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	"os"
21	"path/filepath"
22	"regexp"
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// One directory's contribution to the a release config.
32type ReleaseConfigContribution struct {
33	// Path of the file providing this config contribution.
34	path string
35
36	// The index of the config directory where this release config
37	// contribution was declared.
38	// Flag values cannot be set in a location with a lower index.
39	DeclarationIndex int
40
41	// Protobufs relevant to the config.
42	proto rc_proto.ReleaseConfig
43
44	FlagValues []*FlagValue
45}
46
47// A generated release config.
48type ReleaseConfig struct {
49	// the Name of the release config
50	Name string
51
52	// The index of the config directory where this release config was
53	// first declared.
54	// Flag values cannot be set in a location with a lower index.
55	DeclarationIndex int
56
57	// What contributes to this config.
58	Contributions []*ReleaseConfigContribution
59
60	// Aliases for this release
61	OtherNames []string
62
63	// The names of release configs that we inherit
64	InheritNames []string
65
66	// True if this release config only allows inheritance and aconfig flag
67	// overrides. Build flag value overrides are an error.
68	AconfigFlagsOnly bool
69
70	// Unmarshalled flag artifacts
71	FlagArtifacts FlagArtifacts
72
73	// The files used by this release config
74	FilesUsedMap map[string]bool
75
76	// Generated release config
77	ReleaseConfigArtifact *rc_proto.ReleaseConfigArtifact
78
79	// We have begun compiling this release config.
80	compileInProgress bool
81
82	// Partitioned artifacts for {partition}/etc/build_flags.json
83	PartitionBuildFlags map[string]*rc_proto.FlagArtifacts
84
85	// Prior stage(s) for flag advancement (during development).
86	// Once a flag has met criteria in a prior stage, it can advance to this one.
87	PriorStagesMap map[string]bool
88
89	// What type of release config is this?  This should never be
90	// ReleaseConfigType_CONFIG_TYPE_UNSPECIFIED.
91	ReleaseConfigType rc_proto.ReleaseConfigType
92}
93
94// If true, this is a proper release config that can be used in "lunch".
95func (config *ReleaseConfig) isConfigListable() bool {
96	switch config.ReleaseConfigType {
97	case rc_proto.ReleaseConfigType_RELEASE_CONFIG:
98		return true
99	}
100
101	return false
102}
103
104// If true, this ReleaseConfigType may only inherit from a ReleaseConfig of the
105// same ReleaseConfigType.
106var ReleaseConfigInheritanceDenyMap = map[rc_proto.ReleaseConfigType]bool{
107	rc_proto.ReleaseConfigType_BUILD_VARIANT: true,
108}
109
110func ReleaseConfigFactory(name string, index int) (c *ReleaseConfig) {
111	return &ReleaseConfig{
112		Name:             name,
113		DeclarationIndex: index,
114		FilesUsedMap:     make(map[string]bool),
115		PriorStagesMap:   make(map[string]bool),
116	}
117}
118
119func (config *ReleaseConfig) InheritConfig(iConfig *ReleaseConfig) error {
120	if config.ReleaseConfigType != iConfig.ReleaseConfigType && ReleaseConfigInheritanceDenyMap[config.ReleaseConfigType] {
121		return fmt.Errorf("Release config %s (type '%s') cannot inherit from %s (type '%s')",
122			config.Name, config.ReleaseConfigType, iConfig.Name, iConfig.ReleaseConfigType)
123	}
124	for f := range iConfig.FilesUsedMap {
125		config.FilesUsedMap[f] = true
126	}
127	for _, fa := range iConfig.FlagArtifacts {
128		name := *fa.FlagDeclaration.Name
129		myFa, ok := config.FlagArtifacts[name]
130		if !ok {
131			return fmt.Errorf("Could not inherit flag %s from %s", name, iConfig.Name)
132		}
133		if fa.Redacted {
134			myFa.Redact()
135		}
136		if name == "RELEASE_ACONFIG_VALUE_SETS" {
137			// If there is a value assigned, add the trace.
138			if len(fa.Value.GetStringValue()) > 0 {
139				myFa.Traces = append(myFa.Traces, fa.Traces...)
140				myFa.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{
141					myFa.Value.GetStringValue() + " " + fa.Value.GetStringValue()}}
142			}
143		} else if len(fa.Traces) > 1 {
144			// A value was assigned. Set our value.
145			myFa.Traces = append(myFa.Traces, fa.Traces[1:]...)
146			myFa.Value = fa.Value
147		}
148	}
149	return nil
150}
151
152func (config *ReleaseConfig) GetSortedFileList() []string {
153	return SortedMapKeys(config.FilesUsedMap)
154}
155
156func (config *ReleaseConfig) GenerateReleaseConfig(configs *ReleaseConfigs) error {
157	if config.ReleaseConfigArtifact != nil {
158		return nil
159	}
160	if config.compileInProgress {
161		return fmt.Errorf("Loop detected for release config %s", config.Name)
162	}
163	config.compileInProgress = true
164	isRoot := config.Name == "root"
165
166	// Is this a build-prefix release config, such as 'ap3a'?
167	isBuildPrefix, err := regexp.MatchString("^[a-z][a-z][0-9][0-9a-z]$", config.Name)
168	if err != nil {
169		return err
170	}
171	// Start with only the flag declarations.
172	config.FlagArtifacts = configs.FlagArtifacts.Clone()
173	releaseAconfigValueSets := config.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"]
174	releasePlatformVersion := config.FlagArtifacts["RELEASE_PLATFORM_VERSION"]
175
176	// Generate any configs we need to inherit.  This will detect loops in
177	// the config.
178	contributionsToApply := []*ReleaseConfigContribution{}
179	myInherits := []string{}
180	myInheritsSet := make(map[string]bool)
181	if config.ReleaseConfigType == rc_proto.ReleaseConfigType_RELEASE_CONFIG {
182		if _, err = configs.GetReleaseConfigStrict("root"); err == nil {
183			config.InheritNames = append([]string{"root"}, config.InheritNames...)
184		}
185	}
186	for _, inherit := range config.InheritNames {
187		if _, ok := myInheritsSet[inherit]; ok {
188			continue
189		}
190		if isBuildPrefix && configs.Aliases[inherit] != nil {
191			return fmt.Errorf("%s cannot inherit from alias %s", config.Name, inherit)
192		}
193		myInherits = append(myInherits, inherit)
194		myInheritsSet[inherit] = true
195		// TODO: there are some configs that rely on vgsbr being
196		// present on branches where it isn't. Once the broken configs
197		// are fixed, we can be more strict.  In the meantime, they
198		// will wind up inheriting `trunk_stable` instead of the
199		// non-existent (alias) that they reference today.  Once fixed,
200		// this becomes:
201		//    iConfig, err := configs.GetReleaseConfigStrict(inherit)
202		iConfig, err := configs.GetReleaseConfig(inherit)
203		if err != nil {
204			return err
205		}
206		err = iConfig.GenerateReleaseConfig(configs)
207		if err != nil {
208			return err
209		}
210		err = config.InheritConfig(iConfig)
211		if err != nil {
212			return err
213		}
214	}
215
216	// If we inherited nothing, then we need to mark the global files as used for this
217	// config.  If we inherited, then we already marked them as part of inheritance.
218	if len(config.InheritNames) == 0 {
219		for f := range configs.FilesUsedMap {
220			config.FilesUsedMap[f] = true
221		}
222	}
223
224	contributionsToApply = append(contributionsToApply, config.Contributions...)
225
226	workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL)
227	myDirsMap := make(map[int]bool)
228	myValueDirsMap := make(map[int]bool)
229	if isBuildPrefix && releasePlatformVersion != nil {
230		if MarshalValue(releasePlatformVersion.Value) != strings.ToUpper(config.Name) {
231			value := FlagValue{
232				path: config.Contributions[0].path,
233				proto: rc_proto.FlagValue{
234					Name:  releasePlatformVersion.FlagDeclaration.Name,
235					Value: UnmarshalValue(strings.ToUpper(config.Name)),
236				},
237			}
238			if err := releasePlatformVersion.UpdateValue(value); err != nil {
239				return err
240			}
241		}
242	}
243	for _, contrib := range contributionsToApply {
244		contribAconfigValueSets := []string{}
245		// Gather the aconfig_value_sets from this contribution, allowing duplicates for simplicity.
246		for _, v := range contrib.proto.AconfigValueSets {
247			contribAconfigValueSets = append(contribAconfigValueSets, v)
248		}
249		contribAconfigValueSetsString := strings.Join(contribAconfigValueSets, " ")
250		releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{
251			releaseAconfigValueSets.Value.GetStringValue() + " " + contribAconfigValueSetsString}}
252		releaseAconfigValueSets.Traces = append(
253			releaseAconfigValueSets.Traces,
254			&rc_proto.Tracepoint{
255				Source: proto.String(contrib.path),
256				Value:  &rc_proto.Value{Val: &rc_proto.Value_StringValue{contribAconfigValueSetsString}},
257			})
258
259		for _, priorStage := range contrib.proto.PriorStages {
260			config.PriorStagesMap[priorStage] = true
261		}
262		myDirsMap[contrib.DeclarationIndex] = true
263		// This path *could* provide a value for this release config.
264		myValueDirsMap[contrib.DeclarationIndex] = true
265		if config.AconfigFlagsOnly {
266			// AconfigFlagsOnly allows very very few build flag values, all of them are part of aconfig flags.
267			allowedFlags := map[string]bool{
268				"RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS": true,
269			}
270			for _, fv := range contrib.FlagValues {
271				if !allowedFlags[*fv.proto.Name] {
272					return fmt.Errorf("%s does not allow build flag overrides", config.Name)
273				}
274			}
275		}
276		for _, value := range contrib.FlagValues {
277			name := *value.proto.Name
278			fa, ok := config.FlagArtifacts[name]
279			if !ok {
280				return fmt.Errorf("Setting value for undefined flag %s in %s\n", name, value.path)
281			}
282			// Record that flag declarations from fa.DeclarationIndex were included in this release config.
283			myDirsMap[fa.DeclarationIndex] = true
284			// Do not set myValueDirsMap, since it just records that we *could* provide values here.
285			if fa.DeclarationIndex > contrib.DeclarationIndex {
286				// Setting location is to the left of declaration.
287				return fmt.Errorf("Setting value for flag %s (declared in %s) not allowed in %s\n",
288					name, filepath.Dir(configs.ReleaseConfigMaps[fa.DeclarationIndex].path), value.path)
289			}
290			if isRoot && *fa.FlagDeclaration.Workflow != workflowManual {
291				// The "root" release config can only contain workflow: MANUAL flags.
292				return fmt.Errorf("Setting value for non-MANUAL flag %s is not allowed in %s", name, value.path)
293			}
294			if err := fa.UpdateValue(*value); err != nil {
295				return err
296			}
297		}
298	}
299
300	if config.ReleaseConfigType == rc_proto.ReleaseConfigType_RELEASE_CONFIG {
301		inheritBuildVariant := func() error {
302			build_variant := os.Getenv("TARGET_BUILD_VARIANT")
303			if build_variant == "" || config.Name == build_variant {
304				return nil
305			}
306			variant, err := configs.GetReleaseConfigStrict(build_variant)
307			if err != nil {
308				// Failure to find the build-variant release config is
309				// not an error.
310				return nil
311			}
312			if variant.ReleaseConfigType != rc_proto.ReleaseConfigType_BUILD_VARIANT {
313				return nil
314			}
315			if err = variant.GenerateReleaseConfig(configs); err != nil {
316				return err
317			}
318			return config.InheritConfig(variant)
319		}
320
321		useVariant, ok := config.FlagArtifacts["RELEASE_BUILD_USE_VARIANT_FLAGS"]
322		if ok && MarshalValue(useVariant.Value) != "" {
323			if err = inheritBuildVariant(); err != nil {
324				return err
325			}
326		}
327	}
328
329	// Now remove any duplicates from the actual value of RELEASE_ACONFIG_VALUE_SETS
330	myAconfigValueSets := []string{}
331	myAconfigValueSetsMap := map[string]bool{}
332	for _, v := range strings.Split(releaseAconfigValueSets.Value.GetStringValue(), " ") {
333		if v == "" || myAconfigValueSetsMap[v] {
334			continue
335		}
336		myAconfigValueSetsMap[v] = true
337		myAconfigValueSets = append(myAconfigValueSets, v)
338	}
339	releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.TrimSpace(strings.Join(myAconfigValueSets, " "))}}
340
341	directories := []string{}
342	valueDirectories := []string{}
343	// These path prefixes are exclusive for a release config.
344	// "A release config shall exist in at most one of these."
345	// If we find a benefit to generalizing this, we can do so at that time.
346	exclusiveDirPrefixes := []string{
347		"build/release",
348		"vendor/google_shared/build/release",
349	}
350	var exclusiveDir string
351	for idx, confDir := range configs.configDirs {
352		if _, ok := myDirsMap[idx]; ok {
353			directories = append(directories, confDir)
354		}
355		if _, ok := myValueDirsMap[idx]; ok {
356			for _, dir := range exclusiveDirPrefixes {
357				if strings.HasPrefix(confDir, dir) {
358					if exclusiveDir != "" && !strings.HasPrefix(exclusiveDir, dir) {
359						return fmt.Errorf("%s is declared in both %s and %s",
360							config.Name, exclusiveDir, confDir)
361					}
362					exclusiveDir = confDir
363				}
364			}
365			valueDirectories = append(valueDirectories, confDir)
366		}
367	}
368
369	// Now build the per-partition artifacts
370	config.PartitionBuildFlags = make(map[string]*rc_proto.FlagArtifacts)
371	for _, v := range config.FlagArtifacts {
372		artifact, err := v.MarshalWithoutTraces()
373		if err != nil {
374			return err
375		}
376		// Redacted flags return nil when rendered.
377		if artifact == nil {
378			continue
379		}
380		for _, container := range v.FlagDeclaration.Containers {
381			if _, ok := config.PartitionBuildFlags[container]; !ok {
382				config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{}
383			}
384			config.PartitionBuildFlags[container].Flags = append(config.PartitionBuildFlags[container].Flags, artifact)
385		}
386	}
387	config.ReleaseConfigArtifact = &rc_proto.ReleaseConfigArtifact{
388		Name:       proto.String(config.Name),
389		OtherNames: config.OtherNames,
390		Flags: func() []*rc_proto.FlagArtifact {
391			ret := []*rc_proto.FlagArtifact{}
392			for _, flagName := range config.FlagArtifacts.SortedFlagNames() {
393				flag := config.FlagArtifacts[flagName]
394				ret = append(ret, &rc_proto.FlagArtifact{
395					FlagDeclaration: flag.FlagDeclaration,
396					Traces:          flag.Traces,
397					Value:           flag.Value,
398				})
399			}
400			return ret
401		}(),
402		AconfigValueSets:  myAconfigValueSets,
403		Inherits:          myInherits,
404		Directories:       directories,
405		ValueDirectories:  valueDirectories,
406		PriorStages:       SortedMapKeys(config.PriorStagesMap),
407		ReleaseConfigType: config.ReleaseConfigType.Enum(),
408	}
409
410	config.compileInProgress = false
411	return nil
412}
413
414// Write the makefile for this targetRelease.
415func (config *ReleaseConfig) WriteMakefile(outFile, targetRelease string, configs *ReleaseConfigs) error {
416	makeVars := make(map[string]string)
417
418	myFlagArtifacts := config.FlagArtifacts.Clone()
419
420	// Add any RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS variables.
421	var extraAconfigReleaseConfigs []string
422	if extraAconfigValueSetsValue, ok := config.FlagArtifacts["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok {
423		if val := MarshalValue(extraAconfigValueSetsValue.Value); len(val) > 0 {
424			extraAconfigReleaseConfigs = strings.Split(val, " ")
425		}
426	}
427	for _, rcName := range extraAconfigReleaseConfigs {
428		rc, err := configs.GetReleaseConfigStrict(rcName)
429		if err != nil {
430			return err
431		}
432		myFlagArtifacts["RELEASE_ACONFIG_VALUE_SETS_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"]
433		myFlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION"]
434	}
435
436	// Sort the flags by name first.
437	names := myFlagArtifacts.SortedFlagNames()
438	partitions := make(map[string][]string)
439
440	vNames := []string{}
441	addVar := func(name, suffix, value string) {
442		fullName := fmt.Sprintf("_ALL_RELEASE_FLAGS.%s.%s", name, suffix)
443		vNames = append(vNames, fullName)
444		makeVars[fullName] = value
445	}
446
447	for _, name := range names {
448		flag := myFlagArtifacts[name]
449		decl := flag.FlagDeclaration
450
451		for _, container := range decl.Containers {
452			partitions[container] = append(partitions[container], name)
453		}
454		value := MarshalValue(flag.Value)
455		makeVars[name] = value
456		addVar(name, "TYPE", ValueType(flag.Value))
457		addVar(name, "PARTITIONS", strings.Join(decl.Containers, " "))
458		addVar(name, "DEFAULT", MarshalValue(decl.Value))
459		addVar(name, "VALUE", value)
460		addVar(name, "DECLARED_IN", *flag.Traces[0].Source)
461		addVar(name, "SET_IN", *flag.Traces[len(flag.Traces)-1].Source)
462		addVar(name, "NAMESPACE", *decl.Namespace)
463	}
464	pNames := []string{}
465	for k := range partitions {
466		pNames = append(pNames, k)
467	}
468	slices.Sort(pNames)
469
470	// Now sort the make variables, and output them.
471	slices.Sort(vNames)
472
473	// Write the flags as:
474	//   _ALL_RELELASE_FLAGS
475	//   _ALL_RELEASE_FLAGS.PARTITIONS.*
476	//   all _ALL_RELEASE_FLAGS.*, sorted by name
477	//   Final flag values, sorted by name.
478	data := fmt.Sprintf("# TARGET_RELEASE=%s\n", config.Name)
479	if targetRelease != config.Name {
480		data += fmt.Sprintf("# User specified TARGET_RELEASE=%s\n", targetRelease)
481	}
482	// As it stands this list is not per-product, but conceptually it is, and will be.
483	data += fmt.Sprintf("ALL_RELEASE_CONFIGS_FOR_PRODUCT :=$= %s\n", strings.Join(configs.GetAllReleaseNames(), " "))
484	data += fmt.Sprintf("_used_files := %s\n", strings.Join(config.GetSortedFileList(), " "))
485	data += fmt.Sprintf("_ALL_RELEASE_FLAGS :=$= %s\n", strings.Join(names, " "))
486	for _, pName := range pNames {
487		data += fmt.Sprintf("_ALL_RELEASE_FLAGS.PARTITIONS.%s :=$= %s\n", pName, strings.Join(partitions[pName], " "))
488	}
489	for _, vName := range vNames {
490		data += fmt.Sprintf("%s :=$= %s\n", vName, makeVars[vName])
491	}
492	data += "\n\n# Values for all build flags\n"
493	for _, name := range names {
494		data += fmt.Sprintf("%s :=$= %s\n", name, makeVars[name])
495	}
496	return os.WriteFile(outFile, []byte(data), 0644)
497}
498
499func (config *ReleaseConfig) WritePartitionBuildFlags(outDir string) error {
500	var err error
501	for partition, flags := range config.PartitionBuildFlags {
502		slices.SortFunc(flags.Flags, func(a, b *rc_proto.FlagArtifact) int {
503			return cmp.Compare(*a.FlagDeclaration.Name, *b.FlagDeclaration.Name)
504		})
505		// The json file name must not be modified as this is read from
506		// build_flags_json module
507		if err = WriteMessage(filepath.Join(outDir, fmt.Sprintf("build_flags_%s.json", partition)), flags); err != nil {
508			return err
509		}
510	}
511	return nil
512}
513