• 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 ModeInfo struct {
47	Container string
48	Mode      string
49}
50type CodegenInfo struct {
51	// AconfigDeclarations is the name of the aconfig_declarations modules that
52	// the codegen module is associated with
53	AconfigDeclarations []string
54
55	// Paths to the cache files of the associated aconfig_declaration modules
56	IntermediateCacheOutputPaths Paths
57
58	// Paths to the srcjar files generated from the java_aconfig_library modules
59	Srcjars Paths
60
61	ModeInfos map[string]ModeInfo
62}
63
64var CodegenInfoProvider = blueprint.NewProvider[CodegenInfo]()
65
66func propagateModeInfos(ctx ModuleContext, module Module, to, from map[string]ModeInfo) {
67	if len(from) > 0 {
68		depTag := ctx.OtherModuleDependencyTag(module)
69		if tag, ok := depTag.(PropagateAconfigValidationDependencyTag); ok && tag.PropagateAconfigValidation() {
70			maps.Copy(to, from)
71		}
72	}
73}
74
75type aconfigPropagatingDeclarationsInfo struct {
76	AconfigFiles map[string]Paths
77	ModeInfos    map[string]ModeInfo
78}
79
80var AconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]()
81
82func VerifyAconfigBuildMode(ctx ModuleContext, container string, module blueprint.Module, asError bool) {
83	if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
84		for k, v := range dep.ModeInfos {
85			msg := fmt.Sprintf("%s/%s depends on %s/%s/%s across containers\n",
86				module.Name(), container, k, v.Container, v.Mode)
87			if v.Container != container && v.Mode != "exported" && v.Mode != "force-read-only" {
88				if asError {
89					ctx.ModuleErrorf(msg)
90				} else {
91					fmt.Printf("WARNING: " + msg)
92				}
93			} else {
94				if !asError {
95					fmt.Printf("PASSED: " + msg)
96				}
97			}
98		}
99	}
100}
101
102func aconfigUpdateAndroidBuildActions(ctx ModuleContext) {
103	mergedAconfigFiles := make(map[string]Paths)
104	mergedModeInfos := make(map[string]ModeInfo)
105
106	ctx.VisitDirectDepsIgnoreBlueprint(func(module Module) {
107		if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 {
108			maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos)
109		}
110
111		// If any of our dependencies have aconfig declarations (directly or propagated), then merge those and provide them.
112		if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok {
113			mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath)
114		}
115		if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
116			for container, v := range dep.AconfigFiles {
117				mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
118			}
119			propagateModeInfos(ctx, module, mergedModeInfos, dep.ModeInfos)
120		}
121	})
122	// We only need to set the provider if we have aconfig files.
123	if len(mergedAconfigFiles) > 0 {
124		for _, container := range SortedKeys(mergedAconfigFiles) {
125			aconfigFiles := mergedAconfigFiles[container]
126			mergedAconfigFiles[container] = mergeAconfigFiles(ctx, container, aconfigFiles, true)
127		}
128
129		SetProvider(ctx, AconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{
130			AconfigFiles: mergedAconfigFiles,
131			ModeInfos:    mergedModeInfos,
132		})
133		ctx.Module().base().aconfigFilePaths = getAconfigFilePaths(ctx.Module().base(), mergedAconfigFiles)
134	}
135}
136
137func aconfigUpdateAndroidMkData(ctx fillInEntriesContext, mod Module, data *AndroidMkData) {
138	info, ok := SingletonModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
139	// If there is no aconfigPropagatingProvider, or there are no AconfigFiles, then we are done.
140	if !ok || len(info.AconfigFiles) == 0 {
141		return
142	}
143	data.Extra = append(data.Extra, func(w io.Writer, outputFile Path) {
144		AndroidMkEmitAssignList(w, "LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles).Strings())
145	})
146	// If there is a Custom writer, it needs to support this provider.
147	if data.Custom != nil {
148		switch reflect.TypeOf(mod).String() {
149		case "*aidl.aidlApi": // writes non-custom before adding .phony
150		case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
151		case "*apex.apexBundle": // aconfig_file properties written
152		case "*bpf.bpf": // properties written (both for module and objs)
153		case "*genrule.Module": // writes non-custom before adding .phony
154		case "*java.SystemModules": // doesn't go through base_rules
155		case "*phony.phony": // properties written
156		case "*phony.PhonyRule": // writes phony deps and acts like `.PHONY`
157		case "*sysprop.syspropLibrary": // properties written
158		default:
159			panic(fmt.Errorf("custom make rules do not handle aconfig files for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), mod))
160		}
161	}
162}
163
164func aconfigUpdateAndroidMkEntries(ctx fillInEntriesContext, mod Module, entries *[]AndroidMkEntries) {
165	// If there are no entries, then we can ignore this module, even if it has aconfig files.
166	if len(*entries) == 0 {
167		return
168	}
169	info, ok := SingletonModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
170	if !ok || len(info.AconfigFiles) == 0 {
171		return
172	}
173	// All of the files in the module potentially depend on the aconfig flag values.
174	for idx, _ := range *entries {
175		(*entries)[idx].ExtraEntries = append((*entries)[idx].ExtraEntries,
176			func(ctx AndroidMkExtraEntriesContext, entries *AndroidMkEntries) {
177				entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles))
178			},
179		)
180
181	}
182}
183
184func mergeAconfigFiles(ctx ModuleContext, container string, inputs Paths, generateRule bool) Paths {
185	inputs = SortedUniquePaths(inputs)
186	if len(inputs) == 1 {
187		return Paths{inputs[0]}
188	}
189
190	output := PathForModuleOut(ctx, container, "aconfig_merged.pb")
191
192	if generateRule {
193		ctx.Build(pctx, BuildParams{
194			Rule:        mergeAconfigFilesRule,
195			Description: "merge aconfig files",
196			Inputs:      inputs,
197			Output:      output,
198			Args: map[string]string{
199				"flags": JoinWithPrefix(inputs.Strings(), "--cache "),
200			},
201		})
202	}
203
204	return Paths{output}
205}
206
207func getAconfigFilePaths(m *ModuleBase, aconfigFiles map[string]Paths) (paths Paths) {
208	// TODO(b/311155208): The default container here should be system.
209	container := "system"
210
211	if m.SocSpecific() {
212		container = "vendor"
213	} else if m.ProductSpecific() {
214		container = "product"
215	} else if m.SystemExtSpecific() {
216		container = "system_ext"
217	}
218
219	paths = append(paths, aconfigFiles[container]...)
220	if container == "system" {
221		// TODO(b/311155208): Once the default container is system, we can drop this.
222		paths = append(paths, aconfigFiles[""]...)
223	}
224	if container != "system" {
225		if len(aconfigFiles[container]) == 0 && len(aconfigFiles[""]) > 0 {
226			// TODO(b/308625757): Either we guessed the container wrong, or the flag is misdeclared.
227			// For now, just include the system (aka "") container if we get here.
228			//fmt.Printf("container_mismatch: module=%v container=%v files=%v\n", m, container, aconfigFiles)
229		}
230		paths = append(paths, aconfigFiles[""]...)
231	}
232	return
233}
234