Merge changes I861a60e1,I01bf99fa,I9d16dfec into main am: 9678733731 am: fd440d75e5

Original change: https://android-review.googlesource.com/c/platform/build/soong/+/3150116

Change-Id: I612cdbb3292da68820d7c2446fdba939112a28fc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Treehugger Robot
2024-07-16 01:29:47 +00:00
committed by Automerger Merge Worker
11 changed files with 764 additions and 1 deletions

View File

@@ -42,6 +42,7 @@ bootstrap_go_package {
"buildinfo_prop.go",
"compliance_metadata.go",
"config.go",
"container.go",
"test_config.go",
"configurable_properties.go",
"configured_jars.go",

233
android/container.go Normal file
View File

@@ -0,0 +1,233 @@
// Copyright 2024 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 (
"reflect"
"slices"
"github.com/google/blueprint"
)
type StubsAvailableModule interface {
IsStubsModule() bool
}
// Returns true if the dependency module is a stubs module
var depIsStubsModule = func(_ ModuleContext, _, dep Module) bool {
if stubsModule, ok := dep.(StubsAvailableModule); ok {
return stubsModule.IsStubsModule()
}
return false
}
// Labels of exception functions, which are used to determine special dependencies that allow
// otherwise restricted inter-container dependencies
type exceptionHandleFuncLabel int
const (
checkStubs exceptionHandleFuncLabel = iota
)
// Functions cannot be used as a value passed in providers, because functions are not
// hashable. As a workaround, the exceptionHandleFunc enum values are passed using providers,
// and the corresponding functions are called from this map.
var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]func(ModuleContext, Module, Module) bool{
checkStubs: depIsStubsModule,
}
type InstallableModule interface {
EnforceApiContainerChecks() bool
}
type restriction struct {
// container of the dependency
dependency *container
// Error message to be emitted to the user when the dependency meets this restriction
errorMessage string
// List of labels of allowed exception functions that allows bypassing this restriction.
// If any of the functions mapped to each labels returns true, this dependency would be
// considered allowed and an error will not be thrown.
allowedExceptions []exceptionHandleFuncLabel
}
type container struct {
// The name of the container i.e. partition, api domain
name string
// Map of dependency restricted containers.
restricted []restriction
}
var (
VendorContainer = &container{
name: VendorVariation,
restricted: nil,
}
SystemContainer = &container{
name: "system",
restricted: []restriction{
{
dependency: VendorContainer,
errorMessage: "Module belonging to the system partition other than HALs is " +
"not allowed to depend on the vendor partition module, in order to support " +
"independent development/update cycles and to support the Generic System " +
"Image. Try depending on HALs, VNDK or AIDL instead.",
allowedExceptions: []exceptionHandleFuncLabel{},
},
},
}
ProductContainer = &container{
name: ProductVariation,
restricted: []restriction{
{
dependency: VendorContainer,
errorMessage: "Module belonging to the product partition is not allowed to " +
"depend on the vendor partition module, as this may lead to security " +
"vulnerabilities. Try depending on the HALs or utilize AIDL instead.",
allowedExceptions: []exceptionHandleFuncLabel{},
},
},
}
ApexContainer = initializeApexContainer()
CtsContainer = &container{
name: "cts",
restricted: []restriction{
{
dependency: SystemContainer,
errorMessage: "CTS module should not depend on the modules belonging to the " +
"system partition, including \"framework\". Depending on the system " +
"partition may lead to disclosure of implementation details and regression " +
"due to API changes across platform versions. Try depending on the stubs instead.",
allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
},
},
}
)
func initializeApexContainer() *container {
apexContainer := &container{
name: "apex",
restricted: []restriction{
{
dependency: SystemContainer,
errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
"modules belonging to the system partition. Either statically depend on the " +
"module or convert the depending module to java_sdk_library and depend on " +
"the stubs.",
allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
},
},
}
apexContainer.restricted = append(apexContainer.restricted, restriction{
dependency: apexContainer,
errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
"modules belonging to other Apex(es). Either include the depending " +
"module in the Apex or convert the depending module to java_sdk_library " +
"and depend on its stubs.",
allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
})
return apexContainer
}
type ContainersInfo struct {
belongingContainers []*container
belongingApexes []ApexInfo
}
func (c *ContainersInfo) BelongingContainers() []*container {
return c.belongingContainers
}
var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
// Determines if the module can be installed in the system partition or not.
// Logic is identical to that of modulePartition(...) defined in paths.go
func installInSystemPartition(ctx ModuleContext) bool {
module := ctx.Module()
return !module.InstallInTestcases() &&
!module.InstallInData() &&
!module.InstallInRamdisk() &&
!module.InstallInVendorRamdisk() &&
!module.InstallInDebugRamdisk() &&
!module.InstallInRecovery() &&
!module.InstallInVendor() &&
!module.InstallInOdm() &&
!module.InstallInProduct() &&
determineModuleKind(module.base(), ctx.blueprintBaseModuleContext()) == platformModule
}
func generateContainerInfo(ctx ModuleContext) ContainersInfo {
inSystem := installInSystemPartition(ctx)
inProduct := ctx.Module().InstallInProduct()
inVendor := ctx.Module().InstallInVendor()
inCts := false
inApex := false
if m, ok := ctx.Module().(ImageInterface); ok {
inProduct = inProduct || m.ProductVariantNeeded(ctx)
inVendor = inVendor || m.VendorVariantNeeded(ctx)
}
props := ctx.Module().GetProperties()
for _, prop := range props {
val := reflect.ValueOf(prop).Elem()
if val.Kind() == reflect.Struct {
testSuites := val.FieldByName("Test_suites")
if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
inCts = true
}
}
}
var belongingApexes []ApexInfo
if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
belongingApexes = apexInfo.ApexInfos
inApex = true
}
containers := []*container{}
if inSystem {
containers = append(containers, SystemContainer)
}
if inProduct {
containers = append(containers, ProductContainer)
}
if inVendor {
containers = append(containers, VendorContainer)
}
if inCts {
containers = append(containers, CtsContainer)
}
if inApex {
containers = append(containers, ApexContainer)
}
return ContainersInfo{
belongingContainers: containers,
belongingApexes: belongingApexes,
}
}
func setContainerInfo(ctx ModuleContext) {
if _, ok := ctx.Module().(InstallableModule); ok {
containersInfo := generateContainerInfo(ctx)
SetProvider(ctx, ContainersInfoProvider, containersInfo)
}
}

View File

@@ -22,7 +22,7 @@ type ImageInterface interface {
// VendorVariantNeeded should return true if the module needs a vendor variant (installed on the vendor image).
VendorVariantNeeded(ctx BaseModuleContext) bool
// ProductVariantNeeded should return true if the module needs a product variant (unstalled on the product image).
// ProductVariantNeeded should return true if the module needs a product variant (installed on the product image).
ProductVariantNeeded(ctx BaseModuleContext) bool
// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).

View File

@@ -1779,6 +1779,8 @@ func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext)
variables: make(map[string]string),
}
setContainerInfo(ctx)
m.licenseMetadataFile = PathForModuleOut(ctx, "meta_lic")
dependencyInstallFiles, dependencyPackagingSpecs := m.computeInstallDeps(ctx)

View File

@@ -201,6 +201,12 @@ func ListSetDifference[T comparable](l1, l2 []T) (bool, []T, []T) {
return listsDiffer, diff1, diff2
}
// Returns true if the two lists have common elements.
func HasIntersection[T comparable](l1, l2 []T) bool {
_, a, b := ListSetDifference(l1, l2)
return len(a)+len(b) < len(setFromList(l1))+len(setFromList(l2))
}
// Returns true if the given string s is prefixed with any string in the given prefix list.
func HasAnyPrefix(s string, prefixList []string) bool {
for _, prefix := range prefixList {

View File

@@ -818,3 +818,52 @@ func TestReverseSlice(t *testing.T) {
})
}
}
var hasIntersectionTestCases = []struct {
name string
l1 []string
l2 []string
expected bool
}{
{
name: "empty",
l1: []string{"a", "b", "c"},
l2: []string{},
expected: false,
},
{
name: "both empty",
l1: []string{},
l2: []string{},
expected: false,
},
{
name: "identical",
l1: []string{"a", "b", "c"},
l2: []string{"a", "b", "c"},
expected: true,
},
{
name: "duplicates",
l1: []string{"a", "a", "a"},
l2: []string{"a", "b", "c"},
expected: true,
},
{
name: "duplicates with no intersection",
l1: []string{"d", "d", "d", "d"},
l2: []string{"a", "b", "c"},
expected: false,
},
}
func TestHasIntersection(t *testing.T) {
for _, testCase := range hasIntersectionTestCases {
t.Run(testCase.name, func(t *testing.T) {
hasIntersection := HasIntersection(testCase.l1, testCase.l2)
if !reflect.DeepEqual(hasIntersection, testCase.expected) {
t.Errorf("expected %#v, got %#v", testCase.expected, hasIntersection)
}
})
}
}

View File

@@ -37,6 +37,7 @@ bootstrap_go_package {
"apex_test.go",
"bootclasspath_fragment_test.go",
"classpath_element_test.go",
"container_test.go",
"dexpreopt_bootjars_test.go",
"platform_bootclasspath_test.go",
"systemserver_classpath_fragment_test.go",

329
apex/container_test.go Normal file
View File

@@ -0,0 +1,329 @@
// Copyright 2024 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 apex
import (
"android/soong/android"
"android/soong/java"
"fmt"
"testing"
)
var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
android.AssertBoolEquals(t, errorMessage, expected, actual)
}
func TestApexDepsContainers(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForApexTest,
java.PrepareForTestWithJavaSdkLibraryFiles,
java.FixtureWithLastReleaseApis("mybootclasspathlib"),
).RunTestWithBp(t, `
apex {
name: "myapex",
key: "myapex.key",
bootclasspath_fragments: [
"mybootclasspathfragment",
],
updatable: true,
min_sdk_version: "30",
}
apex_key {
name: "myapex.key",
public_key: "testkey.avbpubkey",
private_key: "testkey.pem",
}
bootclasspath_fragment {
name: "mybootclasspathfragment",
contents: [
"mybootclasspathlib",
],
apex_available: [
"myapex",
],
hidden_api: {
split_packages: ["*"],
},
}
java_sdk_library {
name: "mybootclasspathlib",
srcs: [
"mybootclasspathlib.java",
],
apex_available: [
"myapex",
],
compile_dex: true,
static_libs: [
"foo",
"baz",
],
libs: [
"bar",
],
min_sdk_version: "30",
}
java_library {
name: "foo",
srcs:[
"A.java",
],
apex_available: [
"myapex",
],
min_sdk_version: "30",
}
java_library {
name: "bar",
srcs:[
"A.java",
],
min_sdk_version: "30",
}
java_library {
name: "baz",
srcs:[
"A.java",
],
apex_available: [
"//apex_available:platform",
"myapex",
],
min_sdk_version: "30",
}
`)
testcases := []struct {
moduleName string
variant string
isSystemContainer bool
isApexContainer bool
}{
{
moduleName: "mybootclasspathlib",
variant: "android_common_myapex",
isSystemContainer: true,
isApexContainer: true,
},
{
moduleName: "mybootclasspathlib.impl",
variant: "android_common_apex30",
isSystemContainer: true,
isApexContainer: true,
},
{
moduleName: "mybootclasspathlib.stubs",
variant: "android_common",
isSystemContainer: true,
isApexContainer: false,
},
{
moduleName: "foo",
variant: "android_common_apex30",
isSystemContainer: true,
isApexContainer: true,
},
{
moduleName: "bar",
variant: "android_common",
isSystemContainer: true,
isApexContainer: false,
},
{
moduleName: "baz",
variant: "android_common_apex30",
isSystemContainer: true,
isApexContainer: true,
},
}
for _, c := range testcases {
m := result.ModuleForTests(c.moduleName, c.variant)
containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
belongingContainers := containers.BelongingContainers()
checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
}
}
func TestNonUpdatableApexDepsContainers(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForApexTest,
java.PrepareForTestWithJavaSdkLibraryFiles,
java.FixtureWithLastReleaseApis("mybootclasspathlib"),
).RunTestWithBp(t, `
apex {
name: "myapex",
key: "myapex.key",
bootclasspath_fragments: [
"mybootclasspathfragment",
],
updatable: false,
}
apex_key {
name: "myapex.key",
public_key: "testkey.avbpubkey",
private_key: "testkey.pem",
}
bootclasspath_fragment {
name: "mybootclasspathfragment",
contents: [
"mybootclasspathlib",
],
apex_available: [
"myapex",
],
hidden_api: {
split_packages: ["*"],
},
}
java_sdk_library {
name: "mybootclasspathlib",
srcs: [
"mybootclasspathlib.java",
],
apex_available: [
"myapex",
],
compile_dex: true,
static_libs: [
"foo",
],
libs: [
"bar",
],
}
java_library {
name: "foo",
srcs:[
"A.java",
],
apex_available: [
"myapex",
],
}
java_library {
name: "bar",
srcs:[
"A.java",
],
}
`)
testcases := []struct {
moduleName string
variant string
isSystemContainer bool
isApexContainer bool
}{
{
moduleName: "mybootclasspathlib",
variant: "android_common_myapex",
isSystemContainer: true,
isApexContainer: true,
},
{
moduleName: "mybootclasspathlib.impl",
variant: "android_common_apex10000",
isSystemContainer: true,
isApexContainer: true,
},
{
moduleName: "mybootclasspathlib.stubs",
variant: "android_common",
isSystemContainer: true,
isApexContainer: false,
},
{
moduleName: "foo",
variant: "android_common_apex10000",
isSystemContainer: true,
isApexContainer: true,
},
{
moduleName: "bar",
variant: "android_common",
isSystemContainer: true,
isApexContainer: false,
},
}
for _, c := range testcases {
m := result.ModuleForTests(c.moduleName, c.variant)
containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
belongingContainers := containers.BelongingContainers()
checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
}
}
func TestUpdatableAndNonUpdatableApexesIdenticalMinSdkVersion(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForApexTest,
java.PrepareForTestWithJavaSdkLibraryFiles,
android.FixtureMergeMockFs(android.MockFS{
"system/sepolicy/apex/myapex_non_updatable-file_contexts": nil,
"system/sepolicy/apex/myapex_updatable-file_contexts": nil,
}),
).RunTestWithBp(t, `
apex {
name: "myapex_non_updatable",
key: "myapex_non_updatable.key",
java_libs: [
"foo",
],
updatable: false,
min_sdk_version: "30",
}
apex_key {
name: "myapex_non_updatable.key",
public_key: "testkey.avbpubkey",
private_key: "testkey.pem",
}
apex {
name: "myapex_updatable",
key: "myapex_updatable.key",
java_libs: [
"foo",
],
updatable: true,
min_sdk_version: "30",
}
apex_key {
name: "myapex_updatable.key",
public_key: "testkey.avbpubkey",
private_key: "testkey.pem",
}
java_library {
name: "foo",
srcs:[
"A.java",
],
apex_available: [
"myapex_non_updatable",
"myapex_updatable",
],
min_sdk_version: "30",
sdk_version: "current",
}
`)
fooApexVariant := result.ModuleForTests("foo", "android_common_apex30")
containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), fooApexVariant.Module(), android.ContainersInfoProvider)
belongingContainers := containers.BelongingContainers()
checkContainerMatch(t, "foo", "system", true, android.InList(android.SystemContainer, belongingContainers))
checkContainerMatch(t, "foo", "apex", true, android.InList(android.ApexContainer, belongingContainers))
}

View File

@@ -87,6 +87,7 @@ bootstrap_go_package {
"app_set_test.go",
"app_test.go",
"code_metadata_test.go",
"container_test.go",
"bootclasspath_fragment_test.go",
"device_host_converter_test.go",
"dex_test.go",

View File

@@ -549,6 +549,18 @@ type Module struct {
aconfigCacheFiles android.Paths
}
var _ android.InstallableModule = (*Module)(nil)
// To satisfy the InstallableModule interface
func (j *Module) EnforceApiContainerChecks() bool {
return true
}
// Overrides android.ModuleBase.InstallInProduct()
func (j *Module) InstallInProduct() bool {
return j.ProductSpecific()
}
func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
sdkVersion := j.SdkVersion(ctx)
if sdkVersion.Stable() {

129
java/container_test.go Normal file
View File

@@ -0,0 +1,129 @@
// Copyright 2024 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 java
import (
"android/soong/android"
"fmt"
"testing"
)
var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
android.AssertBoolEquals(t, errorMessage, expected, actual)
}
func TestJavaContainersModuleProperties(t *testing.T) {
result := android.GroupFixturePreparers(
prepareForJavaTest,
).RunTestWithBp(t, `
java_library {
name: "foo",
srcs: ["A.java"],
}
java_library {
name: "foo_vendor",
srcs: ["A.java"],
vendor: true,
sdk_version: "current",
}
java_library {
name: "foo_soc_specific",
srcs: ["A.java"],
soc_specific: true,
sdk_version: "current",
}
java_library {
name: "foo_product_specific",
srcs: ["A.java"],
product_specific: true,
sdk_version: "current",
}
java_test {
name: "foo_cts_test",
srcs: ["A.java"],
test_suites: [
"cts",
],
}
java_test {
name: "foo_non_cts_test",
srcs: ["A.java"],
test_suites: [
"general-tests",
],
}
`)
testcases := []struct {
moduleName string
isSystemContainer bool
isVendorContainer bool
isProductContainer bool
isCts bool
}{
{
moduleName: "foo",
isSystemContainer: true,
isVendorContainer: false,
isProductContainer: false,
isCts: false,
},
{
moduleName: "foo_vendor",
isSystemContainer: false,
isVendorContainer: true,
isProductContainer: false,
isCts: false,
},
{
moduleName: "foo_soc_specific",
isSystemContainer: false,
isVendorContainer: true,
isProductContainer: false,
isCts: false,
},
{
moduleName: "foo_product_specific",
isSystemContainer: false,
isVendorContainer: false,
isProductContainer: true,
isCts: false,
},
{
moduleName: "foo_cts_test",
isSystemContainer: false,
isVendorContainer: false,
isProductContainer: false,
isCts: true,
},
{
moduleName: "foo_non_cts_test",
isSystemContainer: false,
isVendorContainer: false,
isProductContainer: false,
isCts: false,
},
}
for _, c := range testcases {
m := result.ModuleForTests(c.moduleName, "android_common")
containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
belongingContainers := containers.BelongingContainers()
checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
checkContainerMatch(t, c.moduleName, "vendor", c.isVendorContainer, android.InList(android.VendorContainer, belongingContainers))
checkContainerMatch(t, c.moduleName, "product", c.isProductContainer, android.InList(android.ProductContainer, belongingContainers))
}
}