From a4f08813a34418a07aa0ebd8b3e704f3a82081ef Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Tue, 2 Oct 2018 22:03:40 -0700 Subject: [PATCH] Add support for JNI libraries to android_app modules Make android_app modules a MultiTargets module, which means the common variant will have a list of Targets that it needs to handle. Collect JNI libraries for each Target, and package them into or alongside the APK. Bug: 80095087 Test: app_test.go Change-Id: Iabd3921e1d4c4b4cfcc7e131a0b0d9ab83b0ebbb --- Android.bp | 1 + android/config.go | 4 +- androidmk/cmd/androidmk/android.go | 1 + java/androidmk.go | 4 + java/app.go | 72 +++++++++++++++++- java/app_builder.go | 46 +++++++++++- java/app_test.go | 116 +++++++++++++++++++++++++++++ java/builder.go | 9 +++ java/java.go | 20 ++++- java/java_test.go | 17 ++++- 10 files changed, 276 insertions(+), 14 deletions(-) diff --git a/Android.bp b/Android.bp index aeabb1306..eefa149e9 100644 --- a/Android.bp +++ b/Android.bp @@ -219,6 +219,7 @@ bootstrap_go_package { "blueprint-pathtools", "soong", "soong-android", + "soong-cc", "soong-genrule", "soong-java-config", "soong-tradefed", diff --git a/android/config.go b/android/config.go index 4b10552ab..5da1e0bda 100644 --- a/android/config.go +++ b/android/config.go @@ -232,8 +232,8 @@ func TestArchConfig(buildDir string, env map[string]string) Config { config.Targets = map[OsClass][]Target{ Device: []Target{ - {Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Native: true}}, - {Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Native: true}}, + {Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Native: true, Abi: []string{"arm64-v8a"}}}, + {Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Native: true, Abi: []string{"armeabi-v7a"}}}, }, Host: []Target{ {BuildOs, Arch{ArchType: X86_64}}, diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go index 1d509805c..0cb6dd68c 100644 --- a/androidmk/cmd/androidmk/android.go +++ b/androidmk/cmd/androidmk/android.go @@ -140,6 +140,7 @@ func init() { "LOCAL_DX_FLAGS": "dxflags", "LOCAL_JAVA_LIBRARIES": "libs", "LOCAL_STATIC_JAVA_LIBRARIES": "static_libs", + "LOCAL_JNI_SHARED_LIBRARIES": "jni_libs", "LOCAL_AAPT_FLAGS": "aaptflags", "LOCAL_PACKAGE_SPLITS": "package_splits", "LOCAL_COMPATIBILITY_SUITE": "test_suites", diff --git a/java/androidmk.go b/java/androidmk.go index 313a144e9..7a2fe1e11 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -243,6 +243,10 @@ func (app *AndroidApp) AndroidMk() android.AndroidMkData { if len(app.appProperties.Overrides) > 0 { fmt.Fprintln(w, "LOCAL_OVERRIDES_PACKAGES := "+strings.Join(app.appProperties.Overrides, " ")) } + + for _, jniLib := range app.installJniLibs { + fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), "+=", jniLib.name) + } }, }, } diff --git a/java/app.go b/java/app.go index dc5296d17..843ced6af 100644 --- a/java/app.go +++ b/java/app.go @@ -19,9 +19,11 @@ package java import ( "strings" + "github.com/google/blueprint" "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/cc" "android/soong/tradefed" ) @@ -59,6 +61,11 @@ type appProperties struct { // binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed // from PRODUCT_PACKAGES. Overrides []string + + // list of native libraries that will be provided in or alongside the resulting jar + Jni_libs []string `android:"arch_variant"` + + EmbedJNI bool `blueprint:"mutated"` } type AndroidApp struct { @@ -70,6 +77,8 @@ type AndroidApp struct { appProperties appProperties extraLinkFlags []string + + installJniLibs []jniLib } func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths { @@ -92,9 +101,21 @@ type certificate struct { func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) { a.Module.deps(ctx) + if !Bool(a.properties.No_framework_libs) && !Bool(a.properties.No_standard_libs) { a.aapt.deps(ctx, sdkContext(a)) } + + for _, jniTarget := range ctx.MultiTargets() { + variation := []blueprint.Variation{ + {Mutator: "arch", Variation: jniTarget.String()}, + {Mutator: "link", Variation: "shared"}, + } + tag := &jniDependencyTag{ + target: jniTarget, + } + ctx.AddFarVariationDependencies(variation, tag, a.appProperties.Jni_libs...) + } } func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) { @@ -178,7 +199,19 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { packageFile := android.PathForModuleOut(ctx, "package.apk") - CreateAppPackage(ctx, packageFile, a.exportPackage, a.outputFile, certificates) + var jniJarFile android.WritablePath + jniLibs := a.collectJniDeps(ctx) + if len(jniLibs) > 0 { + embedJni := ctx.Config().UnbundledBuild() || a.appProperties.EmbedJNI + if embedJni { + jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip") + TransformJniLibsToJar(ctx, jniJarFile, jniLibs) + } else { + a.installJniLibs = jniLibs + } + } + + CreateAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, a.outputFile, certificates) a.outputFile = packageFile @@ -192,6 +225,35 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { } } +func (a *AndroidApp) collectJniDeps(ctx android.ModuleContext) []jniLib { + var jniLibs []jniLib + + ctx.VisitDirectDeps(func(module android.Module) { + otherName := ctx.OtherModuleName(module) + tag := ctx.OtherModuleDependencyTag(module) + + if jniTag, ok := tag.(*jniDependencyTag); ok { + if dep, ok := module.(*cc.Module); ok { + lib := dep.OutputFile() + if lib.Valid() { + jniLibs = append(jniLibs, jniLib{ + name: ctx.OtherModuleName(module), + path: lib.Path(), + target: jniTag.target, + }) + } else { + ctx.ModuleErrorf("dependency %q missing output file", otherName) + } + } else { + ctx.ModuleErrorf("jni_libs dependency %q must be a cc library", otherName) + + } + } + }) + + return jniLibs +} + func AndroidAppFactory() android.Module { module := &AndroidApp{} @@ -212,7 +274,9 @@ func AndroidAppFactory() android.Module { return class == android.Device && ctx.Config().DevicePrefer32BitApps() }) - InitJavaModule(module, android.DeviceSupported) + android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + return module } @@ -258,6 +322,7 @@ func AndroidTestFactory() android.Module { module.Module.properties.Instrument = true module.Module.properties.Installable = proptools.BoolPtr(true) + module.appProperties.EmbedJNI = true module.AddProperties( &module.Module.properties, @@ -268,6 +333,7 @@ func AndroidTestFactory() android.Module { &module.appTestProperties, &module.testProperties) - InitJavaModule(module, android.DeviceSupported) + android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) return module } diff --git a/java/app_builder.go b/java/app_builder.go index e27b1b70e..b9b5f43a7 100644 --- a/java/app_builder.go +++ b/java/app_builder.go @@ -19,9 +19,11 @@ package java // functions. import ( + "path/filepath" "strings" "github.com/google/blueprint" + "github.com/google/blueprint/proptools" "android/soong/android" ) @@ -61,16 +63,18 @@ var combineApk = pctx.AndroidStaticRule("combineApk", }) func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath, - resJarFile, dexJarFile android.Path, certificates []certificate) { - - // TODO(ccross): JNI libs + resJarFile, jniJarFile, dexJarFile android.Path, certificates []certificate) { unsignedApk := android.PathForModuleOut(ctx, "unsigned.apk") - inputs := android.Paths{resJarFile} + var inputs android.Paths if dexJarFile != nil { inputs = append(inputs, dexJarFile) } + inputs = append(inputs, resJarFile) + if jniJarFile != nil { + inputs = append(inputs, jniJarFile) + } ctx.Build(pctx, android.BuildParams{ Rule: combineApk, @@ -132,3 +136,37 @@ func BuildAAR(ctx android.ModuleContext, outputFile android.WritablePath, }, }) } + +func TransformJniLibsToJar(ctx android.ModuleContext, outputFile android.WritablePath, + jniLibs []jniLib) { + + var deps android.Paths + jarArgs := []string{ + "-j", // junk paths, they will be added back with -P arguments + } + + if !ctx.Config().UnbundledBuild() { + jarArgs = append(jarArgs, "-L 0") + } + + for _, j := range jniLibs { + deps = append(deps, j.path) + jarArgs = append(jarArgs, + "-P "+targetToJniDir(j.target), + "-f "+j.path.String()) + } + + ctx.Build(pctx, android.BuildParams{ + Rule: zip, + Description: "zip jni libs", + Output: outputFile, + Implicits: deps, + Args: map[string]string{ + "jarArgs": strings.Join(proptools.NinjaAndShellEscape(jarArgs), " "), + }, + }) +} + +func targetToJniDir(target android.Target) string { + return filepath.Join("lib", target.Arch.Abi[0]) +} diff --git a/java/app_test.go b/java/app_test.go index c7c94ece6..f6476dc68 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -17,6 +17,7 @@ package java import ( "android/soong/android" "fmt" + "path/filepath" "reflect" "sort" "strings" @@ -338,3 +339,118 @@ func TestAppSdkVersion(t *testing.T) { } } } + +func TestJNI(t *testing.T) { + ctx := testJava(t, ` + toolchain_library { + name: "libcompiler_rt-extras", + src: "", + } + + toolchain_library { + name: "libatomic", + src: "", + } + + toolchain_library { + name: "libgcc", + src: "", + } + + toolchain_library { + name: "libclang_rt.builtins-aarch64-android", + src: "", + } + + toolchain_library { + name: "libclang_rt.builtins-arm-android", + src: "", + } + + cc_object { + name: "crtbegin_so", + stl: "none", + } + + cc_object { + name: "crtend_so", + stl: "none", + } + + cc_library { + name: "libjni", + system_shared_libs: [], + stl: "none", + } + + android_test { + name: "test", + no_framework_libs: true, + jni_libs: ["libjni"], + } + + android_test { + name: "test_first", + no_framework_libs: true, + compile_multilib: "first", + jni_libs: ["libjni"], + } + + android_test { + name: "test_both", + no_framework_libs: true, + compile_multilib: "both", + jni_libs: ["libjni"], + } + + android_test { + name: "test_32", + no_framework_libs: true, + compile_multilib: "32", + jni_libs: ["libjni"], + } + + android_test { + name: "test_64", + no_framework_libs: true, + compile_multilib: "64", + jni_libs: ["libjni"], + } + `) + + // check the existence of the internal modules + ctx.ModuleForTests("test", "android_common") + ctx.ModuleForTests("test_first", "android_common") + ctx.ModuleForTests("test_both", "android_common") + ctx.ModuleForTests("test_32", "android_common") + ctx.ModuleForTests("test_64", "android_common") + + testCases := []struct { + name string + abis []string + }{ + {"test", []string{"arm64-v8a"}}, + {"test_first", []string{"arm64-v8a"}}, + {"test_both", []string{"arm64-v8a", "armeabi-v7a"}}, + {"test_32", []string{"armeabi-v7a"}}, + {"test_64", []string{"arm64-v8a"}}, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + app := ctx.ModuleForTests(test.name, "android_common") + jniLibZip := app.Output("jnilibs.zip") + var abis []string + args := strings.Fields(jniLibZip.Args["jarArgs"]) + for i := 0; i < len(args); i++ { + if args[i] == "-P" { + abis = append(abis, filepath.Base(args[i+1])) + i++ + } + } + if !reflect.DeepEqual(abis, test.abis) { + t.Errorf("want abis %v, got %v", test.abis, abis) + } + }) + } +} diff --git a/java/builder.go b/java/builder.go index 07af8ebda..f55a7c796 100644 --- a/java/builder.go +++ b/java/builder.go @@ -109,6 +109,15 @@ var ( }, "jarArgs") + zip = pctx.AndroidStaticRule("zip", + blueprint.RuleParams{ + Command: `${config.SoongZipCmd} -o $out @$out.rsp`, + CommandDeps: []string{"${config.SoongZipCmd}"}, + Rspfile: "$out.rsp", + RspfileContent: "$jarArgs", + }, + "jarArgs") + combineJar = pctx.AndroidStaticRule("combineJar", blueprint.RuleParams{ Command: `${config.MergeZipsCmd} --ignore-duplicates -j $jarArgs $out $in`, diff --git a/java/java.go b/java/java.go index b4b8feb23..7ef3626d6 100644 --- a/java/java.go +++ b/java/java.go @@ -95,9 +95,6 @@ type CompilerProperties struct { // list of java libraries that will be compiled into the resulting jar Static_libs []string `android:"arch_variant"` - // list of native libraries that will be provided in or alongside the resulting jar - Jni_libs []string `android:"arch_variant"` - // manifest file to be included in resulting jar Manifest *string @@ -365,6 +362,11 @@ type dependencyTag struct { name string } +type jniDependencyTag struct { + blueprint.BaseDependencyTag + target android.Target +} + var ( staticLibTag = dependencyTag{name: "staticlib"} libTag = dependencyTag{name: "javalib"} @@ -389,6 +391,12 @@ type sdkDep struct { aidl android.Path } +type jniLib struct { + name string + path android.Path + target android.Target +} + func (j *Module) shouldInstrument(ctx android.BaseContext) bool { return j.properties.Instrument && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT") } @@ -597,6 +605,7 @@ func (j *Module) deps(ctx android.BottomUpMutatorContext) { ctx.AddFarVariationDependencies([]blueprint.Variation{ {Mutator: "arch", Variation: ctx.Config().BuildOsCommonVariant}, }, annoTag, j.properties.Annotation_processors...) + android.ExtractSourcesDeps(ctx, j.properties.Srcs) android.ExtractSourcesDeps(ctx, j.properties.Exclude_srcs) android.ExtractSourcesDeps(ctx, j.properties.Java_resources) @@ -787,6 +796,11 @@ func (j *Module) collectDeps(ctx android.ModuleContext) deps { otherName := ctx.OtherModuleName(module) tag := ctx.OtherModuleDependencyTag(module) + if _, ok := tag.(*jniDependencyTag); ok { + // Handled by AndroidApp.collectJniDeps + return + } + if to, ok := module.(*Library); ok { switch tag { case bootClasspathTag, libTag, staticLibTag: diff --git a/java/java_test.go b/java/java_test.go index 82accd5a5..1bfd24bbc 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -15,8 +15,6 @@ package java import ( - "android/soong/android" - "android/soong/genrule" "fmt" "io/ioutil" "os" @@ -27,6 +25,10 @@ import ( "testing" "github.com/google/blueprint/proptools" + + "android/soong/android" + "android/soong/cc" + "android/soong/genrule" ) var buildDir string @@ -73,6 +75,7 @@ func testContext(config android.Config, bp string, ctx := android.NewTestArchContext() ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory)) ctx.RegisterModuleType("android_library", android.ModuleFactoryAdaptor(AndroidLibraryFactory)) + ctx.RegisterModuleType("android_test", android.ModuleFactoryAdaptor(AndroidTestFactory)) ctx.RegisterModuleType("java_binary_host", android.ModuleFactoryAdaptor(BinaryHostFactory)) ctx.RegisterModuleType("java_library", android.ModuleFactoryAdaptor(LibraryFactory)) ctx.RegisterModuleType("java_library_host", android.ModuleFactoryAdaptor(LibraryHostFactory)) @@ -95,6 +98,16 @@ func testContext(config android.Config, bp string, ctx.TopDown("java_sdk_library", sdkLibraryMutator).Parallel() }) ctx.RegisterPreSingletonType("overlay", android.SingletonFactoryAdaptor(OverlaySingletonFactory)) + + // Register module types and mutators from cc needed for JNI testing + ctx.RegisterModuleType("cc_library", android.ModuleFactoryAdaptor(cc.LibraryFactory)) + ctx.RegisterModuleType("cc_object", android.ModuleFactoryAdaptor(cc.ObjectFactory)) + ctx.RegisterModuleType("toolchain_library", android.ModuleFactoryAdaptor(cc.ToolchainLibraryFactory)) + ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { + ctx.BottomUp("link", cc.LinkageMutator).Parallel() + ctx.BottomUp("begin", cc.BeginMutator).Parallel() + }) + ctx.Register() extraModules := []string{