xref: /aosp_15_r20/build/soong/android/container.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 android
16
17import (
18	"fmt"
19	"reflect"
20	"slices"
21	"strings"
22
23	"github.com/google/blueprint"
24)
25
26// ----------------------------------------------------------------------------
27// Start of the definitions of exception functions and the lookup table.
28//
29// Functions cannot be used as a value passed in providers, because functions are not
30// hashable. As a workaround, the [exceptionHandleFuncLabel] enum values are passed using providers,
31// and the corresponding functions are called from [exceptionHandleFunctionsTable] map.
32// ----------------------------------------------------------------------------
33
34type exceptionHandleFunc func(ModuleContext, Module, Module) bool
35
36type StubsAvailableModule interface {
37	IsStubsModule() bool
38}
39
40// Returns true if the dependency module is a stubs module
41var depIsStubsModule exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
42	if stubsModule, ok := dep.(StubsAvailableModule); ok {
43		return stubsModule.IsStubsModule()
44	}
45	return false
46}
47
48// Returns true if the dependency module belongs to any of the apexes.
49var depIsApexModule exceptionHandleFunc = func(mctx ModuleContext, _, dep Module) bool {
50	depContainersInfo, _ := getContainerModuleInfo(mctx, dep)
51	return InList(ApexContainer, depContainersInfo.belongingContainers)
52}
53
54// Returns true if the module and the dependent module belongs to common apexes.
55var belongsToCommonApexes exceptionHandleFunc = func(mctx ModuleContext, m, dep Module) bool {
56	mContainersInfo, _ := getContainerModuleInfo(mctx, m)
57	depContainersInfo, _ := getContainerModuleInfo(mctx, dep)
58
59	return HasIntersection(mContainersInfo.ApexNames(), depContainersInfo.ApexNames())
60}
61
62// Returns true when all apexes that the module belongs to are non updatable.
63// For an apex module to be allowed to depend on a non-apex partition module,
64// all apexes that the module belong to must be non updatable.
65var belongsToNonUpdatableApex exceptionHandleFunc = func(mctx ModuleContext, m, _ Module) bool {
66	mContainersInfo, _ := getContainerModuleInfo(mctx, m)
67
68	return !mContainersInfo.UpdatableApex()
69}
70
71// Returns true if the dependency is added via dependency tags that are not used to tag dynamic
72// dependency tags.
73var depIsNotDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool {
74	mInstallable, _ := m.(InstallableModule)
75	depTag := ctx.OtherModuleDependencyTag(dep)
76	return !InList(depTag, mInstallable.DynamicDependencyTags())
77}
78
79// Returns true if the dependency is added via dependency tags that are not used to tag static
80// or dynamic dependency tags. These dependencies do not affect the module in compile time or in
81// runtime, thus are not significant enough to raise an error.
82var depIsNotStaticOrDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool {
83	mInstallable, _ := m.(InstallableModule)
84	depTag := ctx.OtherModuleDependencyTag(dep)
85	return !InList(depTag, append(mInstallable.StaticDependencyTags(), mInstallable.DynamicDependencyTags()...))
86}
87
88var globallyAllowlistedDependencies = []string{
89	// Modules that provide annotations used within the platform and apexes.
90	"aconfig-annotations-lib",
91	"framework-annotations-lib",
92	"unsupportedappusage",
93
94	// TODO(b/363016634): Remove from the allowlist when the module is converted
95	// to java_sdk_library and the java_aconfig_library modules depend on the stub.
96	"aconfig_storage_stub",
97
98	// framework-res provides core resources essential for building apps and system UI.
99	// This module is implicitly added as a dependency for java modules even when the
100	// dependency specifies sdk_version.
101	"framework-res",
102
103	// jacocoagent is implicitly added as a dependency in coverage builds, and is not installed
104	// on the device.
105	"jacocoagent",
106}
107
108// Returns true when the dependency is globally allowlisted for inter-container dependency
109var depIsGloballyAllowlisted exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
110	return InList(dep.Name(), globallyAllowlistedDependencies)
111}
112
113// Labels of exception functions, which are used to determine special dependencies that allow
114// otherwise restricted inter-container dependencies
115type exceptionHandleFuncLabel int
116
117const (
118	checkStubs exceptionHandleFuncLabel = iota
119	checkApexModule
120	checkInCommonApexes
121	checkApexIsNonUpdatable
122	checkNotDynamicDepTag
123	checkNotStaticOrDynamicDepTag
124	checkGlobalAllowlistedDep
125)
126
127// Map of [exceptionHandleFuncLabel] to the [exceptionHandleFunc]
128var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]exceptionHandleFunc{
129	checkStubs:                    depIsStubsModule,
130	checkApexModule:               depIsApexModule,
131	checkInCommonApexes:           belongsToCommonApexes,
132	checkApexIsNonUpdatable:       belongsToNonUpdatableApex,
133	checkNotDynamicDepTag:         depIsNotDynamicDepTag,
134	checkNotStaticOrDynamicDepTag: depIsNotStaticOrDynamicDepTag,
135	checkGlobalAllowlistedDep:     depIsGloballyAllowlisted,
136}
137
138// ----------------------------------------------------------------------------
139// Start of the definitions of container determination functions.
140//
141// Similar to the above section, below defines the functions used to determine
142// the container of each modules.
143// ----------------------------------------------------------------------------
144
145type containerBoundaryFunc func(mctx ModuleContext) bool
146
147var vendorContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
148	m, ok := mctx.Module().(ImageInterface)
149	return mctx.Module().InstallInVendor() || (ok && m.VendorVariantNeeded(mctx))
150}
151
152var systemContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
153	module := mctx.Module()
154
155	return !module.InstallInTestcases() &&
156		!module.InstallInData() &&
157		!module.InstallInRamdisk() &&
158		!module.InstallInVendorRamdisk() &&
159		!module.InstallInDebugRamdisk() &&
160		!module.InstallInRecovery() &&
161		!module.InstallInVendor() &&
162		!module.InstallInOdm() &&
163		!module.InstallInProduct() &&
164		determineModuleKind(module.base(), mctx.blueprintBaseModuleContext()) == platformModule
165}
166
167var productContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
168	m, ok := mctx.Module().(ImageInterface)
169	return mctx.Module().InstallInProduct() || (ok && m.ProductVariantNeeded(mctx))
170}
171
172var apexContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
173	_, ok := ModuleProvider(mctx, AllApexInfoProvider)
174	return ok
175}
176
177var ctsContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
178	props := mctx.Module().GetProperties()
179	for _, prop := range props {
180		val := reflect.ValueOf(prop).Elem()
181		if val.Kind() == reflect.Struct {
182			testSuites := val.FieldByName("Test_suites")
183			if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
184				return true
185			}
186		}
187	}
188	return false
189}
190
191type unstableInfo struct {
192	// Determines if the module contains the private APIs of the platform.
193	ContainsPlatformPrivateApis bool
194}
195
196var unstableInfoProvider = blueprint.NewProvider[unstableInfo]()
197
198func determineUnstableModule(mctx ModuleContext) bool {
199	module := mctx.Module()
200	unstableModule := module.Name() == "framework-minus-apex"
201	if installable, ok := module.(InstallableModule); ok {
202		for _, staticDepTag := range installable.StaticDependencyTags() {
203			mctx.VisitDirectDepsWithTag(staticDepTag, func(dep Module) {
204				if unstableInfo, ok := OtherModuleProvider(mctx, dep, unstableInfoProvider); ok {
205					unstableModule = unstableModule || unstableInfo.ContainsPlatformPrivateApis
206				}
207			})
208		}
209	}
210	return unstableModule
211}
212
213var unstableContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
214	return determineUnstableModule(mctx)
215}
216
217// Map of [*container] to the [containerBoundaryFunc]
218var containerBoundaryFunctionsTable = map[*container]containerBoundaryFunc{
219	VendorContainer:   vendorContainerBoundaryFunc,
220	SystemContainer:   systemContainerBoundaryFunc,
221	ProductContainer:  productContainerBoundaryFunc,
222	ApexContainer:     apexContainerBoundaryFunc,
223	CtsContainer:      ctsContainerBoundaryFunc,
224	UnstableContainer: unstableContainerBoundaryFunc,
225}
226
227// ----------------------------------------------------------------------------
228// End of the definitions of container determination functions.
229// ----------------------------------------------------------------------------
230
231type InstallableModule interface {
232	StaticDependencyTags() []blueprint.DependencyTag
233	DynamicDependencyTags() []blueprint.DependencyTag
234}
235
236type restriction struct {
237	// container of the dependency
238	dependency *container
239
240	// Error message to be emitted to the user when the dependency meets this restriction
241	errorMessage string
242
243	// List of labels of allowed exception functions that allows bypassing this restriction.
244	// If any of the functions mapped to each labels returns true, this dependency would be
245	// considered allowed and an error will not be thrown.
246	allowedExceptions []exceptionHandleFuncLabel
247}
248type container struct {
249	// The name of the container i.e. partition, api domain
250	name string
251
252	// Map of dependency restricted containers.
253	restricted []restriction
254}
255
256var (
257	VendorContainer = &container{
258		name:       VendorVariation,
259		restricted: nil,
260	}
261
262	SystemContainer = &container{
263		name: "system",
264		restricted: []restriction{
265			{
266				dependency: VendorContainer,
267				errorMessage: "Module belonging to the system partition other than HALs is " +
268					"not allowed to depend on the vendor partition module, in order to support " +
269					"independent development/update cycles and to support the Generic System " +
270					"Image. Try depending on HALs, VNDK or AIDL instead.",
271				allowedExceptions: []exceptionHandleFuncLabel{
272					checkStubs,
273					checkNotDynamicDepTag,
274					checkGlobalAllowlistedDep,
275				},
276			},
277		},
278	}
279
280	ProductContainer = &container{
281		name: ProductVariation,
282		restricted: []restriction{
283			{
284				dependency: VendorContainer,
285				errorMessage: "Module belonging to the product partition is not allowed to " +
286					"depend on the vendor partition module, as this may lead to security " +
287					"vulnerabilities. Try depending on the HALs or utilize AIDL instead.",
288				allowedExceptions: []exceptionHandleFuncLabel{
289					checkStubs,
290					checkNotDynamicDepTag,
291					checkGlobalAllowlistedDep,
292				},
293			},
294		},
295	}
296
297	ApexContainer = initializeApexContainer()
298
299	CtsContainer = &container{
300		name: "cts",
301		restricted: []restriction{
302			{
303				dependency: UnstableContainer,
304				errorMessage: "CTS module should not depend on the modules that contain the " +
305					"platform implementation details, including \"framework\". Depending on these " +
306					"modules may lead to disclosure of implementation details and regression " +
307					"due to API changes across platform versions. Try depending on the stubs instead " +
308					"and ensure that the module sets an appropriate 'sdk_version'.",
309				allowedExceptions: []exceptionHandleFuncLabel{
310					checkStubs,
311					checkNotStaticOrDynamicDepTag,
312					checkGlobalAllowlistedDep,
313				},
314			},
315		},
316	}
317
318	// Container signifying that the module contains unstable platform private APIs
319	UnstableContainer = &container{
320		name:       "unstable",
321		restricted: nil,
322	}
323
324	allContainers = []*container{
325		VendorContainer,
326		SystemContainer,
327		ProductContainer,
328		ApexContainer,
329		CtsContainer,
330		UnstableContainer,
331	}
332)
333
334func initializeApexContainer() *container {
335	apexContainer := &container{
336		name: "apex",
337		restricted: []restriction{
338			{
339				dependency: SystemContainer,
340				errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
341					"modules belonging to the system partition. Either statically depend on the " +
342					"module or convert the depending module to java_sdk_library and depend on " +
343					"the stubs.",
344				allowedExceptions: []exceptionHandleFuncLabel{
345					checkStubs,
346					checkApexModule,
347					checkInCommonApexes,
348					checkApexIsNonUpdatable,
349					checkNotStaticOrDynamicDepTag,
350					checkGlobalAllowlistedDep,
351				},
352			},
353		},
354	}
355
356	apexContainer.restricted = append(apexContainer.restricted, restriction{
357		dependency: apexContainer,
358		errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
359			"modules belonging to other Apex(es). Either include the depending " +
360			"module in the Apex or convert the depending module to java_sdk_library " +
361			"and depend on its stubs.",
362		allowedExceptions: []exceptionHandleFuncLabel{
363			checkStubs,
364			checkInCommonApexes,
365			checkNotStaticOrDynamicDepTag,
366			checkGlobalAllowlistedDep,
367		},
368	})
369
370	return apexContainer
371}
372
373type ContainersInfo struct {
374	belongingContainers []*container
375
376	belongingApexes []ApexInfo
377}
378
379func (c *ContainersInfo) BelongingContainers() []*container {
380	return c.belongingContainers
381}
382
383func (c *ContainersInfo) ApexNames() (ret []string) {
384	for _, apex := range c.belongingApexes {
385		ret = append(ret, apex.InApexVariants...)
386	}
387	slices.Sort(ret)
388	return ret
389}
390
391// Returns true if any of the apex the module belongs to is updatable.
392func (c *ContainersInfo) UpdatableApex() bool {
393	for _, apex := range c.belongingApexes {
394		if apex.Updatable {
395			return true
396		}
397	}
398	return false
399}
400
401var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
402
403func satisfyAllowedExceptions(ctx ModuleContext, allowedExceptionLabels []exceptionHandleFuncLabel, m, dep Module) bool {
404	for _, label := range allowedExceptionLabels {
405		if exceptionHandleFunctionsTable[label](ctx, m, dep) {
406			return true
407		}
408	}
409	return false
410}
411
412func (c *ContainersInfo) GetViolations(mctx ModuleContext, m, dep Module, depInfo ContainersInfo) []string {
413	var violations []string
414
415	// Any containers that the module belongs to but the dependency does not belong to must be examined.
416	_, containersUniqueToModule, _ := ListSetDifference(c.belongingContainers, depInfo.belongingContainers)
417
418	// Apex container should be examined even if both the module and the dependency belong to
419	// the apex container to check that the two modules belong to the same apex.
420	if InList(ApexContainer, c.belongingContainers) && !InList(ApexContainer, containersUniqueToModule) {
421		containersUniqueToModule = append(containersUniqueToModule, ApexContainer)
422	}
423
424	for _, containerUniqueToModule := range containersUniqueToModule {
425		for _, restriction := range containerUniqueToModule.restricted {
426			if InList(restriction.dependency, depInfo.belongingContainers) {
427				if !satisfyAllowedExceptions(mctx, restriction.allowedExceptions, m, dep) {
428					violations = append(violations, restriction.errorMessage)
429				}
430			}
431		}
432	}
433
434	return violations
435}
436
437func generateContainerInfo(ctx ModuleContext) ContainersInfo {
438	var containers []*container
439
440	for _, cnt := range allContainers {
441		if containerBoundaryFunctionsTable[cnt](ctx) {
442			containers = append(containers, cnt)
443		}
444	}
445
446	var belongingApexes []ApexInfo
447	if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
448		belongingApexes = apexInfo.ApexInfos
449	}
450
451	return ContainersInfo{
452		belongingContainers: containers,
453		belongingApexes:     belongingApexes,
454	}
455}
456
457func getContainerModuleInfo(ctx ModuleContext, module Module) (ContainersInfo, bool) {
458	if ctx.Module() == module {
459		return ctx.getContainersInfo(), true
460	}
461
462	return OtherModuleProvider(ctx, module, ContainersInfoProvider)
463}
464
465func setContainerInfo(ctx ModuleContext) {
466	// Required to determine the unstable container. This provider is set here instead of the
467	// unstableContainerBoundaryFunc in order to prevent setting the provider multiple times.
468	SetProvider(ctx, unstableInfoProvider, unstableInfo{
469		ContainsPlatformPrivateApis: determineUnstableModule(ctx),
470	})
471
472	if _, ok := ctx.Module().(InstallableModule); ok {
473		containersInfo := generateContainerInfo(ctx)
474		ctx.setContainersInfo(containersInfo)
475		SetProvider(ctx, ContainersInfoProvider, containersInfo)
476	}
477}
478
479func checkContainerViolations(ctx ModuleContext) {
480	if _, ok := ctx.Module().(InstallableModule); ok {
481		containersInfo, _ := getContainerModuleInfo(ctx, ctx.Module())
482		ctx.VisitDirectDeps(func(dep Module) {
483			if !dep.Enabled(ctx) {
484				return
485			}
486
487			// Pre-existing violating dependencies are tracked in containerDependencyViolationAllowlist.
488			// If this dependency is allowlisted, do not check for violation.
489			// If not, check if this dependency matches any restricted dependency and
490			// satisfies any exception functions, which allows bypassing the
491			// restriction. If all of the exceptions are not satisfied, throw an error.
492			if depContainersInfo, ok := getContainerModuleInfo(ctx, dep); ok {
493				if allowedViolations, ok := ContainerDependencyViolationAllowlist[ctx.ModuleName()]; ok && InList(dep.Name(), allowedViolations) {
494					return
495				} else {
496					violations := containersInfo.GetViolations(ctx, ctx.Module(), dep, depContainersInfo)
497					if len(violations) > 0 {
498						errorMessage := fmt.Sprintf("%s cannot depend on %s. ", ctx.ModuleName(), dep.Name())
499						errorMessage += strings.Join(violations, " ")
500						ctx.ModuleErrorf(errorMessage)
501					}
502				}
503			}
504		})
505	}
506}
507