From b3821fea387c927ec96688e5b6e2033bf1c379b0 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Wed, 26 May 2021 10:16:01 +0100 Subject: [PATCH] Support hidden API processing for modules that use platform APIs Previously, hidden API processing could only be done by those bootclasspath_fragment modules that either did not depend on any other fragments (e.g. art-bootclasspath-fragment) or only depended on APIs provided by other fragments (e.g. i18n-bootclasspath-fragment). That meant that modules like com.android.os.statsd-bootclasspath-fragment that depended on APIs provided by parts of the platform which are not yet part of another bootclasspath_fragment could not perform hidden API processing. This change adds support for a bootclasspath_fragment to specify the additional stubs needed to perform hidden API processing. It adds a new additional_stubs property that can be used to specify the additional stub libraries. Most bootclasspath_fragments that need to use the property will need access to the APIs provided by the android-non-updatable.* libraries. Rather than have each fragment explicitly specify the correct module for each scope it treats "android-non-updatable" as if it was a java_sdk_library that can provide different jars for each scope. Soong will handle mapping that to the correct android-non-updatable.* module. Bug: 179354495 Test: m out/soong/hiddenapi/hiddenapi-flags.csv \ out/soong/hiddenapi/hiddenapi-index.csv \ out/soong/hiddenapi/hiddenapi-stub-flags.txt \ out/soong/hiddenapi/hiddenapi-unsupported.csv - make sure that this change does not change the contents. m TARGET_BUILD_APPS=Calendar nothing Merged-In: Ia8b79830ed0e6d42100de03d76b0c51b7f6c8ade Change-Id: Ia8b79830ed0e6d42100de03d76b0c51b7f6c8ade (cherry picked from commit 5cca7c44e51d0d08a5ea842d0f9870a772529dec) --- android/sdk.go | 2 + apex/bootclasspath_fragment_test.go | 327 ++++++++++++++++++++++++++++ java/bootclasspath_fragment.go | 22 +- java/hiddenapi_modular.go | 107 ++++++++- sdk/update.go | 5 + 5 files changed, 451 insertions(+), 12 deletions(-) diff --git a/android/sdk.go b/android/sdk.go index e644806c4..42c8ffa32 100644 --- a/android/sdk.go +++ b/android/sdk.go @@ -359,6 +359,8 @@ type SdkMemberTypeDependencyTag interface { // SdkMemberType returns the SdkMemberType that will be used to automatically add the child module // to the sdk. + // + // Returning nil will prevent the module being added to the sdk. SdkMemberType(child Module) SdkMemberType // ExportMember determines whether a module added to the sdk through this tag will be exported diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go index f97404a63..8554c19c2 100644 --- a/apex/bootclasspath_fragment_test.go +++ b/apex/bootclasspath_fragment_test.go @@ -22,6 +22,7 @@ import ( "android/soong/android" "android/soong/java" + "github.com/google/blueprint/proptools" ) // Contains tests for bootclasspath_fragment logic from java/bootclasspath_fragment.go as the ART @@ -868,4 +869,330 @@ func TestBootclasspathFragment_HiddenAPIList(t *testing.T) { android.AssertStringDoesContain(t, "test", command, "--test-stub-classpath="+quuzTestStubs+":"+fooStubs) } +// TestBootclasspathFragment_AndroidNonUpdatable checks to make sure that setting +// additional_stubs: ["android-non-updatable"] causes the source android-non-updatable modules to be +// added to the hiddenapi list tool. +func TestBootclasspathFragment_AndroidNonUpdatable(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForTestWithBootclasspathFragment, + prepareForTestWithArtApex, + prepareForTestWithMyapex, + // Configure bootclasspath jars to ensure that hidden API encoding is performed on them. + java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "myapex:foo", "myapex:bar"), + // Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding + // is disabled. + android.FixtureAddTextFile("frameworks/base/Android.bp", ""), + + java.PrepareForTestWithJavaSdkLibraryFiles, + java.FixtureWithLastReleaseApis("foo", "android-non-updatable"), + ).RunTestWithBp(t, ` + java_sdk_library { + name: "android-non-updatable", + srcs: ["b.java"], + compile_dex: true, + public: { + enabled: true, + }, + system: { + enabled: true, + }, + test: { + enabled: true, + }, + module_lib: { + enabled: true, + }, + } + + apex { + name: "com.android.art", + key: "com.android.art.key", + bootclasspath_fragments: ["art-bootclasspath-fragment"], + java_libs: [ + "baz", + "quuz", + ], + updatable: false, + } + + apex_key { + name: "com.android.art.key", + public_key: "com.android.art.avbpubkey", + private_key: "com.android.art.pem", + } + + java_library { + name: "baz", + apex_available: [ + "com.android.art", + ], + srcs: ["b.java"], + compile_dex: true, + } + + java_library { + name: "quuz", + apex_available: [ + "com.android.art", + ], + srcs: ["b.java"], + compile_dex: true, + } + + bootclasspath_fragment { + name: "art-bootclasspath-fragment", + image_name: "art", + // Must match the "com.android.art:" entries passed to FixtureConfigureBootJars above. + contents: ["baz", "quuz"], + apex_available: [ + "com.android.art", + ], + } + + apex { + name: "myapex", + key: "myapex.key", + bootclasspath_fragments: [ + "mybootclasspathfragment", + ], + updatable: false, + } + + apex_key { + name: "myapex.key", + public_key: "testkey.avbpubkey", + private_key: "testkey.pem", + } + + java_sdk_library { + name: "foo", + srcs: ["b.java"], + shared_library: false, + public: {enabled: true}, + apex_available: [ + "myapex", + ], + } + + java_library { + name: "bar", + srcs: ["b.java"], + installable: true, + apex_available: [ + "myapex", + ], + } + + bootclasspath_fragment { + name: "mybootclasspathfragment", + contents: [ + "foo", + "bar", + ], + apex_available: [ + "myapex", + ], + additional_stubs: ["android-non-updatable"], + fragments: [ + { + apex: "com.android.art", + module: "art-bootclasspath-fragment", + }, + ], + } + `) + + java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_apex10000", []string{ + "android-non-updatable.stubs", + "android-non-updatable.stubs.module_lib", + "android-non-updatable.stubs.system", + "android-non-updatable.stubs.test", + "art-bootclasspath-fragment", + "bar", + "dex2oatd", + "foo", + }) + + nonUpdatablePublicStubs := getDexJarPath(result, "android-non-updatable.stubs") + nonUpdatableSystemStubs := getDexJarPath(result, "android-non-updatable.stubs.system") + nonUpdatableTestStubs := getDexJarPath(result, "android-non-updatable.stubs.test") + nonUpdatableModuleLibStubs := getDexJarPath(result, "android-non-updatable.stubs.module_lib") + + // Make sure that the fragment uses the android-non-updatable modules when generating the hidden + // API flags. + fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000") + + rule := fragment.Rule("modularHiddenAPIStubFlagsFile") + command := rule.RuleParams.Command + android.AssertStringDoesContain(t, "check correct rule", command, "hiddenapi list") + + // Make sure that the module_lib non-updatable stubs are available for resolving references from + // the implementation boot dex jars provided by this module. + android.AssertStringDoesContain(t, "android-non-updatable widest", command, "--dependency-stub-dex="+nonUpdatableModuleLibStubs) + + // Make sure that the appropriate non-updatable stubs are available for resolving references from + // the different API stubs provided by this module. + android.AssertStringDoesContain(t, "public", command, "--public-stub-classpath="+nonUpdatablePublicStubs) + android.AssertStringDoesContain(t, "system", command, "--system-stub-classpath="+nonUpdatableSystemStubs) + android.AssertStringDoesContain(t, "test", command, "--test-stub-classpath="+nonUpdatableTestStubs) +} + +// TestBootclasspathFragment_AndroidNonUpdatable_AlwaysUsePrebuiltSdks checks to make sure that +// setting additional_stubs: ["android-non-updatable"] causes the prebuilt android-non-updatable +// modules to be added to the hiddenapi list tool. +func TestBootclasspathFragment_AndroidNonUpdatable_AlwaysUsePrebuiltSdks(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForTestWithBootclasspathFragment, + java.PrepareForTestWithJavaDefaultModules, + prepareForTestWithArtApex, + prepareForTestWithMyapex, + // Configure bootclasspath jars to ensure that hidden API encoding is performed on them. + java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "myapex:foo", "myapex:bar"), + // Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding + // is disabled. + android.FixtureAddTextFile("frameworks/base/Android.bp", ""), + + android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { + variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true) + }), + + java.PrepareForTestWithJavaSdkLibraryFiles, + java.FixtureWithPrebuiltApis(map[string][]string{ + "current": {"android-non-updatable"}, + "30": {"foo"}, + }), + ).RunTestWithBp(t, ` + apex { + name: "com.android.art", + key: "com.android.art.key", + bootclasspath_fragments: ["art-bootclasspath-fragment"], + java_libs: [ + "baz", + "quuz", + ], + updatable: false, + } + + apex_key { + name: "com.android.art.key", + public_key: "com.android.art.avbpubkey", + private_key: "com.android.art.pem", + } + + java_library { + name: "baz", + apex_available: [ + "com.android.art", + ], + srcs: ["b.java"], + compile_dex: true, + } + + java_library { + name: "quuz", + apex_available: [ + "com.android.art", + ], + srcs: ["b.java"], + compile_dex: true, + } + + bootclasspath_fragment { + name: "art-bootclasspath-fragment", + image_name: "art", + // Must match the "com.android.art:" entries passed to FixtureConfigureBootJars above. + contents: ["baz", "quuz"], + apex_available: [ + "com.android.art", + ], + } + + apex { + name: "myapex", + key: "myapex.key", + bootclasspath_fragments: [ + "mybootclasspathfragment", + ], + updatable: false, + } + + apex_key { + name: "myapex.key", + public_key: "testkey.avbpubkey", + private_key: "testkey.pem", + } + + java_sdk_library { + name: "foo", + srcs: ["b.java"], + shared_library: false, + public: {enabled: true}, + apex_available: [ + "myapex", + ], + } + + java_library { + name: "bar", + srcs: ["b.java"], + installable: true, + apex_available: [ + "myapex", + ], + } + + bootclasspath_fragment { + name: "mybootclasspathfragment", + contents: [ + "foo", + "bar", + ], + apex_available: [ + "myapex", + ], + additional_stubs: ["android-non-updatable"], + fragments: [ + { + apex: "com.android.art", + module: "art-bootclasspath-fragment", + }, + ], + } + `) + + java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_apex10000", []string{ + "art-bootclasspath-fragment", + "bar", + "dex2oatd", + "foo", + "prebuilt_sdk_module-lib_current_android-non-updatable", + "prebuilt_sdk_public_current_android-non-updatable", + "prebuilt_sdk_system_current_android-non-updatable", + "prebuilt_sdk_test_current_android-non-updatable", + }) + + nonUpdatablePublicStubs := getDexJarPath(result, "sdk_public_current_android-non-updatable") + nonUpdatableSystemStubs := getDexJarPath(result, "sdk_system_current_android-non-updatable") + nonUpdatableTestStubs := getDexJarPath(result, "sdk_test_current_android-non-updatable") + nonUpdatableModuleLibStubs := getDexJarPath(result, "sdk_module-lib_current_android-non-updatable") + + // Make sure that the fragment uses the android-non-updatable modules when generating the hidden + // API flags. + fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000") + + rule := fragment.Rule("modularHiddenAPIStubFlagsFile") + command := rule.RuleParams.Command + android.AssertStringDoesContain(t, "check correct rule", command, "hiddenapi list") + + // Make sure that the module_lib non-updatable stubs are available for resolving references from + // the implementation boot dex jars provided by this module. + android.AssertStringDoesContain(t, "android-non-updatable widest", command, "--dependency-stub-dex="+nonUpdatableModuleLibStubs) + + // Make sure that the appropriate non-updatable stubs are available for resolving references from + // the different API stubs provided by this module. + android.AssertStringDoesContain(t, "public", command, "--public-stub-classpath="+nonUpdatablePublicStubs) + android.AssertStringDoesContain(t, "system", command, "--system-stub-classpath="+nonUpdatableSystemStubs) + android.AssertStringDoesContain(t, "test", command, "--test-stub-classpath="+nonUpdatableTestStubs) +} + // TODO(b/177892522) - add test for host apex. diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 3f08d059d..62760c1ef 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -124,6 +124,15 @@ type bootclasspathFragmentProperties struct { // Hidden API related properties. Hidden_api HiddenAPIFlagFileProperties + // The list of additional stub libraries which this fragment's contents use but which are not + // provided by another bootclasspath_fragment. + // + // Note, "android-non-updatable" is treated specially. While no such module exists it is treated + // as if it was a java_sdk_library. So, when public API stubs are needed then it will be replaced + // with "android-non-updatable.stubs", with "androidn-non-updatable.system.stubs" when the system + // stubs are needed and so on. + Additional_stubs []string + // Properties that allow a fragment to depend on other fragments. This is needed for hidden API // processing as it needs access to all the classes used by a fragment including those provided // by other fragments. @@ -378,6 +387,15 @@ func (b *BootclasspathFragmentModule) DepsMutator(ctx android.BottomUpMutatorCon // bootclasspath fragment. hiddenAPIAddStubLibDependencies(ctx, b.properties.apiScopeToStubLibs()) + for _, additionalStubModule := range b.properties.Additional_stubs { + for _, apiScope := range hiddenAPISdkLibrarySupportedScopes { + // Add a dependency onto a possibly scope specific stub library. + scopeSpecificDependency := apiScope.scopeSpecificStubModule(ctx, additionalStubModule) + tag := hiddenAPIStubsDependencyTag{apiScope: apiScope, fromAdditionalDependency: true} + ctx.AddVariationDependencies(nil, tag, scopeSpecificDependency) + } + } + if SkipDexpreoptBootJars(ctx) { return } @@ -633,8 +651,8 @@ func (b *BootclasspathFragmentModule) createHiddenAPIFlagInput(ctx android.Modul // Populate with flag file paths from the properties. input.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api) - // Store the stub dex jars from this module's fragment dependencies. - input.DependencyStubDexJarsByScope = dependencyHiddenApiInfo.TransitiveStubDexJarsByScope + // Add the stub dex jars from this module's fragment dependencies. + input.DependencyStubDexJarsByScope.append(dependencyHiddenApiInfo.TransitiveStubDexJarsByScope) return input } diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go index 8a0d6349e..ba53ea6f7 100644 --- a/java/hiddenapi_modular.go +++ b/java/hiddenapi_modular.go @@ -37,13 +37,65 @@ type HiddenAPIScope struct { // The option needed to passed to "hiddenapi list". hiddenAPIListOption string + + // The name sof the source stub library modules that contain the API provided by the platform, + // i.e. by modules that are not in an APEX. + nonUpdatableSourceModule string + + // The names of the prebuilt stub library modules that contain the API provided by the platform, + // i.e. by modules that are not in an APEX. + nonUpdatablePrebuiltModule string } // initHiddenAPIScope initializes the scope. func initHiddenAPIScope(apiScope *HiddenAPIScope) *HiddenAPIScope { + sdkKind := apiScope.sdkKind + // The platform does not provide a core platform API. + if sdkKind != android.SdkCorePlatform { + kindAsString := sdkKind.String() + var insert string + if sdkKind == android.SdkPublic { + insert = "" + } else { + insert = "." + strings.ReplaceAll(kindAsString, "-", "_") + } + + nonUpdatableModule := "android-non-updatable" + + // Construct the name of the android-non-updatable source module for this scope. + apiScope.nonUpdatableSourceModule = fmt.Sprintf("%s.stubs%s", nonUpdatableModule, insert) + + prebuiltModuleName := func(name string, kind string) string { + return fmt.Sprintf("sdk_%s_current_%s", kind, name) + } + + // Construct the name of the android-non-updatable prebuilt module for this scope. + apiScope.nonUpdatablePrebuiltModule = prebuiltModuleName(nonUpdatableModule, kindAsString) + } + return apiScope } +// android-non-updatable takes the name of a module and returns a possibly scope specific name of +// the module. +func (l *HiddenAPIScope) scopeSpecificStubModule(ctx android.BaseModuleContext, name string) string { + // The android-non-updatable is not a java_sdk_library but there are separate stub libraries for + // each scope. + // TODO(b/192067200): Remove special handling of android-non-updatable. + if name == "android-non-updatable" { + if ctx.Config().AlwaysUsePrebuiltSdks() { + return l.nonUpdatablePrebuiltModule + } else { + return l.nonUpdatableSourceModule + } + } else { + // Assume that the module is either a java_sdk_library (or equivalent) and so will provide + // separate stub jars for each scope or is a java_library (or equivalent) in which case it will + // have the same stub jar for each scope. + return name + } +} + func (l *HiddenAPIScope) String() string { return fmt.Sprintf("HiddenAPIScope{%s}", l.name) } @@ -122,6 +174,11 @@ type hiddenAPIStubsDependencyTag struct { // The api scope for which this dependency was added. apiScope *HiddenAPIScope + + // Indicates that the dependency is not for an API provided by the current bootclasspath fragment + // but is an additional API provided by a module that is not part of the current bootclasspath + // fragment. + fromAdditionalDependency bool } func (b hiddenAPIStubsDependencyTag) ExcludeFromApexContents() { @@ -132,6 +189,11 @@ func (b hiddenAPIStubsDependencyTag) ReplaceSourceWithPrebuilt() bool { } func (b hiddenAPIStubsDependencyTag) SdkMemberType(child android.Module) android.SdkMemberType { + // Do not add additional dependencies to the sdk. + if b.fromAdditionalDependency { + return nil + } + // If the module is a java_sdk_library then treat it as if it was specific in the java_sdk_libs // property, otherwise treat if it was specified in the java_header_libs property. if javaSdkLibrarySdkMemberType.IsInstance(child) { @@ -243,15 +305,10 @@ func buildRuleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, name, tempPath := tempPathForRestat(ctx, outputPath) // Find the widest API stubs provided by the fragments on which this depends, if any. - var dependencyStubDexJars android.Paths - for i := len(hiddenAPIScopes) - 1; i >= 0; i-- { - apiScope := hiddenAPIScopes[i] - stubsForAPIScope := input.DependencyStubDexJarsByScope[apiScope] - if len(stubsForAPIScope) != 0 { - dependencyStubDexJars = stubsForAPIScope - break - } - } + dependencyStubDexJars := input.DependencyStubDexJarsByScope.stubDexJarsForWidestAPIScope() + + // Add widest API stubs from the additional dependencies of this, if any. + dependencyStubDexJars = append(dependencyStubDexJars, input.AdditionalStubDexJarsByScope.stubDexJarsForWidestAPIScope()...) command := rule.Command(). Tool(ctx.Config().HostToolPath(ctx, "hiddenapi")). @@ -266,6 +323,7 @@ func buildRuleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, name, // other fragment's APIs. var paths android.Paths paths = append(paths, input.DependencyStubDexJarsByScope[apiScope]...) + paths = append(paths, input.AdditionalStubDexJarsByScope[apiScope]...) paths = append(paths, input.StubDexJarsByScope[apiScope]...) if len(paths) > 0 { option := apiScope.hiddenAPIListOption @@ -514,6 +572,20 @@ func (s StubDexJarsByScope) dedupAndSort() { } } +// stubDexJarsForWidestAPIScope returns the stub dex jars for the widest API scope provided by this +// map. The relative width of APIs is determined by their order in hiddenAPIScopes. +func (s StubDexJarsByScope) stubDexJarsForWidestAPIScope() android.Paths { + for i := len(hiddenAPIScopes) - 1; i >= 0; i-- { + apiScope := hiddenAPIScopes[i] + stubsForAPIScope := s[apiScope] + if len(stubsForAPIScope) != 0 { + return stubsForAPIScope + } + } + + return nil +} + // HiddenAPIFlagInput encapsulates information obtained from a module and its dependencies that are // needed for hidden API flag generation. type HiddenAPIFlagInput struct { @@ -530,6 +602,13 @@ type HiddenAPIFlagInput struct { // fragment on which this depends. DependencyStubDexJarsByScope StubDexJarsByScope + // AdditionalStubDexJarsByScope contains stub dex jars provided by other modules in addition to + // the ones that are obtained from fragments on which this depends. + // + // These are kept separate from stub dex jars in HiddenAPIFlagInput.DependencyStubDexJarsByScope + // as there are not propagated transitively to other fragments that depend on this. + AdditionalStubDexJarsByScope StubDexJarsByScope + // RemovedTxtFiles is the list of removed.txt files provided by java_sdk_library modules that are // specified in the bootclasspath_fragment's stub_libs and contents properties. RemovedTxtFiles android.Paths @@ -541,6 +620,7 @@ func newHiddenAPIFlagInput() HiddenAPIFlagInput { FlagFilesByCategory: FlagFilesByCategory{}, StubDexJarsByScope: StubDexJarsByScope{}, DependencyStubDexJarsByScope: StubDexJarsByScope{}, + AdditionalStubDexJarsByScope: StubDexJarsByScope{}, } return input @@ -614,7 +694,14 @@ func (i *HiddenAPIFlagInput) gatherStubLibInfo(ctx android.ModuleContext, conten tag := ctx.OtherModuleDependencyTag(module) if hiddenAPIStubsTag, ok := tag.(hiddenAPIStubsDependencyTag); ok { apiScope := hiddenAPIStubsTag.apiScope - addFromModule(ctx, module, apiScope) + if hiddenAPIStubsTag.fromAdditionalDependency { + dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module, apiScope.sdkKind) + if dexJar != nil { + i.AdditionalStubDexJarsByScope[apiScope] = append(i.AdditionalStubDexJarsByScope[apiScope], dexJar) + } + } else { + addFromModule(ctx, module, apiScope) + } } }) diff --git a/sdk/update.go b/sdk/update.go index 13c24844c..84f0e4e2f 100644 --- a/sdk/update.go +++ b/sdk/update.go @@ -148,6 +148,11 @@ func (s *sdk) collectMembers(ctx android.ModuleContext) { if memberTag, ok := tag.(android.SdkMemberTypeDependencyTag); ok { memberType := memberTag.SdkMemberType(child) + // If a nil SdkMemberType was returned then this module should not be added to the sdk. + if memberType == nil { + return false + } + // Make sure that the resolved module is allowed in the member list property. if !memberType.IsInstance(child) { ctx.ModuleErrorf("module %q is not valid in property %s", ctx.OtherModuleName(child), memberType.SdkPropertyName())