diff --git a/android/config.go b/android/config.go index 04c91295d..57ab70b29 100644 --- a/android/config.go +++ b/android/config.go @@ -1321,6 +1321,14 @@ func (c *config) EnforceProductPartitionInterface() bool { return Bool(c.productVariables.EnforceProductPartitionInterface) } +func (c *config) EnforceInterPartitionJavaSdkLibrary() bool { + return Bool(c.productVariables.EnforceInterPartitionJavaSdkLibrary) +} + +func (c *config) InterPartitionJavaLibraryAllowList() []string { + return c.productVariables.InterPartitionJavaLibraryAllowList +} + func (c *config) InstallExtraFlattenedApexes() bool { return Bool(c.productVariables.InstallExtraFlattenedApexes) } diff --git a/android/variable.go b/android/variable.go index a9a9c87c2..0df5272c0 100644 --- a/android/variable.go +++ b/android/variable.go @@ -347,6 +347,9 @@ type productVariables struct { EnforceProductPartitionInterface *bool `json:",omitempty"` + EnforceInterPartitionJavaSdkLibrary *bool `json:",omitempty"` + InterPartitionJavaLibraryAllowList []string `json:",omitempty"` + InstallExtraFlattenedApexes *bool `json:",omitempty"` BoardUsesRecoveryAsBoot *bool `json:",omitempty"` diff --git a/java/Android.bp b/java/Android.bp index 9e8dc786b..39502b3e1 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -48,6 +48,7 @@ bootstrap_go_package { "robolectric.go", "sdk.go", "sdk_library.go", + "sdk_library_external.go", "support_libraries.go", "sysprop.go", "system_modules.go", diff --git a/java/java.go b/java/java.go index 8738e00cf..9000f7478 100644 --- a/java/java.go +++ b/java/java.go @@ -772,6 +772,37 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...) ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...) + if ctx.DeviceConfig().VndkVersion() != "" && ctx.Config().EnforceInterPartitionJavaSdkLibrary() { + // Require java_sdk_library at inter-partition java dependency to ensure stable + // interface between partitions. If inter-partition java_library dependency is detected, + // raise build error because java_library doesn't have a stable interface. + // + // Inputs: + // PRODUCT_ENFORCE_INTER_PARTITION_JAVA_SDK_LIBRARY + // if true, enable enforcement + // PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST + // exception list of java_library names to allow inter-partition dependency + for idx, lib := range j.properties.Libs { + if libDeps[idx] == nil { + continue + } + + if _, ok := syspropPublicStubs[lib]; ok { + continue + } + + if javaDep, ok := libDeps[idx].(javaSdkLibraryEnforceContext); ok { + // java_sdk_library is always allowed at inter-partition dependency. + // So, skip check. + if _, ok := javaDep.(*SdkLibrary); ok { + continue + } + + j.checkPartitionsForJavaDependency(ctx, "libs", javaDep) + } + } + } + // For library dependencies that are component libraries (like stubs), add the implementation // as a dependency (dexpreopt needs to be against the implementation library, not stubs). for _, dep := range libDeps { diff --git a/java/java_test.go b/java/java_test.go index 83db4433c..f7cf03f4d 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -805,6 +805,165 @@ func TestJavaSdkLibraryImport_Preferred(t *testing.T) { }) } +func TestJavaSdkLibraryEnforce(t *testing.T) { + partitionToBpOption := func(partition string) string { + switch partition { + case "system": + return "" + case "vendor": + return "soc_specific: true," + case "product": + return "product_specific: true," + default: + panic("Invalid partition group name: " + partition) + } + } + + type testConfigInfo struct { + libraryType string + fromPartition string + toPartition string + enforceVendorInterface bool + enforceProductInterface bool + enforceJavaSdkLibraryCheck bool + allowList []string + } + + createTestConfig := func(info testConfigInfo) android.Config { + bpFileTemplate := ` + java_library { + name: "foo", + srcs: ["foo.java"], + libs: ["bar"], + sdk_version: "current", + %s + } + + %s { + name: "bar", + srcs: ["bar.java"], + sdk_version: "current", + %s + } + ` + + bpFile := fmt.Sprintf(bpFileTemplate, + partitionToBpOption(info.fromPartition), + info.libraryType, + partitionToBpOption(info.toPartition)) + + config := testConfig(nil, bpFile, nil) + configVariables := config.TestProductVariables + + configVariables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface) + if info.enforceVendorInterface { + configVariables.DeviceVndkVersion = proptools.StringPtr("current") + } + configVariables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck) + configVariables.InterPartitionJavaLibraryAllowList = info.allowList + + return config + } + + isValidDependency := func(configInfo testConfigInfo) bool { + if configInfo.enforceVendorInterface == false { + return true + } + + if configInfo.enforceJavaSdkLibraryCheck == false { + return true + } + + if inList("bar", configInfo.allowList) { + return true + } + + if configInfo.libraryType == "java_library" { + if configInfo.fromPartition != configInfo.toPartition { + if !configInfo.enforceProductInterface && + ((configInfo.fromPartition == "system" && configInfo.toPartition == "product") || + (configInfo.fromPartition == "product" && configInfo.toPartition == "system")) { + return true + } + return false + } + } + + return true + } + + errorMessage := "is not allowed across the partitions" + + allPartitionCombinations := func() [][2]string { + var result [][2]string + partitions := []string{"system", "vendor", "product"} + + for _, fromPartition := range partitions { + for _, toPartition := range partitions { + result = append(result, [2]string{fromPartition, toPartition}) + } + } + + return result + } + + allFlagCombinations := func() [][3]bool { + var result [][3]bool + flagValues := [2]bool{false, true} + + for _, vendorInterface := range flagValues { + for _, productInterface := range flagValues { + for _, enableEnforce := range flagValues { + result = append(result, [3]bool{vendorInterface, productInterface, enableEnforce}) + } + } + } + + return result + } + + for _, libraryType := range []string{"java_library", "java_sdk_library"} { + for _, partitionValues := range allPartitionCombinations() { + for _, flagValues := range allFlagCombinations() { + testInfo := testConfigInfo{ + libraryType: libraryType, + fromPartition: partitionValues[0], + toPartition: partitionValues[1], + enforceVendorInterface: flagValues[0], + enforceProductInterface: flagValues[1], + enforceJavaSdkLibraryCheck: flagValues[2], + } + + if isValidDependency(testInfo) { + testJavaWithConfig(t, createTestConfig(testInfo)) + } else { + testJavaErrorWithConfig(t, errorMessage, createTestConfig(testInfo)) + } + } + } + } + + testJavaWithConfig(t, createTestConfig(testConfigInfo{ + libraryType: "java_library", + fromPartition: "vendor", + toPartition: "system", + enforceVendorInterface: true, + enforceProductInterface: true, + enforceJavaSdkLibraryCheck: true, + allowList: []string{"bar"}, + })) + + testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{ + libraryType: "java_library", + fromPartition: "vendor", + toPartition: "system", + enforceVendorInterface: true, + enforceProductInterface: true, + enforceJavaSdkLibraryCheck: true, + allowList: []string{"foo"}, + })) +} + func TestDefaults(t *testing.T) { ctx, _ := testJava(t, ` java_defaults { diff --git a/java/sdk_library_external.go b/java/sdk_library_external.go new file mode 100644 index 000000000..293493685 --- /dev/null +++ b/java/sdk_library_external.go @@ -0,0 +1,109 @@ +// Copyright 2020 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" +) + +type partitionGroup int + +// Representation of partition group for checking inter-partition library dependencies. +// Between system and system_ext, there are no restrictions of dependencies, +// so we can treat these partitions as the same in terms of inter-partition dependency. +// Same policy is applied between vendor and odm partiton. +const ( + partitionGroupNone partitionGroup = iota + // group for system, and system_ext partition + partitionGroupSystem + // group for vendor and odm partition + partitionGroupVendor + // product partition + partitionGroupProduct +) + +func (g partitionGroup) String() string { + switch g { + case partitionGroupSystem: + return "system" + case partitionGroupVendor: + return "vendor" + case partitionGroupProduct: + return "product" + } + + return "" +} + +// Get partition group of java module that can be used at inter-partition dependency check. +// We currently have three groups +// (system, system_ext) => system partition group +// (vendor, odm) => vendor partition group +// (product) => product partition group +func (j *Module) partitionGroup(ctx android.EarlyModuleContext) partitionGroup { + // system and system_ext partition can be treated as the same in terms of inter-partition dependency. + if j.Platform() || j.SystemExtSpecific() { + return partitionGroupSystem + } + + // vendor and odm partition can be treated as the same in terms of inter-partition dependency. + if j.SocSpecific() || j.DeviceSpecific() { + return partitionGroupVendor + } + + // product partition is independent. + if j.ProductSpecific() { + return partitionGroupProduct + } + + panic("Cannot determine partition type") +} + +func (j *Module) allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool { + return inList(j.Name(), ctx.Config().InterPartitionJavaLibraryAllowList()) +} + +type javaSdkLibraryEnforceContext interface { + Name() string + allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool + partitionGroup(ctx android.EarlyModuleContext) partitionGroup +} + +var _ javaSdkLibraryEnforceContext = (*Module)(nil) + +func (j *Module) checkPartitionsForJavaDependency(ctx android.EarlyModuleContext, propName string, dep javaSdkLibraryEnforceContext) { + if dep.allowListedInterPartitionJavaLibrary(ctx) { + return + } + + // If product interface is not enforced, skip check between system and product partition. + // But still need to check between product and vendor partition because product interface flag + // just represents enforcement between product and system, and vendor interface enforcement + // that is enforced here by precondition is representing enforcement between vendor and other partitions. + if !ctx.Config().EnforceProductPartitionInterface() { + productToSystem := j.partitionGroup(ctx) == partitionGroupProduct && dep.partitionGroup(ctx) == partitionGroupSystem + systemToProduct := j.partitionGroup(ctx) == partitionGroupSystem && dep.partitionGroup(ctx) == partitionGroupProduct + + if productToSystem || systemToProduct { + return + } + } + + // If module and dependency library is inter-partition + if j.partitionGroup(ctx) != dep.partitionGroup(ctx) { + errorFormat := "dependency on java_library (%q) is not allowed across the partitions (%s -> %s), use java_sdk_library instead" + ctx.PropertyErrorf(propName, errorFormat, dep.Name(), j.partitionGroup(ctx), dep.partitionGroup(ctx)) + } +}