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
diff --git a/java/androidmk.go b/java/androidmk.go
index 313a144..7a2fe1e 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -243,6 +243,10 @@
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 dc5296d..843ced6 100644
--- a/java/app.go
+++ b/java/app.go
@@ -19,9 +19,11 @@
import (
"strings"
+ "github.com/google/blueprint"
"github.com/google/blueprint/proptools"
"android/soong/android"
+ "android/soong/cc"
"android/soong/tradefed"
)
@@ -59,6 +61,11 @@
// 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 @@
appProperties appProperties
extraLinkFlags []string
+
+ installJniLibs []jniLib
}
func (a *AndroidApp) ExportedProguardFlagFiles() android.Paths {
@@ -92,9 +101,21 @@
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 @@
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) 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 @@
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 @@
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 @@
&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 e27b1b7..b9b5f43 100644
--- a/java/app_builder.go
+++ b/java/app_builder.go
@@ -19,9 +19,11 @@
// functions.
import (
+ "path/filepath"
"strings"
"github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
"android/soong/android"
)
@@ -61,16 +63,18 @@
})
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 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 c7c94ec..f6476dc 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -17,6 +17,7 @@
import (
"android/soong/android"
"fmt"
+ "path/filepath"
"reflect"
"sort"
"strings"
@@ -338,3 +339,118 @@
}
}
}
+
+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 07af8eb..f55a7c7 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -109,6 +109,15 @@
},
"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 b4b8feb..7ef3626 100644
--- a/java/java.go
+++ b/java/java.go
@@ -95,9 +95,6 @@
// 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 @@
name string
}
+type jniDependencyTag struct {
+ blueprint.BaseDependencyTag
+ target android.Target
+}
+
var (
staticLibTag = dependencyTag{name: "staticlib"}
libTag = dependencyTag{name: "javalib"}
@@ -389,6 +391,12 @@
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 @@
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 @@
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 82accd5..1bfd24b 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 @@
"testing"
"github.com/google/blueprint/proptools"
+
+ "android/soong/android"
+ "android/soong/cc"
+ "android/soong/genrule"
)
var buildDir string
@@ -73,6 +75,7 @@
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 @@
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{