Add support for excluding libraries from class loader contexts

A number of tests in the cts/tests/signature/api-check check for the
accessibility of classes from the android.test.base,
android.test.runner and android.test.mock libraries. Some tests expect
to find the classes other do not. Unfortunately, the tests use
libraries, specifically compatibility-device-util-axt, that depend on
the android.test... libraries which causes Soong to implicitly add
<uses-library> entries to the manifest so that they will be accessible
at runtime. That causes the tests that do not expect to find the
classes to fail.

Bug: 209607558
Test: m nothing
Change-Id: I54c194ab23d5a70df790ece3fe98f2b3d6a1c1f6
This commit is contained in:
Paul Duffin
2022-02-03 17:54:15 +00:00
parent 47cb940902
commit 0653057603
5 changed files with 220 additions and 4 deletions

View File

@@ -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.

View File

@@ -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)