Add java sdk library enforcement flag
Add java sdk library enforcement for inter-partition library
dependency, for ensuring backward-compatible libraries for
inter-partition dependencies.
Test: m nothing
Bug: 168180538
Change-Id: I6bfac54c3499b03003a3bc6c2bb62b165b4ce5f9
diff --git a/java/Android.bp b/java/Android.bp
index 9e8dc78..39502b3 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -48,6 +48,7 @@
"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 7cf04fa..808c389 100644
--- a/java/java.go
+++ b/java/java.go
@@ -756,6 +756,37 @@
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 cf56e66..594cb82 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -784,6 +784,165 @@
})
}
+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 0000000..2934936
--- /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))
+ }
+}