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	"sort"
25	"strings"
26
27	rc_proto "android/soong/cmd/release_config/release_config_proto"
28
29	"google.golang.org/protobuf/proto"
30)
31
32// One directory's contribution to the a release config.
33type ReleaseConfigContribution struct {
34	// Path of the file providing this config contribution.
35	path string
36
37	// The index of the config directory where this release config
38	// contribution was declared.
39	// Flag values cannot be set in a location with a lower index.
40	DeclarationIndex int
41
42	// Protobufs relevant to the config.
43	proto rc_proto.ReleaseConfig
44
45	FlagValues []*FlagValue
46}
47
48// A generated release config.
49type ReleaseConfig struct {
50	// the Name of the release config
51	Name string
52
53	// The index of the config directory where this release config was
54	// first declared.
55	// Flag values cannot be set in a location with a lower index.
56	DeclarationIndex int
57
58	// What contributes to this config.
59	Contributions []*ReleaseConfigContribution
60
61	// Aliases for this release
62	OtherNames []string
63
64	// The names of release configs that we inherit
65	InheritNames []string
66
67	// True if this release config only allows inheritance and aconfig flag
68	// overrides. Build flag value overrides are an error.
69	AconfigFlagsOnly bool
70
71	// Unmarshalled flag artifacts
72	FlagArtifacts FlagArtifacts
73
74	// The files used by this release config
75	FilesUsedMap map[string]bool
76
77	// Generated release config
78	ReleaseConfigArtifact *rc_proto.ReleaseConfigArtifact
79
80	// We have begun compiling this release config.
81	compileInProgress bool
82
83	// Partitioned artifacts for {partition}/etc/build_flags.json
84	PartitionBuildFlags map[string]*rc_proto.FlagArtifacts
85
86	// Prior stage(s) for flag advancement (during development).
87	// Once a flag has met criteria in a prior stage, it can advance to this one.
88	PriorStagesMap map[string]bool
89}
90
91func ReleaseConfigFactory(name string, index int) (c *ReleaseConfig) {
92	return &ReleaseConfig{
93		Name:             name,
94		DeclarationIndex: index,
95		FilesUsedMap:     make(map[string]bool),
96		PriorStagesMap:   make(map[string]bool),
97	}
98}
99
100func (config *ReleaseConfig) InheritConfig(iConfig *ReleaseConfig) error {
101	for f := range iConfig.FilesUsedMap {
102		config.FilesUsedMap[f] = true
103	}
104	for _, fa := range iConfig.FlagArtifacts {
105		name := *fa.FlagDeclaration.Name
106		myFa, ok := config.FlagArtifacts[name]
107		if !ok {
108			return fmt.Errorf("Could not inherit flag %s from %s", name, iConfig.Name)
109		}
110		if name == "RELEASE_ACONFIG_VALUE_SETS" {
111			// If there is a value assigned, add the trace.
112			if len(fa.Value.GetStringValue()) > 0 {
113				myFa.Traces = append(myFa.Traces, fa.Traces...)
114				myFa.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{
115					myFa.Value.GetStringValue() + " " + fa.Value.GetStringValue()}}
116			}
117		} else if len(fa.Traces) > 1 {
118			// A value was assigned. Set our value.
119			myFa.Traces = append(myFa.Traces, fa.Traces[1:]...)
120			myFa.Value = fa.Value
121		}
122	}
123	return nil
124}
125
126func (config *ReleaseConfig) GetSortedFileList() []string {
127	return SortedMapKeys(config.FilesUsedMap)
128}
129
130func (config *ReleaseConfig) GenerateReleaseConfig(configs *ReleaseConfigs) error {
131	if config.ReleaseConfigArtifact != nil {
132		return nil
133	}
134	if config.compileInProgress {
135		return fmt.Errorf("Loop detected for release config %s", config.Name)
136	}
137	config.compileInProgress = true
138	isRoot := config.Name == "root"
139
140	// Is this a build-prefix release config, such as 'ap3a'?
141	isBuildPrefix, err := regexp.MatchString("^[a-z][a-z][0-9][0-9a-z]$", config.Name)
142	if err != nil {
143		return err
144	}
145	// Start with only the flag declarations.
146	config.FlagArtifacts = configs.FlagArtifacts.Clone()
147	releaseAconfigValueSets := config.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"]
148	releasePlatformVersion := config.FlagArtifacts["RELEASE_PLATFORM_VERSION"]
149
150	// Generate any configs we need to inherit.  This will detect loops in
151	// the config.
152	contributionsToApply := []*ReleaseConfigContribution{}
153	myInherits := []string{}
154	myInheritsSet := make(map[string]bool)
155	// If there is a "root" release config, it is the start of every inheritance chain.
156	_, err = configs.GetReleaseConfig("root")
157	if err == nil && !isRoot {
158		config.InheritNames = append([]string{"root"}, config.InheritNames...)
159	}
160	for _, inherit := range config.InheritNames {
161		if _, ok := myInheritsSet[inherit]; ok {
162			continue
163		}
164		if isBuildPrefix && configs.Aliases[inherit] != nil {
165			return fmt.Errorf("%s cannot inherit from alias %s", config.Name, inherit)
166		}
167		myInherits = append(myInherits, inherit)
168		myInheritsSet[inherit] = true
169		iConfig, err := configs.GetReleaseConfig(inherit)
170		if err != nil {
171			return err
172		}
173		err = iConfig.GenerateReleaseConfig(configs)
174		if err != nil {
175			return err
176		}
177		err = config.InheritConfig(iConfig)
178		if err != nil {
179			return err
180		}
181	}
182
183	// If we inherited nothing, then we need to mark the global files as used for this
184	// config.  If we inherited, then we already marked them as part of inheritance.
185	if len(config.InheritNames) == 0 {
186		for f := range configs.FilesUsedMap {
187			config.FilesUsedMap[f] = true
188		}
189	}
190
191	contributionsToApply = append(contributionsToApply, config.Contributions...)
192
193	workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL)
194	myDirsMap := make(map[int]bool)
195	if isBuildPrefix && releasePlatformVersion != nil {
196		if MarshalValue(releasePlatformVersion.Value) != strings.ToUpper(config.Name) {
197			value := FlagValue{
198				path: config.Contributions[0].path,
199				proto: rc_proto.FlagValue{
200					Name:  releasePlatformVersion.FlagDeclaration.Name,
201					Value: UnmarshalValue(strings.ToUpper(config.Name)),
202				},
203			}
204			if err := releasePlatformVersion.UpdateValue(value); err != nil {
205				return err
206			}
207		}
208	}
209	for _, contrib := range contributionsToApply {
210		contribAconfigValueSets := []string{}
211		// Gather the aconfig_value_sets from this contribution, allowing duplicates for simplicity.
212		for _, v := range contrib.proto.AconfigValueSets {
213			contribAconfigValueSets = append(contribAconfigValueSets, v)
214		}
215		contribAconfigValueSetsString := strings.Join(contribAconfigValueSets, " ")
216		releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{
217			releaseAconfigValueSets.Value.GetStringValue() + " " + contribAconfigValueSetsString}}
218		releaseAconfigValueSets.Traces = append(
219			releaseAconfigValueSets.Traces,
220			&rc_proto.Tracepoint{
221				Source: proto.String(contrib.path),
222				Value:  &rc_proto.Value{Val: &rc_proto.Value_StringValue{contribAconfigValueSetsString}},
223			})
224
225		for _, priorStage := range contrib.proto.PriorStages {
226			config.PriorStagesMap[priorStage] = true
227		}
228		myDirsMap[contrib.DeclarationIndex] = true
229		if config.AconfigFlagsOnly && len(contrib.FlagValues) > 0 {
230			return fmt.Errorf("%s does not allow build flag overrides", config.Name)
231		}
232		for _, value := range contrib.FlagValues {
233			name := *value.proto.Name
234			fa, ok := config.FlagArtifacts[name]
235			if !ok {
236				return fmt.Errorf("Setting value for undefined flag %s in %s\n", name, value.path)
237			}
238			myDirsMap[fa.DeclarationIndex] = true
239			if fa.DeclarationIndex > contrib.DeclarationIndex {
240				// Setting location is to the left of declaration.
241				return fmt.Errorf("Setting value for flag %s not allowed in %s\n", name, value.path)
242			}
243			if isRoot && *fa.FlagDeclaration.Workflow != workflowManual {
244				// The "root" release config can only contain workflow: MANUAL flags.
245				return fmt.Errorf("Setting value for non-MANUAL flag %s is not allowed in %s", name, value.path)
246			}
247			if err := fa.UpdateValue(*value); err != nil {
248				return err
249			}
250			if fa.Redacted {
251				delete(config.FlagArtifacts, name)
252			}
253		}
254	}
255	// Now remove any duplicates from the actual value of RELEASE_ACONFIG_VALUE_SETS
256	myAconfigValueSets := []string{}
257	myAconfigValueSetsMap := map[string]bool{}
258	for _, v := range strings.Split(releaseAconfigValueSets.Value.GetStringValue(), " ") {
259		if myAconfigValueSetsMap[v] {
260			continue
261		}
262		myAconfigValueSetsMap[v] = true
263		myAconfigValueSets = append(myAconfigValueSets, v)
264	}
265	releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.TrimSpace(strings.Join(myAconfigValueSets, " "))}}
266
267	directories := []string{}
268	for idx, confDir := range configs.configDirs {
269		if _, ok := myDirsMap[idx]; ok {
270			directories = append(directories, confDir)
271		}
272	}
273
274	// Now build the per-partition artifacts
275	config.PartitionBuildFlags = make(map[string]*rc_proto.FlagArtifacts)
276	for _, v := range config.FlagArtifacts {
277		artifact, err := v.MarshalWithoutTraces()
278		if err != nil {
279			return err
280		}
281		for _, container := range v.FlagDeclaration.Containers {
282			if _, ok := config.PartitionBuildFlags[container]; !ok {
283				config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{}
284			}
285			config.PartitionBuildFlags[container].FlagArtifacts = append(config.PartitionBuildFlags[container].FlagArtifacts, artifact)
286		}
287	}
288	config.ReleaseConfigArtifact = &rc_proto.ReleaseConfigArtifact{
289		Name:       proto.String(config.Name),
290		OtherNames: config.OtherNames,
291		FlagArtifacts: func() []*rc_proto.FlagArtifact {
292			ret := []*rc_proto.FlagArtifact{}
293			flagNames := []string{}
294			for k := range config.FlagArtifacts {
295				flagNames = append(flagNames, k)
296			}
297			sort.Strings(flagNames)
298			for _, flagName := range flagNames {
299				flag := config.FlagArtifacts[flagName]
300				ret = append(ret, &rc_proto.FlagArtifact{
301					FlagDeclaration: flag.FlagDeclaration,
302					Traces:          flag.Traces,
303					Value:           flag.Value,
304				})
305			}
306			return ret
307		}(),
308		AconfigValueSets: myAconfigValueSets,
309		Inherits:         myInherits,
310		Directories:      directories,
311		PriorStages:      SortedMapKeys(config.PriorStagesMap),
312	}
313
314	config.compileInProgress = false
315	return nil
316}
317
318// Write the makefile for this targetRelease.
319func (config *ReleaseConfig) WriteMakefile(outFile, targetRelease string, configs *ReleaseConfigs) error {
320	makeVars := make(map[string]string)
321
322	myFlagArtifacts := config.FlagArtifacts.Clone()
323	// Sort the flags by name first.
324	names := myFlagArtifacts.SortedFlagNames()
325	partitions := make(map[string][]string)
326
327	vNames := []string{}
328	addVar := func(name, suffix, value string) {
329		fullName := fmt.Sprintf("_ALL_RELEASE_FLAGS.%s.%s", name, suffix)
330		vNames = append(vNames, fullName)
331		makeVars[fullName] = value
332	}
333
334	for _, name := range names {
335		flag := myFlagArtifacts[name]
336		decl := flag.FlagDeclaration
337
338		for _, container := range decl.Containers {
339			partitions[container] = append(partitions[container], name)
340		}
341		value := MarshalValue(flag.Value)
342		makeVars[name] = value
343		addVar(name, "TYPE", ValueType(flag.Value))
344		addVar(name, "PARTITIONS", strings.Join(decl.Containers, " "))
345		addVar(name, "DEFAULT", MarshalValue(decl.Value))
346		addVar(name, "VALUE", value)
347		addVar(name, "DECLARED_IN", *flag.Traces[0].Source)
348		addVar(name, "SET_IN", *flag.Traces[len(flag.Traces)-1].Source)
349		addVar(name, "NAMESPACE", *decl.Namespace)
350	}
351	pNames := []string{}
352	for k := range partitions {
353		pNames = append(pNames, k)
354	}
355	slices.Sort(pNames)
356
357	// Now sort the make variables, and output them.
358	slices.Sort(vNames)
359
360	// Write the flags as:
361	//   _ALL_RELELASE_FLAGS
362	//   _ALL_RELEASE_FLAGS.PARTITIONS.*
363	//   all _ALL_RELEASE_FLAGS.*, sorted by name
364	//   Final flag values, sorted by name.
365	data := fmt.Sprintf("# TARGET_RELEASE=%s\n", config.Name)
366	if targetRelease != config.Name {
367		data += fmt.Sprintf("# User specified TARGET_RELEASE=%s\n", targetRelease)
368	}
369	// As it stands this list is not per-product, but conceptually it is, and will be.
370	data += fmt.Sprintf("ALL_RELEASE_CONFIGS_FOR_PRODUCT :=$= %s\n", strings.Join(configs.GetAllReleaseNames(), " "))
371	data += fmt.Sprintf("_used_files := %s\n", strings.Join(config.GetSortedFileList(), " "))
372	data += fmt.Sprintf("_ALL_RELEASE_FLAGS :=$= %s\n", strings.Join(names, " "))
373	for _, pName := range pNames {
374		data += fmt.Sprintf("_ALL_RELEASE_FLAGS.PARTITIONS.%s :=$= %s\n", pName, strings.Join(partitions[pName], " "))
375	}
376	for _, vName := range vNames {
377		data += fmt.Sprintf("%s :=$= %s\n", vName, makeVars[vName])
378	}
379	data += "\n\n# Values for all build flags\n"
380	for _, name := range names {
381		data += fmt.Sprintf("%s :=$= %s\n", name, makeVars[name])
382	}
383	return os.WriteFile(outFile, []byte(data), 0644)
384}
385
386func (config *ReleaseConfig) WritePartitionBuildFlags(outDir string) error {
387	var err error
388	for partition, flags := range config.PartitionBuildFlags {
389		slices.SortFunc(flags.FlagArtifacts, func(a, b *rc_proto.FlagArtifact) int {
390			return cmp.Compare(*a.FlagDeclaration.Name, *b.FlagDeclaration.Name)
391		})
392		// The json file name must not be modified as this is read from
393		// build_flags_json module
394		if err = WriteMessage(filepath.Join(outDir, fmt.Sprintf("build_flags_%s.json", partition)), flags); err != nil {
395			return err
396		}
397	}
398	return nil
399}
400