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
This commit is contained in:
Colin Cross 2018-10-02 22:03:40 -07:00
parent b1a5e9cadf
commit a4f08813a3
10 changed files with 276 additions and 14 deletions

View File

@ -219,6 +219,7 @@ bootstrap_go_package {
"blueprint-pathtools",
"soong",
"soong-android",
"soong-cc",
"soong-genrule",
"soong-java-config",
"soong-tradefed",

View File

@ -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}},

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

@ -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`,

View File

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

View File

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