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