platform_build_soong/android/apex.go
Jihoon Kang 46d66de1c1 Propagate DirectlyInAnyApex to transitive dependencies
`UpdateDirectlyInAnyApex` is used to propagate the ApexProperties
`DirectlyInAnyApex` and `InAnyApex` to the direct dependencies of the
direct dependencies of an apex bundle. In other words, this will be
propagated only to two-levels max dependency from the apex bundle.

However, the implementation library of the sdk library can have longer
dependency chain from the apex bundle than two levels:
e.g. apex -> bcpf -> sdk_lib -> sdk_lib impl lib

Therefore, even if the implementation library of the sdk library is
registered as a dependency using the "CopyDirectlyInAnyApexTag"
dependency tag, the ApexProperties would not be propagated to the
implementation library. In order to resolve this issue and recognize
the implementation library to be directly in apex and allow
instrumentation for the implementation library, this change proposes
propagating `DirectlyInAnyApex` and `InAnyApex` to transitive
dependencies on top of direct dependencies.

Test: DIST_DIR=dist_dir TARGET_BUILD_VARIANT=userdebug PRODUCT=mainline_modules_x86_64 COVERAGE_MODULES="uwb" ./vendor/google/build/build_unbundled_coverage_mainline_module.sh && \
unzip -l out/target/product/module_x86_64/jacoco-report-classes-all.jar and ensure that framework-uwb is included
Bug: 341170242

Change-Id: I27d7a74f6e5bc3e0a044d13c619f4897b6b2eb57
2024-05-23 22:40:35 +00:00

1055 lines
40 KiB
Go

// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android
import (
"fmt"
"slices"
"sort"
"strconv"
"strings"
"sync"
"github.com/google/blueprint"
)
var (
// This is the sdk version when APEX was first introduced
SdkVersion_Android10 = uncheckedFinalApiLevel(29)
)
// ApexInfo describes the metadata about one or more apexBundles that an apex variant of a module is
// part of. When an apex variant is created, the variant is associated with one apexBundle. But
// when multiple apex variants are merged for deduping (see mergeApexVariations), this holds the
// information about the apexBundles that are merged together.
// Accessible via `ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)`
type ApexInfo struct {
// Name of the apex variation that this module (i.e. the apex variant of the module) is
// mutated into, or "" for a platform (i.e. non-APEX) variant.
//
// Also note that a module can be included in multiple APEXes, in which case, the module is
// mutated into one or more variants, each of which is for an APEX. The variants then can
// later be deduped if they don't need to be compiled differently. This is an optimization
// done in mergeApexVariations.
ApexVariationName string
// ApiLevel that this module has to support at minimum.
MinSdkVersion ApiLevel
// True if this module comes from an updatable apexBundle.
Updatable bool
// True if this module can use private platform APIs. Only non-updatable APEX can set this
// to true.
UsePlatformApis bool
// List of Apex variant names that this module is associated with. This initially is the
// same as the `ApexVariationName` field. Then when multiple apex variants are merged in
// mergeApexVariations, ApexInfo struct of the merged variant holds the list of apexBundles
// that are merged together.
InApexVariants []string
// List of APEX Soong module names that this module is part of. Note that the list includes
// different variations of the same APEX. For example, if module `foo` is included in the
// apex `com.android.foo`, and also if there is an override_apex module
// `com.mycompany.android.foo` overriding `com.android.foo`, then this list contains both
// `com.android.foo` and `com.mycompany.android.foo`. If the APEX Soong module is a
// prebuilt, the name here doesn't have the `prebuilt_` prefix.
InApexModules []string
// Pointers to the ApexContents struct each of which is for apexBundle modules that this
// module is part of. The ApexContents gives information about which modules the apexBundle
// has and whether a module became part of the apexBundle via a direct dependency or not.
ApexContents []*ApexContents
// True if this is for a prebuilt_apex.
//
// If true then this will customize the apex processing to make it suitable for handling
// prebuilt_apex, e.g. it will prevent ApexInfos from being merged together.
//
// See Prebuilt.ApexInfoMutator for more information.
ForPrebuiltApex bool
// Returns the name of the test apexes that this module is included in.
TestApexes []string
}
// AllApexInfo holds the ApexInfo of all apexes that include this module.
type AllApexInfo struct {
ApexInfos []ApexInfo
}
var ApexInfoProvider = blueprint.NewMutatorProvider[ApexInfo]("apex_mutate")
var AllApexInfoProvider = blueprint.NewMutatorProvider[*AllApexInfo]("apex_info")
func (i ApexInfo) AddJSONData(d *map[string]interface{}) {
(*d)["Apex"] = map[string]interface{}{
"ApexVariationName": i.ApexVariationName,
"MinSdkVersion": i.MinSdkVersion,
"InApexModules": i.InApexModules,
"InApexVariants": i.InApexVariants,
"ForPrebuiltApex": i.ForPrebuiltApex,
}
}
// mergedName gives the name of the alias variation that will be used when multiple apex variations
// of a module can be deduped into one variation. For example, if libfoo is included in both apex.a
// and apex.b, and if the two APEXes have the same min_sdk_version (say 29), then libfoo doesn't
// have to be built twice, but only once. In that case, the two apex variations apex.a and apex.b
// are configured to have the same alias variation named apex29. Whether platform APIs is allowed
// or not also matters; if two APEXes don't have the same allowance, they get different names and
// thus wouldn't be merged.
func (i ApexInfo) mergedName() string {
name := "apex" + strconv.Itoa(i.MinSdkVersion.FinalOrFutureInt())
return name
}
// IsForPlatform tells whether this module is for the platform or not. If false is returned, it
// means that this apex variant of the module is built for an APEX.
func (i ApexInfo) IsForPlatform() bool {
return i.ApexVariationName == ""
}
// InApexVariant tells whether this apex variant of the module is part of the given apexVariant or
// not.
func (i ApexInfo) InApexVariant(apexVariant string) bool {
for _, a := range i.InApexVariants {
if a == apexVariant {
return true
}
}
return false
}
func (i ApexInfo) InApexModule(apexModuleName string) bool {
for _, a := range i.InApexModules {
if a == apexModuleName {
return true
}
}
return false
}
// ApexTestForInfo stores the contents of APEXes for which this module is a test - although this
// module is not part of the APEX - and thus has access to APEX internals.
type ApexTestForInfo struct {
ApexContents []*ApexContents
}
var ApexTestForInfoProvider = blueprint.NewMutatorProvider[ApexTestForInfo]("apex_test_for")
// ApexBundleInfo contains information about the dependencies of an apex
type ApexBundleInfo struct {
Contents *ApexContents
}
var ApexBundleInfoProvider = blueprint.NewMutatorProvider[ApexBundleInfo]("apex_info")
// DepIsInSameApex defines an interface that should be used to determine whether a given dependency
// should be considered as part of the same APEX as the current module or not. Note: this was
// extracted from ApexModule to make it easier to define custom subsets of the ApexModule interface
// and improve code navigation within the IDE.
type DepIsInSameApex interface {
// DepIsInSameApex tests if the other module 'dep' is considered as part of the same APEX as
// this module. For example, a static lib dependency usually returns true here, while a
// shared lib dependency to a stub library returns false.
//
// This method must not be called directly without first ignoring dependencies whose tags
// implement ExcludeFromApexContentsTag. Calls from within the func passed to WalkPayloadDeps()
// are fine as WalkPayloadDeps() will ignore those dependencies automatically. Otherwise, use
// IsDepInSameApex instead.
DepIsInSameApex(ctx BaseModuleContext, dep Module) bool
}
func IsDepInSameApex(ctx BaseModuleContext, module, dep Module) bool {
depTag := ctx.OtherModuleDependencyTag(dep)
if _, ok := depTag.(ExcludeFromApexContentsTag); ok {
// The tag defines a dependency that never requires the child module to be part of the same
// apex as the parent.
return false
}
return module.(DepIsInSameApex).DepIsInSameApex(ctx, dep)
}
// ApexModule is the interface that a module type is expected to implement if the module has to be
// built differently depending on whether the module is destined for an APEX or not (i.e., installed
// to one of the regular partitions).
//
// Native shared libraries are one such module type; when it is built for an APEX, it should depend
// only on stable interfaces such as NDK, stable AIDL, or C APIs from other APEXes.
//
// A module implementing this interface will be mutated into multiple variations by apex.apexMutator
// if it is directly or indirectly included in one or more APEXes. Specifically, if a module is
// included in apex.foo and apex.bar then three apex variants are created: platform, apex.foo and
// apex.bar. The platform variant is for the regular partitions (e.g., /system or /vendor, etc.)
// while the other two are for the APEXs, respectively. The latter two variations can be merged (see
// mergedName) when the two APEXes have the same min_sdk_version requirement.
type ApexModule interface {
Module
DepIsInSameApex
apexModuleBase() *ApexModuleBase
// Marks that this module should be built for the specified APEX. Call this BEFORE
// apex.apexMutator is run.
BuildForApex(apex ApexInfo)
// Returns true if this module is present in any APEX either directly or indirectly. Call
// this after apex.apexMutator is run.
InAnyApex() bool
// Returns true if this module is directly in any APEX. Call this AFTER apex.apexMutator is
// run.
DirectlyInAnyApex() bool
// NotInPlatform tells whether or not this module is included in an APEX and therefore
// shouldn't be exposed to the platform (i.e. outside of the APEX) directly. A module is
// considered to be included in an APEX either when there actually is an APEX that
// explicitly has the module as its dependency or the module is not available to the
// platform, which indicates that the module belongs to at least one or more other APEXes.
NotInPlatform() bool
// Tests if this module could have APEX variants. Even when a module type implements
// ApexModule interface, APEX variants are created only for the module instances that return
// true here. This is useful for not creating APEX variants for certain types of shared
// libraries such as NDK stubs.
CanHaveApexVariants() bool
// Tests if this module can be installed to APEX as a file. For example, this would return
// true for shared libs while return false for static libs because static libs are not
// installable module (but it can still be mutated for APEX)
IsInstallableToApex() bool
// Tests if this module is available for the specified APEX or ":platform". This is from the
// apex_available property of the module.
AvailableFor(what string) bool
// AlwaysRequiresPlatformApexVariant allows the implementing module to determine whether an
// APEX mutator should always be created for it.
//
// Returns false by default.
AlwaysRequiresPlatformApexVariant() bool
// Returns true if this module is not available to platform (i.e. apex_available property
// doesn't have "//apex_available:platform"), or shouldn't be available to platform, which
// is the case when this module depends on other module that isn't available to platform.
NotAvailableForPlatform() bool
// Marks that this module is not available to platform. Set by the
// check-platform-availability mutator in the apex package.
SetNotAvailableForPlatform()
// Returns the list of APEXes that this module is a test for. The module has access to the
// private part of the listed APEXes even when it is not included in the APEXes. This by
// default returns nil. A module type should override the default implementation. For
// example, cc_test module type returns the value of test_for here.
TestFor() []string
// Returns nil (success) if this module should support the given sdk version. Returns an
// error if not. No default implementation is provided for this method. A module type
// implementing this interface should provide an implementation. A module supports an sdk
// version when the module's min_sdk_version is equal to or less than the given sdk version.
ShouldSupportSdkVersion(ctx BaseModuleContext, sdkVersion ApiLevel) error
// Returns true if this module needs a unique variation per apex, effectively disabling the
// deduping. This is turned on when, for example if use_apex_name_macro is set so that each
// apex variant should be built with different macro definitions.
UniqueApexVariations() bool
}
// Properties that are common to all module types implementing ApexModule interface.
type ApexProperties struct {
// Availability of this module in APEXes. Only the listed APEXes can contain this module. If
// the module has stubs then other APEXes and the platform may access it through them
// (subject to visibility).
//
// "//apex_available:anyapex" is a pseudo APEX name that matches to any APEX.
// "//apex_available:platform" refers to non-APEX partitions like "system.img".
// "com.android.gki.*" matches any APEX module name with the prefix "com.android.gki.".
// Default is ["//apex_available:platform"].
Apex_available []string
// See ApexModule.InAnyApex()
InAnyApex bool `blueprint:"mutated"`
// See ApexModule.DirectlyInAnyApex()
DirectlyInAnyApex bool `blueprint:"mutated"`
// AnyVariantDirectlyInAnyApex is true in the primary variant of a module if _any_ variant
// of the module is directly in any apex. This includes host, arch, asan, etc. variants. It
// is unused in any variant that is not the primary variant. Ideally this wouldn't be used,
// as it incorrectly mixes arch variants if only one arch is in an apex, but a few places
// depend on it, for example when an ASAN variant is created before the apexMutator. Call
// this after apex.apexMutator is run.
AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
// See ApexModule.NotAvailableForPlatform()
NotAvailableForPlatform bool `blueprint:"mutated"`
// See ApexModule.UniqueApexVariants()
UniqueApexVariationsForDeps bool `blueprint:"mutated"`
// The test apexes that includes this apex variant
TestApexes []string `blueprint:"mutated"`
}
// Marker interface that identifies dependencies that are excluded from APEX contents.
//
// Unless the tag also implements the AlwaysRequireApexVariantTag this will prevent an apex variant
// from being created for the module.
//
// At the moment the sdk.sdkRequirementsMutator relies on the fact that the existing tags which
// implement this interface do not define dependencies onto members of an sdk_snapshot. If that
// changes then sdk.sdkRequirementsMutator will need fixing.
type ExcludeFromApexContentsTag interface {
blueprint.DependencyTag
// Method that differentiates this interface from others.
ExcludeFromApexContents()
}
// Marker interface that identifies dependencies that always requires an APEX variant to be created.
//
// It is possible for a dependency to require an apex variant but exclude the module from the APEX
// contents. See sdk.sdkMemberDependencyTag.
type AlwaysRequireApexVariantTag interface {
blueprint.DependencyTag
// Return true if this tag requires that the target dependency has an apex variant.
AlwaysRequireApexVariant() bool
}
// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex state
// from the parent to the child. For example, stubs libraries are marked as DirectlyInAnyApex if
// their implementation is in an apex.
type CopyDirectlyInAnyApexTag interface {
blueprint.DependencyTag
// Method that differentiates this interface from others.
CopyDirectlyInAnyApex()
}
// Interface that identifies dependencies to skip Apex dependency check
type SkipApexAllowedDependenciesCheck interface {
// Returns true to skip the Apex dependency check, which limits the allowed dependency in build.
SkipApexAllowedDependenciesCheck() bool
}
// ApexModuleBase provides the default implementation for the ApexModule interface. APEX-aware
// modules are expected to include this struct and call InitApexModule().
type ApexModuleBase struct {
ApexProperties ApexProperties
apexPropertiesLock sync.Mutex // protects ApexProperties during parallel apexDirectlyInAnyMutator
canHaveApexVariants bool
apexInfos []ApexInfo
apexInfosLock sync.Mutex // protects apexInfos during parallel apexInfoMutator
}
// Initializes ApexModuleBase struct. Not calling this (even when inheriting from ApexModuleBase)
// prevents the module from being mutated for apexBundle.
func InitApexModule(m ApexModule) {
base := m.apexModuleBase()
base.canHaveApexVariants = true
m.AddProperties(&base.ApexProperties)
}
// Implements ApexModule
func (m *ApexModuleBase) apexModuleBase() *ApexModuleBase {
return m
}
var (
availableToPlatformList = []string{AvailableToPlatform}
)
// Implements ApexModule
func (m *ApexModuleBase) ApexAvailable() []string {
aa := m.ApexProperties.Apex_available
if len(aa) > 0 {
return aa
}
// Default is availability to platform
return CopyOf(availableToPlatformList)
}
// Implements ApexModule
func (m *ApexModuleBase) BuildForApex(apex ApexInfo) {
m.apexInfosLock.Lock()
defer m.apexInfosLock.Unlock()
for i, v := range m.apexInfos {
if v.ApexVariationName == apex.ApexVariationName {
if len(apex.InApexModules) != 1 {
panic(fmt.Errorf("Newly created apexInfo must be for a single APEX"))
}
// Even when the ApexVariantNames are the same, the given ApexInfo might
// actually be for different APEX. This can happen when an APEX is
// overridden via override_apex. For example, there can be two apexes
// `com.android.foo` (from the `apex` module type) and
// `com.mycompany.android.foo` (from the `override_apex` module type), both
// of which has the same ApexVariantName `com.android.foo`. Add the apex
// name to the list so that it's not lost.
if !InList(apex.InApexModules[0], v.InApexModules) {
m.apexInfos[i].InApexModules = append(m.apexInfos[i].InApexModules, apex.InApexModules[0])
}
return
}
}
m.apexInfos = append(m.apexInfos, apex)
}
// Implements ApexModule
func (m *ApexModuleBase) InAnyApex() bool {
return m.ApexProperties.InAnyApex
}
// Implements ApexModule
func (m *ApexModuleBase) DirectlyInAnyApex() bool {
return m.ApexProperties.DirectlyInAnyApex
}
// Implements ApexModule
func (m *ApexModuleBase) NotInPlatform() bool {
return m.ApexProperties.AnyVariantDirectlyInAnyApex || !m.AvailableFor(AvailableToPlatform)
}
// Implements ApexModule
func (m *ApexModuleBase) CanHaveApexVariants() bool {
return m.canHaveApexVariants
}
// Implements ApexModule
func (m *ApexModuleBase) IsInstallableToApex() bool {
// If needed, this will bel overridden by concrete types inheriting
// ApexModuleBase
return false
}
// Implements ApexModule
func (m *ApexModuleBase) TestFor() []string {
// If needed, this will be overridden by concrete types inheriting
// ApexModuleBase
return nil
}
// Returns the test apexes that this module is included in.
func (m *ApexModuleBase) TestApexes() []string {
return m.ApexProperties.TestApexes
}
// Implements ApexModule
func (m *ApexModuleBase) UniqueApexVariations() bool {
// If needed, this will bel overridden by concrete types inheriting
// ApexModuleBase
return false
}
// Implements ApexModule
func (m *ApexModuleBase) DepIsInSameApex(ctx BaseModuleContext, dep Module) bool {
// By default, if there is a dependency from A to B, we try to include both in the same
// APEX, unless B is explicitly from outside of the APEX (i.e. a stubs lib). Thus, returning
// true. This is overridden by some module types like apex.ApexBundle, cc.Module,
// java.Module, etc.
return true
}
const (
AvailableToPlatform = "//apex_available:platform"
AvailableToAnyApex = "//apex_available:anyapex"
AvailableToGkiApex = "com.android.gki.*"
)
var (
AvailableToRecognziedWildcards = []string{
AvailableToPlatform,
AvailableToAnyApex,
AvailableToGkiApex,
}
)
// CheckAvailableForApex provides the default algorithm for checking the apex availability. When the
// availability is empty, it defaults to ["//apex_available:platform"] which means "available to the
// platform but not available to any APEX". When the list is not empty, `what` is matched against
// the list. If there is any matching element in the list, thus function returns true. The special
// availability "//apex_available:anyapex" matches with anything except for
// "//apex_available:platform".
func CheckAvailableForApex(what string, apex_available []string) bool {
if len(apex_available) == 0 {
return what == AvailableToPlatform
}
return InList(what, apex_available) ||
(what != AvailableToPlatform && InList(AvailableToAnyApex, apex_available)) ||
(strings.HasPrefix(what, "com.android.gki.") && InList(AvailableToGkiApex, apex_available)) ||
(what == "com.google.mainline.primary.libs") || // TODO b/248601389
(what == "com.google.mainline.go.primary.libs") // TODO b/248601389
}
// Implements ApexModule
func (m *ApexModuleBase) AvailableFor(what string) bool {
return CheckAvailableForApex(what, m.ApexProperties.Apex_available)
}
// Implements ApexModule
func (m *ApexModuleBase) AlwaysRequiresPlatformApexVariant() bool {
return false
}
// Implements ApexModule
func (m *ApexModuleBase) NotAvailableForPlatform() bool {
return m.ApexProperties.NotAvailableForPlatform
}
// Implements ApexModule
func (m *ApexModuleBase) SetNotAvailableForPlatform() {
m.ApexProperties.NotAvailableForPlatform = true
}
// This function makes sure that the apex_available property is valid
func (m *ApexModuleBase) checkApexAvailableProperty(mctx BaseModuleContext) {
for _, n := range m.ApexProperties.Apex_available {
if n == AvailableToPlatform || n == AvailableToAnyApex || n == AvailableToGkiApex {
continue
}
if !mctx.OtherModuleExists(n) && !mctx.Config().AllowMissingDependencies() {
mctx.PropertyErrorf("apex_available", "%q is not a valid module name", n)
}
}
}
// AvailableToSameApexes returns true if the two modules are apex_available to
// exactly the same set of APEXes (and platform), i.e. if their apex_available
// properties have the same elements.
func AvailableToSameApexes(mod1, mod2 ApexModule) bool {
mod1ApexAvail := SortedUniqueStrings(mod1.apexModuleBase().ApexProperties.Apex_available)
mod2ApexAvail := SortedUniqueStrings(mod2.apexModuleBase().ApexProperties.Apex_available)
if len(mod1ApexAvail) != len(mod2ApexAvail) {
return false
}
for i, v := range mod1ApexAvail {
if v != mod2ApexAvail[i] {
return false
}
}
return true
}
// mergeApexVariations deduplicates apex variations that would build identically into a common
// variation. It returns the reduced list of variations and a list of aliases from the original
// variation names to the new variation names.
func mergeApexVariations(apexInfos []ApexInfo) (merged []ApexInfo, aliases [][2]string) {
seen := make(map[string]int)
for _, apexInfo := range apexInfos {
// If this is for a prebuilt apex then use the actual name of the apex variation to prevent this
// from being merged with other ApexInfo. See Prebuilt.ApexInfoMutator for more information.
if apexInfo.ForPrebuiltApex {
merged = append(merged, apexInfo)
continue
}
// Merge the ApexInfo together. If a compatible ApexInfo exists then merge the information from
// this one into it, otherwise create a new merged ApexInfo from this one and save it away so
// other ApexInfo instances can be merged into it.
variantName := apexInfo.ApexVariationName
mergedName := apexInfo.mergedName()
if index, exists := seen[mergedName]; exists {
// Variants having the same mergedName are deduped
merged[index].InApexVariants = append(merged[index].InApexVariants, variantName)
merged[index].InApexModules = append(merged[index].InApexModules, apexInfo.InApexModules...)
merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
// Platform APIs is allowed for this module only when all APEXes containing
// the module are with `use_platform_apis: true`.
merged[index].UsePlatformApis = merged[index].UsePlatformApis && apexInfo.UsePlatformApis
merged[index].TestApexes = append(merged[index].TestApexes, apexInfo.TestApexes...)
} else {
seen[mergedName] = len(merged)
apexInfo.ApexVariationName = mergedName
apexInfo.InApexVariants = CopyOf(apexInfo.InApexVariants)
apexInfo.InApexModules = CopyOf(apexInfo.InApexModules)
apexInfo.ApexContents = append([]*ApexContents(nil), apexInfo.ApexContents...)
apexInfo.TestApexes = CopyOf(apexInfo.TestApexes)
merged = append(merged, apexInfo)
}
aliases = append(aliases, [2]string{variantName, mergedName})
}
return merged, aliases
}
// IncomingApexTransition is called by apexTransitionMutator.IncomingTransition on modules that can be in apexes.
// The incomingVariation can be either the name of an apex if the dependency is coming directly from an apex
// module, or it can be the name of an apex variation (e.g. apex10000) if it is coming from another module that
// is in the apex.
func IncomingApexTransition(ctx IncomingTransitionContext, incomingVariation string) string {
module := ctx.Module().(ApexModule)
base := module.apexModuleBase()
var apexInfos []ApexInfo
if allApexInfos, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
apexInfos = allApexInfos.ApexInfos
}
// Dependencies from platform variations go to the platform variation.
if incomingVariation == "" {
return ""
}
// If this module has no apex variations the use the platform variation.
if len(apexInfos) == 0 {
return ""
}
// Convert the list of apex infos into from the AllApexInfoProvider into the merged list
// of apex variations and the aliases from apex names to apex variations.
var aliases [][2]string
if !module.UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
apexInfos, aliases = mergeApexVariations(apexInfos)
}
// Check if the incoming variation matches an apex name, and if so use the corresponding
// apex variation.
aliasIndex := slices.IndexFunc(aliases, func(alias [2]string) bool {
return alias[0] == incomingVariation
})
if aliasIndex >= 0 {
return aliases[aliasIndex][1]
}
// Check if the incoming variation matches an apex variation.
apexIndex := slices.IndexFunc(apexInfos, func(info ApexInfo) bool {
return info.ApexVariationName == incomingVariation
})
if apexIndex >= 0 {
return incomingVariation
}
return ""
}
func MutateApexTransition(ctx BaseModuleContext, variation string) {
module := ctx.Module().(ApexModule)
base := module.apexModuleBase()
platformVariation := variation == ""
var apexInfos []ApexInfo
if allApexInfos, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
apexInfos = allApexInfos.ApexInfos
}
// Shortcut
if len(apexInfos) == 0 {
return
}
// Do some validity checks.
// TODO(jiyong): is this the right place?
base.checkApexAvailableProperty(ctx)
if !module.UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
apexInfos, _ = mergeApexVariations(apexInfos)
}
var inApex ApexMembership
for _, a := range apexInfos {
for _, apexContents := range a.ApexContents {
inApex = inApex.merge(apexContents.contents[ctx.ModuleName()])
}
}
base.ApexProperties.InAnyApex = true
base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
if platformVariation && !ctx.Host() && !module.AvailableFor(AvailableToPlatform) {
// Do not install the module for platform, but still allow it to output
// uninstallable AndroidMk entries in certain cases when they have side
// effects. TODO(jiyong): move this routine to somewhere else
module.MakeUninstallable()
}
if !platformVariation {
var thisApexInfo ApexInfo
apexIndex := slices.IndexFunc(apexInfos, func(info ApexInfo) bool {
return info.ApexVariationName == variation
})
if apexIndex >= 0 {
thisApexInfo = apexInfos[apexIndex]
} else {
panic(fmt.Errorf("failed to find apexInfo for incoming variation %q", variation))
}
SetProvider(ctx, ApexInfoProvider, thisApexInfo)
}
// Set the value of TestApexes in every single apex variant.
// This allows each apex variant to be aware of the test apexes in the user provided apex_available.
var testApexes []string
for _, a := range apexInfos {
testApexes = append(testApexes, a.TestApexes...)
}
base.ApexProperties.TestApexes = testApexes
}
func ApexInfoMutator(ctx TopDownMutatorContext, module ApexModule) {
base := module.apexModuleBase()
if len(base.apexInfos) > 0 {
apexInfos := slices.Clone(base.apexInfos)
slices.SortFunc(apexInfos, func(a, b ApexInfo) int {
return strings.Compare(a.ApexVariationName, b.ApexVariationName)
})
SetProvider(ctx, AllApexInfoProvider, &AllApexInfo{apexInfos})
// base.apexInfos is only needed to propagate the list of apexes from the apex module to its
// contents within apexInfoMutator. Clear it so it doesn't accidentally get used later.
base.apexInfos = nil
}
}
// UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies that are
// in the same APEX have unique APEX variations so that the module can link against the right
// variant.
func UpdateUniqueApexVariationsForDeps(mctx BottomUpMutatorContext, am ApexModule) {
// anyInSameApex returns true if the two ApexInfo lists contain any values in an
// InApexVariants list in common. It is used instead of DepIsInSameApex because it needs to
// determine if the dep is in the same APEX due to being directly included, not only if it
// is included _because_ it is a dependency.
anyInSameApex := func(a, b ApexModule) bool {
collectApexes := func(m ApexModule) []string {
if allApexInfo, ok := OtherModuleProvider(mctx, m, AllApexInfoProvider); ok {
var ret []string
for _, info := range allApexInfo.ApexInfos {
ret = append(ret, info.InApexVariants...)
}
return ret
}
return nil
}
aApexes := collectApexes(a)
bApexes := collectApexes(b)
sort.Strings(bApexes)
for _, aApex := range aApexes {
index := sort.SearchStrings(bApexes, aApex)
if index < len(bApexes) && bApexes[index] == aApex {
return true
}
}
return false
}
// If any of the dependencies requires unique apex variations, so does this module.
mctx.VisitDirectDeps(func(dep Module) {
if depApexModule, ok := dep.(ApexModule); ok {
if anyInSameApex(depApexModule, am) &&
(depApexModule.UniqueApexVariations() ||
depApexModule.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps) {
am.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps = true
}
}
})
}
// UpdateDirectlyInAnyApex uses the final module to store if any variant of this module is directly
// in any APEX, and then copies the final value to all the modules. It also copies the
// DirectlyInAnyApex value to any transitive dependencies with a CopyDirectlyInAnyApexTag
// dependency tag.
func UpdateDirectlyInAnyApex(mctx BottomUpMutatorContext, am ApexModule) {
base := am.apexModuleBase()
// Copy DirectlyInAnyApex and InAnyApex from any transitive dependencies with a
// CopyDirectlyInAnyApexTag dependency tag.
mctx.WalkDeps(func(child, parent Module) bool {
if _, ok := mctx.OtherModuleDependencyTag(child).(CopyDirectlyInAnyApexTag); ok {
depBase := child.(ApexModule).apexModuleBase()
depBase.apexPropertiesLock.Lock()
defer depBase.apexPropertiesLock.Unlock()
depBase.ApexProperties.DirectlyInAnyApex = base.ApexProperties.DirectlyInAnyApex
depBase.ApexProperties.InAnyApex = base.ApexProperties.InAnyApex
return true
}
return false
})
if base.ApexProperties.DirectlyInAnyApex {
// Variants of a module are always visited sequentially in order, so it is safe to
// write to another variant of this module. For a BottomUpMutator the
// PrimaryModule() is visited first and FinalModule() is visited last.
mctx.FinalModule().(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex = true
}
// If this is the FinalModule (last visited module) copy
// AnyVariantDirectlyInAnyApex to all the other variants
if am == mctx.FinalModule().(ApexModule) {
mctx.VisitAllModuleVariants(func(variant Module) {
variant.(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex =
base.ApexProperties.AnyVariantDirectlyInAnyApex
})
}
}
// ApexMembership tells how a module became part of an APEX.
type ApexMembership int
const (
notInApex ApexMembership = 0
indirectlyInApex = iota
directlyInApex
)
// ApexContents gives an information about member modules of an apexBundle. Each apexBundle has an
// apexContents, and modules in that apex have a provider containing the apexContents of each
// apexBundle they are part of.
type ApexContents struct {
// map from a module name to its membership in this apexBundle
contents map[string]ApexMembership
}
// NewApexContents creates and initializes an ApexContents that is suitable
// for use with an apex module.
// - contents is a map from a module name to information about its membership within
// the apex.
func NewApexContents(contents map[string]ApexMembership) *ApexContents {
return &ApexContents{
contents: contents,
}
}
// Updates an existing membership by adding a new direct (or indirect) membership
func (i ApexMembership) Add(direct bool) ApexMembership {
if direct || i == directlyInApex {
return directlyInApex
}
return indirectlyInApex
}
// Merges two membership into one. Merging is needed because a module can be a part of an apexBundle
// in many different paths. For example, it could be dependend on by the apexBundle directly, but at
// the same time, there might be an indirect dependency to the module. In that case, the more
// specific dependency (the direct one) is chosen.
func (i ApexMembership) merge(other ApexMembership) ApexMembership {
if other == directlyInApex || i == directlyInApex {
return directlyInApex
}
if other == indirectlyInApex || i == indirectlyInApex {
return indirectlyInApex
}
return notInApex
}
// Tests whether a module named moduleName is directly included in the apexBundle where this
// ApexContents is tagged.
func (ac *ApexContents) DirectlyInApex(moduleName string) bool {
return ac.contents[moduleName] == directlyInApex
}
// Tests whether a module named moduleName is included in the apexBundle where this ApexContent is
// tagged.
func (ac *ApexContents) InApex(moduleName string) bool {
return ac.contents[moduleName] != notInApex
}
// Tests whether a module named moduleName is directly depended on by all APEXes in an ApexInfo.
func DirectlyInAllApexes(apexInfo ApexInfo, moduleName string) bool {
for _, contents := range apexInfo.ApexContents {
if !contents.DirectlyInApex(moduleName) {
return false
}
}
return true
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//Below are routines for extra safety checks.
//
// BuildDepsInfoLists is to flatten the dependency graph for an apexBundle into a text file
// (actually two in slightly different formats). The files are mostly for debugging, for example to
// see why a certain module is included in an APEX via which dependency path.
//
// CheckMinSdkVersion is to make sure that all modules in an apexBundle satisfy the min_sdk_version
// requirement of the apexBundle.
// A dependency info for a single ApexModule, either direct or transitive.
type ApexModuleDepInfo struct {
// Name of the dependency
To string
// List of dependencies To belongs to. Includes APEX itself, if a direct dependency.
From []string
// Whether the dependency belongs to the final compiled APEX.
IsExternal bool
// min_sdk_version of the ApexModule
MinSdkVersion string
}
// A map of a dependency name to its ApexModuleDepInfo
type DepNameToDepInfoMap map[string]ApexModuleDepInfo
type ApexBundleDepsInfo struct {
flatListPath OutputPath
fullListPath OutputPath
}
type ApexBundleDepsInfoIntf interface {
Updatable() bool
FlatListPath() Path
FullListPath() Path
}
func (d *ApexBundleDepsInfo) FlatListPath() Path {
return d.flatListPath
}
func (d *ApexBundleDepsInfo) FullListPath() Path {
return d.fullListPath
}
// Generate two module out files:
// 1. FullList with transitive deps and their parents in the dep graph
// 2. FlatList with a flat list of transitive deps
// In both cases transitive deps of external deps are not included. Neither are deps that are only
// available to APEXes; they are developed with updatability in mind and don't need manual approval.
func (d *ApexBundleDepsInfo) BuildDepsInfoLists(ctx ModuleContext, minSdkVersion string, depInfos DepNameToDepInfoMap) {
var fullContent strings.Builder
var flatContent strings.Builder
fmt.Fprintf(&fullContent, "%s(minSdkVersion:%s):\n", ctx.ModuleName(), minSdkVersion)
for _, key := range FirstUniqueStrings(SortedKeys(depInfos)) {
info := depInfos[key]
toName := fmt.Sprintf("%s(minSdkVersion:%s)", info.To, info.MinSdkVersion)
if info.IsExternal {
toName = toName + " (external)"
}
fmt.Fprintf(&fullContent, " %s <- %s\n", toName, strings.Join(SortedUniqueStrings(info.From), ", "))
fmt.Fprintf(&flatContent, "%s\n", toName)
}
d.fullListPath = PathForModuleOut(ctx, "depsinfo", "fulllist.txt").OutputPath
WriteFileRule(ctx, d.fullListPath, fullContent.String())
d.flatListPath = PathForModuleOut(ctx, "depsinfo", "flatlist.txt").OutputPath
WriteFileRule(ctx, d.flatListPath, flatContent.String())
ctx.Phony(fmt.Sprintf("%s-depsinfo", ctx.ModuleName()), d.fullListPath, d.flatListPath)
}
// Function called while walking an APEX's payload dependencies.
//
// Return true if the `to` module should be visited, false otherwise.
type PayloadDepsCallback func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool
type WalkPayloadDepsFunc func(ctx ModuleContext, do PayloadDepsCallback)
// ModuleWithMinSdkVersionCheck represents a module that implements min_sdk_version checks
type ModuleWithMinSdkVersionCheck interface {
Module
MinSdkVersion(ctx EarlyModuleContext) ApiLevel
CheckMinSdkVersion(ctx ModuleContext)
}
// CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version
// accordingly
func CheckMinSdkVersion(ctx ModuleContext, minSdkVersion ApiLevel, walk WalkPayloadDepsFunc) {
// do not enforce min_sdk_version for host
if ctx.Host() {
return
}
// do not enforce for coverage build
if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") || ctx.DeviceConfig().NativeCoverageEnabled() || ctx.DeviceConfig().ClangCoverageEnabled() {
return
}
// do not enforce deps.min_sdk_version if APEX/APK doesn't set min_sdk_version
if minSdkVersion.IsNone() {
return
}
walk(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
if externalDep {
// external deps are outside the payload boundary, which is "stable"
// interface. We don't have to check min_sdk_version for external
// dependencies.
return false
}
if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
return false
}
if m, ok := to.(ModuleWithMinSdkVersionCheck); ok {
// This dependency performs its own min_sdk_version check, just make sure it sets min_sdk_version
// to trigger the check.
if !m.MinSdkVersion(ctx).Specified() {
ctx.OtherModuleErrorf(m, "must set min_sdk_version")
}
return false
}
if err := to.ShouldSupportSdkVersion(ctx, minSdkVersion); err != nil {
toName := ctx.OtherModuleName(to)
ctx.OtherModuleErrorf(to, "should support min_sdk_version(%v) for %q: %v."+
"\n\nDependency path: %s\n\n"+
"Consider adding 'min_sdk_version: %q' to %q",
minSdkVersion, ctx.ModuleName(), err.Error(),
ctx.GetPathString(false),
minSdkVersion, toName)
return false
}
return true
})
}
// Construct ApiLevel object from min_sdk_version string value
func MinSdkVersionFromValue(ctx EarlyModuleContext, value string) ApiLevel {
if value == "" {
return NoneApiLevel
}
apiLevel, err := ApiLevelFromUser(ctx, value)
if err != nil {
ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
return NoneApiLevel
}
return apiLevel
}
// Implemented by apexBundle.
type ApexTestInterface interface {
// Return true if the apex bundle is an apex_test
IsTestApex() bool
}
var ApexExportsInfoProvider = blueprint.NewProvider[ApexExportsInfo]()
// ApexExportsInfo contains information about the artifacts provided by apexes to dexpreopt and hiddenapi
type ApexExportsInfo struct {
// Canonical name of this APEX. Used to determine the path to the activated APEX on
// device (/apex/<apex_name>)
ApexName string
// Path to the image profile file on host (or empty, if profile is not generated).
ProfilePathOnHost Path
// Map from the apex library name (without prebuilt_ prefix) to the dex file path on host
LibraryNameToDexJarPathOnHost map[string]Path
}
var PrebuiltInfoProvider = blueprint.NewProvider[PrebuiltInfo]()
// contents of prebuilt_info.json
type PrebuiltInfo struct {
// Name of the apex, without the prebuilt_ prefix
Name string
Is_prebuilt bool
// This is relative to root of the workspace.
// In case of mainline modules, this file contains the build_id that was used
// to generate the mainline module prebuilt.
Prebuilt_info_file_path string `json:",omitempty"`
}