diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go index 658e8e2dc..36513b64b 100644 --- a/dexpreopt/class_loader_context.go +++ b/dexpreopt/class_loader_context.go @@ -210,6 +210,34 @@ type ClassLoaderContext struct { Subcontexts []*ClassLoaderContext } +// excludeLibs excludes the libraries from this ClassLoaderContext. +// +// This treats the supplied context as being immutable (as it may come from a dependency). So, it +// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used +// within this context then this will return a deep copy of this without those libraries. +// +// If this ClassLoaderContext matches one of the libraries to exclude then this returns (nil, true) +// to indicate that this context should be excluded from the containing list. +// +// If any of this ClassLoaderContext's Subcontexts reference the excluded libraries then this +// returns a pointer to a copy of this without the excluded libraries and true to indicate that this +// was copied. +// +// Otherwise, this returns a pointer to this and false to indicate that this was not copied. +func (c *ClassLoaderContext) excludeLibs(excludedLibs []string) (*ClassLoaderContext, bool) { + if android.InList(c.Name, excludedLibs) { + return nil, true + } + + if excludedList, modified := excludeLibsFromCLCList(c.Subcontexts, excludedLibs); modified { + clcCopy := *c + clcCopy.Subcontexts = excludedList + return &clcCopy, true + } + + return c, false +} + // ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key // AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version. // @@ -408,6 +436,67 @@ func (clcMap ClassLoaderContextMap) Dump() string { return string(bytes) } +// excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list. +// +// This treats the supplied list as being immutable (as it may come from a dependency). So, it +// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used +// within the contexts in the list then this will return a deep copy of the list without those +// libraries. +// +// If any of the ClassLoaderContext in the list reference the excluded libraries then this returns a +// copy of this list without the excluded libraries and true to indicate that this was copied. +// +// Otherwise, this returns the list and false to indicate that this was not copied. +func excludeLibsFromCLCList(clcList []*ClassLoaderContext, excludedLibs []string) ([]*ClassLoaderContext, bool) { + modifiedList := false + copiedList := make([]*ClassLoaderContext, 0, len(clcList)) + for _, clc := range clcList { + resultClc, modifiedClc := clc.excludeLibs(excludedLibs) + if resultClc != nil { + copiedList = append(copiedList, resultClc) + } + modifiedList = modifiedList || modifiedClc + } + + if modifiedList { + return copiedList, true + } else { + return clcList, false + } +} + +// ExcludeLibs excludes the libraries from the ClassLoaderContextMap. +// +// If the list o libraries is empty then this returns the ClassLoaderContextMap. +// +// This treats the ClassLoaderContextMap as being immutable (as it may come from a dependency). So, +// it implements copy-on-exclusion logic. That means that if any of the excluded libraries are used +// within the contexts in the map then this will return a deep copy of the map without those +// libraries. +// +// Otherwise, this returns the map unchanged. +func (clcMap ClassLoaderContextMap) ExcludeLibs(excludedLibs []string) ClassLoaderContextMap { + if len(excludedLibs) == 0 { + return clcMap + } + + excludedClcMap := make(ClassLoaderContextMap) + modifiedMap := false + for sdkVersion, clcList := range clcMap { + excludedList, modifiedList := excludeLibsFromCLCList(clcList, excludedLibs) + if len(excludedList) != 0 { + excludedClcMap[sdkVersion] = excludedList + } + modifiedMap = modifiedMap || modifiedList + } + + if modifiedMap { + return excludedClcMap + } else { + return clcMap + } +} + // Now that the full unconditional context is known, reconstruct conditional context. // Apply filters for individual libraries, mirroring what the PackageManager does when it // constructs class loader context on device. diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go index 4a3d390c2..5d3a9d943 100644 --- a/dexpreopt/class_loader_context_test.go +++ b/dexpreopt/class_loader_context_test.go @@ -284,6 +284,111 @@ func TestCLCSdkVersionOrder(t *testing.T) { }) } +func TestCLCMExcludeLibs(t *testing.T) { + ctx := testContext() + const optional = false + const implicit = true + + excludeLibs := func(t *testing.T, m ClassLoaderContextMap, excluded_libs ...string) ClassLoaderContextMap { + // Dump the CLCM before creating a new copy that excludes a specific set of libraries. + before := m.Dump() + + // Create a new CLCM that excludes some libraries. + c := m.ExcludeLibs(excluded_libs) + + // Make sure that the original CLCM was not changed. + after := m.Dump() + android.AssertStringEquals(t, "input CLCM modified", before, after) + + return c + } + + t.Run("exclude nothing", func(t *testing.T) { + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil) + + a := excludeLibs(t, m) + + android.AssertStringEquals(t, "output CLCM ", `{ + "28": [ + { + "Name": "a", + "Optional": false, + "Implicit": true, + "Host": "out/soong/a.jar", + "Device": "/system/a.jar", + "Subcontexts": [] + } + ] +}`, a.Dump()) + }) + + t.Run("one item from list", func(t *testing.T) { + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil) + m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil) + + a := excludeLibs(t, m, "a") + + expected := `{ + "28": [ + { + "Name": "b", + "Optional": false, + "Implicit": true, + "Host": "out/soong/b.jar", + "Device": "/system/b.jar", + "Subcontexts": [] + } + ] +}` + android.AssertStringEquals(t, "output CLCM ", expected, a.Dump()) + }) + + t.Run("all items from a list", func(t *testing.T) { + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil) + m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil) + + a := excludeLibs(t, m, "a", "b") + + android.AssertStringEquals(t, "output CLCM ", `{}`, a.Dump()) + }) + + t.Run("items from a subcontext", func(t *testing.T) { + s := make(ClassLoaderContextMap) + s.AddContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil) + s.AddContext(ctx, AnySdkVersion, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil) + + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), s) + + a := excludeLibs(t, m, "b") + + android.AssertStringEquals(t, "output CLCM ", `{ + "28": [ + { + "Name": "a", + "Optional": false, + "Implicit": true, + "Host": "out/soong/a.jar", + "Device": "/system/a.jar", + "Subcontexts": [ + { + "Name": "c", + "Optional": false, + "Implicit": true, + "Host": "out/soong/c.jar", + "Device": "/system/c.jar", + "Subcontexts": [] + } + ] + } + ] +}`, a.Dump()) + }) +} + func checkError(t *testing.T, have error, want string) { if have == nil { t.Errorf("\nwant error: '%s'\nhave: none", want) diff --git a/java/aar.go b/java/aar.go index 4687424c2..51aad8da0 100644 --- a/java/aar.go +++ b/java/aar.go @@ -267,11 +267,15 @@ var extractAssetsRule = pctx.AndroidStaticRule("extractAssets", }) func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkContext, - classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) { + classLoaderContexts dexpreopt.ClassLoaderContextMap, excludedLibs []string, + extraLinkFlags ...string) { transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags := aaptLibs(ctx, sdkContext, classLoaderContexts) + // Exclude any libraries from the supplied list. + classLoaderContexts = classLoaderContexts.ExcludeLibs(excludedLibs) + // App manifest file manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml") manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile) @@ -530,7 +534,7 @@ func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.aapt.isLibrary = true a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx) - a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts) + a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, nil) a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform() diff --git a/java/app.go b/java/app.go index 9f2f99a16..e4432ff4b 100755 --- a/java/app.go +++ b/java/app.go @@ -425,7 +425,8 @@ func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) { a.aapt.splitNames = a.appProperties.Package_splits a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent) - a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, aaptLinkFlags...) + a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, + a.usesLibraryProperties.Exclude_uses_libs, aaptLinkFlags...) // apps manifests are handled by aapt, don't let Module see them a.properties.Manifest = nil @@ -1211,6 +1212,23 @@ type UsesLibraryProperties struct { // libraries, because SDK ones are automatically picked up by Soong. The name // normally is the same as the module name, but there are exceptions. Provides_uses_lib *string + + // A list of shared library names to exclude from the classpath of the APK. Adding a library here + // will prevent it from being used when precompiling the APK and prevent it from being implicitly + // added to the APK's manifest's elements. + // + // Care must be taken when using this as it could result in runtime errors if the APK actually + // uses classes provided by the library and which are not provided in any other way. + // + // This is primarily intended for use by various CTS tests that check the runtime handling of the + // android.test.base shared library (and related libraries) but which depend on some common + // libraries that depend on the android.test.base library. Without this those tests will end up + // with a in their manifest which would either + // render the tests worthless (as they would be testing the wrong behavior), or would break the + // test altogether by providing access to classes that the tests were not expecting. Those tests + // provide the android.test.base statically and use jarjar to rename them so they do not collide + // with the classes provided by the android.test.base library. + Exclude_uses_libs []string } // usesLibrary provides properties and helper functions for AndroidApp and AndroidAppImport to verify that the diff --git a/java/rro.go b/java/rro.go index 0b4d0916a..be84afffc 100644 --- a/java/rro.go +++ b/java/rro.go @@ -139,7 +139,7 @@ func (r *RuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleC aaptLinkFlags = append(aaptLinkFlags, "--rename-overlay-target-package "+*r.overridableProperties.Target_package_name) } - r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...) + r.aapt.buildActions(ctx, r, nil, nil, aaptLinkFlags...) // Sign the built package _, certificates := collectAppDeps(ctx, r, false, false)