Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b614cd44 authored by Colin Cross's avatar Colin Cross
Browse files

Verify that libraries in apexes don't link to implementations outside the apex

As part of removing some of the complexity in Soong around using
stub vs. implementations for shared library dependencies a syntax
will be added to Soong to allow explicitly selecting stubs vs.
implementation.  To avoid incorrect use, add a verification pass
on apexes that ensure that all transitive implementation libraries
used to link native libraries or binaries in the apex are
themselves in the apex.

Bug: 372543712
Test: TestApexVerifyNativeImplementationLibs
Flag: EXEMPT host only
Change-Id: I4aeaca00a359ce97e8f9efd2d8bffb8f9d2dc0df
parent 7ceb14aa
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package android
import (
	"fmt"
	"regexp"
	"slices"
	"strings"

	"github.com/google/blueprint"
@@ -562,7 +563,7 @@ func (b *baseModuleContext) WalkDepsProxy(visit func(ModuleProxy, ModuleProxy) b
}

func (b *baseModuleContext) GetWalkPath() []Module {
	return b.walkPath
	return slices.Clone(b.walkPath)
}

func (b *baseModuleContext) GetTagPath() []blueprint.DependencyTag {
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ bootstrap_go_package {
    pkgPath: "android/soong/apex",
    deps: [
        "blueprint",
        "blueprint-bpmodify",
        "soong",
        "soong-aconfig",
        "soong-aconfig-codegen",
+126 −0
Original line number Diff line number Diff line
@@ -20,10 +20,12 @@ import (
	"fmt"
	"path/filepath"
	"regexp"
	"slices"
	"sort"
	"strings"

	"github.com/google/blueprint"
	"github.com/google/blueprint/depset"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
@@ -464,6 +466,12 @@ type apexBundle struct {
	// GenerateAndroidBuildActions.
	filesInfo []apexFile

	// List of files that were excluded by the unwanted_transitive_deps property.
	unwantedTransitiveFilesInfo []apexFile

	// List of files that were excluded due to conflicts with other variants of the same module.
	duplicateTransitiveFilesInfo []apexFile

	// List of other module names that should be installed when this APEX gets installed (LOCAL_REQUIRED_MODULES).
	makeModulesToInstall []string

@@ -1877,6 +1885,14 @@ type visitorContext struct {

	// visitor skips these from this list of module names
	unwantedTransitiveDeps []string

	// unwantedTransitiveFilesInfo contains files that would have been in the apex
	// except that they were listed in unwantedTransitiveDeps.
	unwantedTransitiveFilesInfo []apexFile

	// duplicateTransitiveFilesInfo contains files that would ahve been in the apex
	// except that another variant of the same module was already in the apex.
	duplicateTransitiveFilesInfo []apexFile
}

func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
@@ -1887,6 +1903,7 @@ func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
		// Needs additional verification for the resulting APEX to ensure that skipped artifacts don't make problems.
		// For example, DT_NEEDED modules should be found within the APEX unless they are marked in `requiredNativeLibs`.
		if f.transitiveDep && f.module != nil && android.InList(mctx.OtherModuleName(f.module), vctx.unwantedTransitiveDeps) {
			vctx.unwantedTransitiveFilesInfo = append(vctx.unwantedTransitiveFilesInfo, f)
			continue
		}
		dest := filepath.Join(f.installDir, f.builtFile.Base())
@@ -1897,6 +1914,8 @@ func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
				mctx.ModuleErrorf("apex file %v is provided by two different files %v and %v",
					dest, e.builtFile, f.builtFile)
				return
			} else {
				vctx.duplicateTransitiveFilesInfo = append(vctx.duplicateTransitiveFilesInfo, f)
			}
			// If a module is directly included and also transitively depended on
			// consider it as directly included.
@@ -1911,6 +1930,7 @@ func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
	for _, v := range encountered {
		vctx.filesInfo = append(vctx.filesInfo, v)
	}

	sort.Slice(vctx.filesInfo, func(i, j int) bool {
		// Sort by destination path so as to ensure consistent ordering even if the source of the files
		// changes.
@@ -2341,6 +2361,8 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	// 3) some fields in apexBundle struct are configured
	a.installDir = android.PathForModuleInstall(ctx, "apex")
	a.filesInfo = vctx.filesInfo
	a.unwantedTransitiveFilesInfo = vctx.unwantedTransitiveFilesInfo
	a.duplicateTransitiveFilesInfo = vctx.duplicateTransitiveFilesInfo

	a.setPayloadFsType(ctx)
	a.setSystemLibLink(ctx)
@@ -2367,6 +2389,8 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {

	a.setOutputFiles(ctx)
	a.enforcePartitionTagOnApexSystemServerJar(ctx)

	a.verifyNativeImplementationLibs(ctx)
}

// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2920,3 +2944,105 @@ func rBcpPackages() map[string][]string {
func (a *apexBundle) IsTestApex() bool {
	return a.testApex
}

// verifyNativeImplementationLibs compares the list of transitive implementation libraries used to link native
// libraries in the apex against the list of implementation libraries in the apex, ensuring that none of the
// libraries in the apex have references to private APIs from outside the apex.
func (a *apexBundle) verifyNativeImplementationLibs(ctx android.ModuleContext) {
	var directImplementationLibs android.Paths
	var transitiveImplementationLibs []depset.DepSet[android.Path]

	if a.properties.IsCoverageVariant {
		return
	}

	if a.testApex {
		return
	}

	if a.UsePlatformApis() {
		return
	}

	checkApexTag := func(tag blueprint.DependencyTag) bool {
		switch tag {
		case sharedLibTag, jniLibTag, executableTag, androidAppTag:
			return true
		default:
			return false
		}
	}

	checkTransitiveTag := func(tag blueprint.DependencyTag) bool {
		switch {
		case cc.IsSharedDepTag(tag), java.IsJniDepTag(tag), rust.IsRlibDepTag(tag), rust.IsDylibDepTag(tag), checkApexTag(tag):
			return true
		default:
			return false
		}
	}

	var appEmbeddedJNILibs android.Paths
	ctx.VisitDirectDeps(func(dep android.Module) {
		tag := ctx.OtherModuleDependencyTag(dep)
		if !checkApexTag(tag) {
			return
		}
		if tag == sharedLibTag || tag == jniLibTag {
			outputFile := android.OutputFileForModule(ctx, dep, "")
			directImplementationLibs = append(directImplementationLibs, outputFile)
		}
		if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
			transitiveImplementationLibs = append(transitiveImplementationLibs, info.ImplementationDeps)
		}
		if info, ok := android.OtherModuleProvider(ctx, dep, java.AppInfoProvider); ok {
			appEmbeddedJNILibs = append(appEmbeddedJNILibs, info.EmbeddedJNILibs...)
		}
	})

	depSet := depset.New(depset.PREORDER, directImplementationLibs, transitiveImplementationLibs)
	allImplementationLibs := depSet.ToList()

	allFileInfos := slices.Concat(a.filesInfo, a.unwantedTransitiveFilesInfo, a.duplicateTransitiveFilesInfo)

	for _, lib := range allImplementationLibs {
		inApex := slices.ContainsFunc(allFileInfos, func(fi apexFile) bool {
			return fi.builtFile == lib
		})
		inApkInApex := slices.Contains(appEmbeddedJNILibs, lib)

		if !inApex && !inApkInApex {
			ctx.ModuleErrorf("library in apex transitively linked against implementation library %q not in apex", lib)
			var depPath []android.Module
			ctx.WalkDeps(func(child, parent android.Module) bool {
				if depPath != nil {
					return false
				}

				tag := ctx.OtherModuleDependencyTag(child)

				if parent == ctx.Module() {
					if !checkApexTag(tag) {
						return false
					}
				}

				if checkTransitiveTag(tag) {
					if android.OutputFileForModule(ctx, child, "") == lib {
						depPath = ctx.GetWalkPath()
					}
					return true
				}

				return false
			})
			if depPath != nil {
				ctx.ModuleErrorf("dependency path:")
				for _, m := range depPath {
					ctx.ModuleErrorf("   %s", ctx.OtherModuleName(m))
				}
				return
			}
		}
	}
}
+401 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import (
	"path/filepath"
	"reflect"
	"regexp"
	"slices"
	"sort"
	"strconv"
	"strings"
@@ -28,6 +29,7 @@ import (
	"android/soong/aconfig/codegen"

	"github.com/google/blueprint"
	"github.com/google/blueprint/bpmodify"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
@@ -225,6 +227,10 @@ var prepareForTestWithMyapex = android.FixtureMergeMockFs(android.MockFS{
	"system/sepolicy/apex/myapex-file_contexts": nil,
})

var prepareForTestWithOtherapex = android.FixtureMergeMockFs(android.MockFS{
	"system/sepolicy/apex/otherapex-file_contexts": nil,
})

// ensure that 'result' equals 'expected'
func ensureEquals(t *testing.T, result string, expected string) {
	t.Helper()
@@ -12114,3 +12120,398 @@ func TestFilesystemWithApexDeps(t *testing.T) {
	fileList := android.ContentFromFileRuleForTests(t, result, partition.Output("fileList"))
	android.AssertDeepEquals(t, "filesystem with apex", "apex/myapex.apex\n", fileList)
}

func TestApexVerifyNativeImplementationLibs(t *testing.T) {
	t.Parallel()

	extractDepenencyPathFromErrors := func(errs []error) []string {
		i := slices.IndexFunc(errs, func(err error) bool {
			return strings.Contains(err.Error(), "dependency path:")
		})
		if i < 0 {
			return nil
		}
		var dependencyPath []string
		for _, err := range errs[i+1:] {
			s := err.Error()
			lastSpace := strings.LastIndexByte(s, ' ')
			if lastSpace >= 0 {
				dependencyPath = append(dependencyPath, s[lastSpace+1:])
			}
		}
		return dependencyPath
	}

	checkErrors := func(wantDependencyPath []string) func(t *testing.T, result *android.TestResult) {
		return func(t *testing.T, result *android.TestResult) {
			t.Helper()
			if len(result.Errs) == 0 {
				t.Fatalf("expected errors")
			}
			t.Log("found errors:")
			for _, err := range result.Errs {
				t.Log(err)
			}
			if g, w := result.Errs[0].Error(), "library in apex transitively linked against implementation library"; !strings.Contains(g, w) {
				t.Fatalf("expected error %q, got %q", w, g)
			}
			dependencyPath := extractDepenencyPathFromErrors(result.Errs)
			if g, w := dependencyPath, wantDependencyPath; !slices.Equal(g, w) {
				t.Errorf("expected dependency path %q, got %q", w, g)
			}
		}
	}

	addToSharedLibs := func(module, lib string) func(bp *bpmodify.Blueprint) {
		return func(bp *bpmodify.Blueprint) {
			m := bp.ModulesByName(module)
			props, err := m.GetOrCreateProperty(bpmodify.List, "shared_libs")
			if err != nil {
				panic(err)
			}
			props.AddStringToList(lib)
		}
	}

	bpTemplate := `
	apex {
		name: "myapex",
		key: "myapex.key",
		native_shared_libs: ["mylib"],
		rust_dyn_libs: ["libmyrust"],
		binaries: ["mybin", "myrustbin"],
		jni_libs: ["libjni"],
		apps: ["myapp"],
		updatable: false,
	}

	apex {
		name: "otherapex",
		key: "myapex.key",
		native_shared_libs: ["libotherapex"],
		updatable: false,
	}

	apex_key {
		name: "myapex.key",
		public_key: "testkey.avbpubkey",
		private_key: "testkey.pem",
	}

	cc_library {
		name: "mylib",
		srcs: ["foo.cpp"],
		apex_available: ["myapex"],
	}

	cc_binary {
		name: "mybin",
		srcs: ["foo.cpp"],
		apex_available: ["myapex"],
	}

	rust_library {
		name: "libmyrust",
		crate_name: "myrust",
		srcs: ["src/lib.rs"],
		rustlibs: ["libmyrust_transitive_dylib"],
		rlibs: ["libmyrust_transitive_rlib"],
		apex_available: ["myapex"],
	}

	rust_library{
		name: "libmyrust_transitive_dylib",
		crate_name: "myrust_transitive_dylib",
		srcs: ["src/lib.rs"],
		apex_available: ["myapex"],
	}

	rust_library {
		name: "libmyrust_transitive_rlib",
		crate_name: "myrust_transitive_rlib",
		srcs: ["src/lib.rs"],
		apex_available: ["myapex"],
	}

	rust_binary {
		name: "myrustbin",
		srcs: ["src/main.rs"],
		apex_available: ["myapex"],
	}

	cc_library {
		name: "libbar",
		sdk_version: "current",
		srcs: ["bar.cpp"],
		apex_available: ["myapex"],
		stl: "none",
	}

	android_app {
		name: "myapp",
		jni_libs: ["libembeddedjni"],
		use_embedded_native_libs: true,
		sdk_version: "current",
		apex_available: ["myapex"],
	}

	cc_library {
		name: "libembeddedjni",
		sdk_version: "current",
		srcs: ["bar.cpp"],
		apex_available: ["myapex"],
		stl: "none",
	}

	cc_library {
		name: "libjni",
		sdk_version: "current",
		srcs: ["bar.cpp"],
		apex_available: ["myapex"],
		stl: "none",
	}

	cc_library {
		name: "libotherapex",
		sdk_version: "current",
		srcs: ["otherapex.cpp"],
		apex_available: ["otherapex"],
		stubs: {
			symbol_file: "libotherapex.map.txt",
			versions: ["1", "2", "3"],
		},
		stl: "none",
	}

	cc_library {
		name: "libplatform",
		sdk_version: "current",
		srcs: ["libplatform.cpp"],
		stubs: {
			symbol_file: "libplatform.map.txt",
			versions: ["1", "2", "3"],
		},
		stl: "none",
		system_shared_libs: [],
	}
	`

	testCases := []struct {
		name           string
		bpModifier     func(bp *bpmodify.Blueprint)
		dependencyPath []string
	}{
		{
			name:           "library dependency in other apex",
			bpModifier:     addToSharedLibs("mylib", "libotherapex#impl"),
			dependencyPath: []string{"myapex", "mylib", "libotherapex"},
		},
		{
			name: "transitive library dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("mylib", "libbar")(bp)
				addToSharedLibs("libbar", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "mylib", "libbar", "libotherapex"},
		},
		{
			name:           "library dependency in platform",
			bpModifier:     addToSharedLibs("mylib", "libplatform#impl"),
			dependencyPath: []string{"myapex", "mylib", "libplatform"},
		},
		{
			name:           "jni library dependency in other apex",
			bpModifier:     addToSharedLibs("libjni", "libotherapex#impl"),
			dependencyPath: []string{"myapex", "libjni", "libotherapex"},
		},
		{
			name: "transitive jni library dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libjni", "libbar")(bp)
				addToSharedLibs("libbar", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libjni", "libbar", "libotherapex"},
		},
		{
			name:           "jni library dependency in platform",
			bpModifier:     addToSharedLibs("libjni", "libplatform#impl"),
			dependencyPath: []string{"myapex", "libjni", "libplatform"},
		},
		{
			name: "transitive jni library dependency in platform",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libjni", "libbar")(bp)
				addToSharedLibs("libbar", "libplatform#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libjni", "libbar", "libplatform"},
		},
		// TODO: embedded JNI in apps should be checked too, but Soong currently just packages the transitive
		//  JNI libraries even if they came from another apex.
		//{
		//	name:           "app jni library dependency in other apex",
		//	bpModifier:     addToSharedLibs("libembeddedjni", "libotherapex#impl"),
		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libotherapex"},
		//},
		//{
		//	name: "transitive app jni library dependency in other apex",
		//	bpModifier: func(bp *bpmodify.Blueprint) {
		//		addToSharedLibs("libembeddedjni", "libbar")(bp)
		//		addToSharedLibs("libbar", "libotherapex#impl")(bp)
		//	},
		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libbar", "libotherapex"},
		//},
		//{
		//	name:           "app jni library dependency in platform",
		//	bpModifier:     addToSharedLibs("libembeddedjni", "libplatform#impl"),
		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libplatform"},
		//},
		//{
		//	name: "transitive app jni library dependency in platform",
		//	bpModifier: func(bp *bpmodify.Blueprint) {
		//		addToSharedLibs("libembeddedjni", "libbar")(bp)
		//		addToSharedLibs("libbar", "libplatform#impl")(bp)
		//	},
		//	dependencyPath: []string{"myapex", "myapp", "libembeddedjni", "libbar", "libplatform"},
		//},
		{
			name:           "binary dependency in other apex",
			bpModifier:     addToSharedLibs("mybin", "libotherapex#impl"),
			dependencyPath: []string{"myapex", "mybin", "libotherapex"},
		},
		{
			name: "transitive binary dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("mybin", "libbar")(bp)
				addToSharedLibs("libbar", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "mybin", "libbar", "libotherapex"},
		},
		{
			name:           "binary dependency in platform",
			bpModifier:     addToSharedLibs("mybin", "libplatform#impl"),
			dependencyPath: []string{"myapex", "mybin", "libplatform"},
		},
		{
			name: "transitive binary dependency in platform",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("mybin", "libbar")(bp)
				addToSharedLibs("libbar", "libplatform#impl")(bp)
			},
			dependencyPath: []string{"myapex", "mybin", "libbar", "libplatform"},
		},

		{
			name:           "rust library dependency in other apex",
			bpModifier:     addToSharedLibs("libmyrust", "libotherapex#impl"),
			dependencyPath: []string{"myapex", "libmyrust", "libotherapex"},
		},
		{
			name: "transitive rust library dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libmyrust", "libbar")(bp)
				addToSharedLibs("libbar", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libmyrust", "libbar", "libotherapex"},
		},
		{
			name:           "rust library dependency in platform",
			bpModifier:     addToSharedLibs("libmyrust", "libplatform#impl"),
			dependencyPath: []string{"myapex", "libmyrust", "libplatform"},
		},
		{
			name: "transitive rust library dependency in platform",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libmyrust", "libbar")(bp)
				addToSharedLibs("libbar", "libplatform#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libmyrust", "libbar", "libplatform"},
		},
		{
			name: "transitive rust library dylib dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libmyrust_transitive_dylib", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_dylib", "libotherapex"},
		},
		{
			name: "transitive rust library dylib dependency in platform",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libmyrust_transitive_dylib", "libplatform#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_dylib", "libplatform"},
		},
		{
			name: "transitive rust library rlib dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libmyrust_transitive_rlib", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_rlib", "libotherapex"},
		},
		{
			name: "transitive rust library rlib dependency in platform",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("libmyrust_transitive_rlib", "libplatform#impl")(bp)
			},
			dependencyPath: []string{"myapex", "libmyrust", "libmyrust_transitive_rlib", "libplatform"},
		},
		{
			name:           "rust binary dependency in other apex",
			bpModifier:     addToSharedLibs("myrustbin", "libotherapex#impl"),
			dependencyPath: []string{"myapex", "myrustbin", "libotherapex"},
		},
		{
			name: "transitive rust binary dependency in other apex",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("myrustbin", "libbar")(bp)
				addToSharedLibs("libbar", "libotherapex#impl")(bp)
			},
			dependencyPath: []string{"myapex", "myrustbin", "libbar", "libotherapex"},
		},
		{
			name:           "rust binary dependency in platform",
			bpModifier:     addToSharedLibs("myrustbin", "libplatform#impl"),
			dependencyPath: []string{"myapex", "myrustbin", "libplatform"},
		},
		{
			name: "transitive rust binary dependency in platform",
			bpModifier: func(bp *bpmodify.Blueprint) {
				addToSharedLibs("myrustbin", "libbar")(bp)
				addToSharedLibs("libbar", "libplatform#impl")(bp)
			},
			dependencyPath: []string{"myapex", "myrustbin", "libbar", "libplatform"},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			bp, err := bpmodify.NewBlueprint("", []byte(bpTemplate))
			if err != nil {
				t.Fatal(err)
			}
			if testCase.bpModifier != nil {
				func() {
					defer func() {
						if r := recover(); r != nil {
							t.Fatalf("panic in bpModifier: %v", r)
						}
					}()
					testCase.bpModifier(bp)
				}()
			}
			android.GroupFixturePreparers(
				android.PrepareForTestWithAndroidBuildComponents,
				cc.PrepareForTestWithCcBuildComponents,
				java.PrepareForTestWithDexpreopt,
				rust.PrepareForTestWithRustDefaultModules,
				PrepareForTestWithApexBuildComponents,
				prepareForTestWithMyapex,
				prepareForTestWithOtherapex,
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
				}),
			).ExtendWithErrorHandler(android.FixtureCustomErrorHandler(checkErrors(testCase.dependencyPath))).
				RunTestWithBp(t, bp.String())
		})
	}
}
+1 −4
Original line number Diff line number Diff line
@@ -45,10 +45,7 @@ func TestCreateClasspathElements(t *testing.T) {
		prepareForTestWithPlatformBootclasspath,
		prepareForTestWithArtApex,
		prepareForTestWithMyapex,
		// For otherapex.
		android.FixtureMergeMockFs(android.MockFS{
			"system/sepolicy/apex/otherapex-file_contexts": nil,
		}),
		prepareForTestWithOtherapex,
		java.PrepareForTestWithJavaSdkLibraryFiles,
		java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
		java.FixtureConfigureApexBootJars("myapex:bar"),
Loading