• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2023 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 android
16
17import (
18	"fmt"
19	"io"
20	"maps"
21	"reflect"
22
23	"github.com/google/blueprint"
24)
25
26var (
27	mergeAconfigFilesRule = pctx.AndroidStaticRule("mergeAconfigFilesRule",
28		blueprint.RuleParams{
29			Command:     `${aconfig} dump --dedup --format protobuf --out $out $flags`,
30			CommandDeps: []string{"${aconfig}"},
31		}, "flags")
32	_ = pctx.HostBinToolVariable("aconfig", "aconfig")
33)
34
35// Provider published by aconfig_value_set
36type AconfigDeclarationsProviderData struct {
37	Package                     string
38	Container                   string
39	Exportable                  bool
40	IntermediateCacheOutputPath WritablePath
41	IntermediateDumpOutputPath  WritablePath
42}
43
44var AconfigDeclarationsProviderKey = blueprint.NewProvider[AconfigDeclarationsProviderData]()
45
46type AconfigReleaseDeclarationsProviderData map[string]AconfigDeclarationsProviderData
47
48var AconfigReleaseDeclarationsProviderKey = blueprint.NewProvider[AconfigReleaseDeclarationsProviderData]()
49
50type ModeInfo struct {
51	Container string
52	Mode      string
53}
54type CodegenInfo struct {
55	// AconfigDeclarations is the name of the aconfig_declarations modules that
56	// the codegen module is associated with
57	AconfigDeclarations []string
58
59	// Paths to the cache files of the associated aconfig_declaration modules
60	IntermediateCacheOutputPaths Paths
61
62	// Paths to the srcjar files generated from the java_aconfig_library modules
63	Srcjars Paths
64
65	ModeInfos map[string]ModeInfo
66}
67
68var CodegenInfoProvider = blueprint.NewProvider[CodegenInfo]()
69
70func propagateModeInfos(ctx ModuleContext, module Module, to, from map[string]ModeInfo) {
71	if len(from) > 0 {
72		depTag := ctx.OtherModuleDependencyTag(module)
73		if tag, ok := depTag.(PropagateAconfigValidationDependencyTag); ok && tag.PropagateAconfigValidation() {
74			maps.Copy(to, from)
75		}
76	}
77}
78
79type aconfigPropagatingDeclarationsInfo struct {
80	AconfigFiles map[string]Paths
81	ModeInfos    map[string]ModeInfo
82}
83
84var AconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]()
85
86func VerifyAconfigBuildMode(ctx ModuleContext, container string, module blueprint.Module, asError bool) {
87	if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
88		for k, v := range dep.ModeInfos {
89			msg := fmt.Sprintf("%s/%s depends on %s/%s/%s across containers\n",
90				module.Name(), container, k, v.Container, v.Mode)
91			if v.Container != container && v.Mode != "exported" && v.Mode != "force-read-only" {
92				if asError {
93					ctx.ModuleErrorf(msg)
94				} else {
95					fmt.Print("WARNING: " + msg)
96				}
97			} else {
98				if !asError {
99					fmt.Print("PASSED: " + msg)
100				}
101			}
102		}
103	}
104}
105
106func aconfigUpdateAndroidBuildActions(ctx ModuleContext) {
107	mergedAconfigFiles := make(map[string]Paths)
108	mergedModeInfos := make(map[string]ModeInfo)
109
110	ctx.VisitDirectDepsProxy(func(module ModuleProxy) {
111		if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 {
112			maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos)
113		}
114
115		// If any of our dependencies have aconfig declarations (directly or propagated), then merge those and provide them.
116		if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok {
117			mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath)
118		}
119		// If we were generating on-device artifacts for other release configs, we would need to add code here to propagate
120		// those artifacts as well.  See also b/298444886.
121		if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
122			for container, v := range dep.AconfigFiles {
123				mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
124			}
125			propagateModeInfos(ctx, module, mergedModeInfos, dep.ModeInfos)
126		}
127	})
128	// We only need to set the provider if we have aconfig files.
129	if len(mergedAconfigFiles) > 0 {
130		for _, container := range SortedKeys(mergedAconfigFiles) {
131			aconfigFiles := mergedAconfigFiles[container]
132			mergedAconfigFiles[container] = mergeAconfigFiles(ctx, container, aconfigFiles, true)
133		}
134
135		SetProvider(ctx, AconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{
136			AconfigFiles: mergedAconfigFiles,
137			ModeInfos:    mergedModeInfos,
138		})
139		ctx.setAconfigPaths(getAconfigFilePaths(getContainer(ctx.Module()), mergedAconfigFiles))
140	}
141}
142
143func aconfigUpdateAndroidMkData(ctx fillInEntriesContext, mod Module, data *AndroidMkData) {
144	info, ok := OtherModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
145	// If there is no aconfigPropagatingProvider, or there are no AconfigFiles, then we are done.
146	if !ok || len(info.AconfigFiles) == 0 {
147		return
148	}
149	data.Extra = append(data.Extra, func(w io.Writer, outputFile Path) {
150		AndroidMkEmitAssignList(w, "LOCAL_ACONFIG_FILES", getAconfigFilePaths(
151			getContainerUsingProviders(ctx, mod), info.AconfigFiles).Strings())
152	})
153	// If there is a Custom writer, it needs to support this provider.
154	if data.Custom != nil {
155		switch reflect.TypeOf(mod).String() {
156		case "*aidl.aidlApi": // writes non-custom before adding .phony
157		case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
158		case "*apex.apexBundle": // aconfig_file properties written
159		case "*bpf.bpf": // properties written (both for module and objs)
160		case "*genrule.Module": // writes non-custom before adding .phony
161		case "*java.SystemModules": // doesn't go through base_rules
162		case "*phony.phony": // properties written
163		case "*phony.PhonyRule": // writes phony deps and acts like `.PHONY`
164		case "*sysprop.syspropLibrary": // properties written
165		default:
166			panic(fmt.Errorf("custom make rules do not handle aconfig files for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), mod))
167		}
168	}
169}
170
171func aconfigUpdateAndroidMkEntries(ctx fillInEntriesContext, mod Module, entries *[]AndroidMkEntries) {
172	// If there are no entries, then we can ignore this module, even if it has aconfig files.
173	if len(*entries) == 0 {
174		return
175	}
176	info, ok := OtherModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
177	if !ok || len(info.AconfigFiles) == 0 {
178		return
179	}
180	// All of the files in the module potentially depend on the aconfig flag values.
181	for idx, _ := range *entries {
182		(*entries)[idx].ExtraEntries = append((*entries)[idx].ExtraEntries,
183			func(_ AndroidMkExtraEntriesContext, entries *AndroidMkEntries) {
184				entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(
185					getContainerUsingProviders(ctx, mod), info.AconfigFiles))
186			},
187		)
188
189	}
190}
191
192// TODO(b/397766191): Change the signature to take ModuleProxy
193// Please only access the module's internal data through providers.
194func aconfigUpdateAndroidMkInfos(ctx fillInEntriesContext, mod Module, infos *AndroidMkProviderInfo) {
195	info, ok := OtherModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
196	if !ok || len(info.AconfigFiles) == 0 {
197		return
198	}
199	// All of the files in the module potentially depend on the aconfig flag values.
200	infos.PrimaryInfo.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(
201		getContainerUsingProviders(ctx, mod), info.AconfigFiles))
202	if len(infos.ExtraInfo) > 0 {
203		for _, ei := range (*infos).ExtraInfo {
204			ei.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(
205				getContainerUsingProviders(ctx, mod), info.AconfigFiles))
206		}
207	}
208}
209
210func mergeAconfigFiles(ctx ModuleContext, container string, inputs Paths, generateRule bool) Paths {
211	inputs = SortedUniquePaths(inputs)
212	if len(inputs) == 1 {
213		return Paths{inputs[0]}
214	}
215
216	output := PathForModuleOut(ctx, container, "aconfig_merged.pb")
217
218	if generateRule {
219		ctx.Build(pctx, BuildParams{
220			Rule:        mergeAconfigFilesRule,
221			Description: "merge aconfig files",
222			Inputs:      inputs,
223			Output:      output,
224			Args: map[string]string{
225				"flags": JoinWithPrefix(inputs.Strings(), "--cache "),
226			},
227		})
228	}
229
230	return Paths{output}
231}
232
233func getContainer(m Module) string {
234	container := "system"
235	base := m.base()
236	if base.SocSpecific() {
237		container = "vendor"
238	} else if base.ProductSpecific() {
239		container = "product"
240	} else if base.SystemExtSpecific() {
241		container = "system_ext"
242	}
243
244	return container
245}
246
247// TODO(b/397766191): Change the signature to take ModuleProxy
248// Please only access the module's internal data through providers.
249func getContainerUsingProviders(ctx OtherModuleProviderContext, m Module) string {
250	container := "system"
251	commonInfo := OtherModulePointerProviderOrDefault(ctx, m, CommonModuleInfoProvider)
252	if commonInfo.Vendor || commonInfo.Proprietary || commonInfo.SocSpecific {
253		container = "vendor"
254	} else if commonInfo.ProductSpecific {
255		container = "product"
256	} else if commonInfo.SystemExtSpecific {
257		container = "system_ext"
258	}
259
260	return container
261}
262
263func getAconfigFilePaths(container string, aconfigFiles map[string]Paths) (paths Paths) {
264	paths = append(paths, aconfigFiles[container]...)
265	if container == "system" {
266		// TODO(b/311155208): Once the default container is system, we can drop this.
267		paths = append(paths, aconfigFiles[""]...)
268	}
269	if container != "system" {
270		if len(aconfigFiles[container]) == 0 && len(aconfigFiles[""]) > 0 {
271			// TODO(b/308625757): Either we guessed the container wrong, or the flag is misdeclared.
272			// For now, just include the system (aka "") container if we get here.
273			//fmt.Printf("container_mismatch: module=%v container=%v files=%v\n", m, container, aconfigFiles)
274		}
275		paths = append(paths, aconfigFiles[""]...)
276	}
277	return
278}
279