From e4246abd7f456eb4119f2cffc01bdfca852584b5 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Tue, 5 Feb 2019 21:55:21 -0800 Subject: [PATCH] Make manifest and APK agree on uncompressed native libs Only put uncompressed native libs in an APK if the min_sdk_version supports it (>= 23, Marshmallow), and set android:extractNativeLibs="false" in the AndroidManifest.xml so that the platform won't extract them anyways. Bug: 117618214 Test: m checkbuild Change-Id: I760017e48bf3c6b618aabde0982df45995765d48 --- androidmk/cmd/androidmk/android.go | 1 + java/aar.go | 5 ++-- java/android_manifest.go | 16 +++++++++-- java/app.go | 26 ++++++++++++++---- java/app_builder.go | 4 +-- java/builder.go | 4 +-- scripts/manifest_fixer.py | 33 ++++++++++++++++++++++- scripts/manifest_fixer_test.py | 43 ++++++++++++++++++++++++++++++ 8 files changed, 118 insertions(+), 14 deletions(-) diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go index aef8944df..abe79179e 100644 --- a/androidmk/cmd/androidmk/android.go +++ b/androidmk/cmd/androidmk/android.go @@ -188,6 +188,7 @@ func init() { "LOCAL_EXPORT_PACKAGE_RESOURCES": "export_package_resources", "LOCAL_PRIVILEGED_MODULE": "privileged", "LOCAL_AAPT_INCLUDE_ALL_RESOURCES": "aapt_include_all_resources", + "LOCAL_USE_EMBEDDED_NATIVE_LIBS": "use_embedded_native_libs", "LOCAL_DEX_PREOPT": "dex_preopt.enabled", "LOCAL_DEX_PREOPT_APP_IMAGE": "dex_preopt.app_image", diff --git a/java/aar.go b/java/aar.go index fcdd518b4..f6a3d3a75 100644 --- a/java/aar.go +++ b/java/aar.go @@ -75,6 +75,7 @@ type aapt struct { rTxt android.Path extraAaptPackagesFile android.Path isLibrary bool + uncompressedJNI bool aaptProperties aaptProperties } @@ -181,7 +182,7 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml") manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile) - manifestPath := manifestMerger(ctx, manifestSrcPath, sdkContext, staticLibManifests, a.isLibrary) + manifestPath := manifestMerger(ctx, manifestSrcPath, sdkContext, staticLibManifests, a.isLibrary, a.uncompressedJNI) linkFlags, linkDeps, resDirs, overlayDirs, rroDirs := a.aapt2Flags(ctx, sdkContext, manifestPath) @@ -330,7 +331,7 @@ func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { } func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { - a.isLibrary = true + a.aapt.isLibrary = true a.aapt.buildActions(ctx, sdkContext(a)) ctx.CheckbuildFile(a.proguardOptionsFile) diff --git a/java/android_manifest.go b/java/android_manifest.go index 36f24ff97..6d4399ddb 100644 --- a/java/android_manifest.go +++ b/java/android_manifest.go @@ -15,12 +15,13 @@ package java import ( - "android/soong/java/config" + "fmt" "strings" "github.com/google/blueprint" "android/soong/android" + "android/soong/java/config" ) var manifestFixerRule = pctx.AndroidStaticRule("manifestFixer", @@ -43,11 +44,22 @@ var manifestMergerRule = pctx.AndroidStaticRule("manifestMerger", "libs") func manifestMerger(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext, - staticLibManifests android.Paths, isLibrary bool) android.Path { + staticLibManifests android.Paths, isLibrary bool, uncompressedJNI bool) android.Path { var args []string if isLibrary { args = append(args, "--library") + } else { + minSdkVersion, err := sdkVersionToNumber(ctx, sdkContext.minSdkVersion()) + if err != nil { + ctx.ModuleErrorf("invalid minSdkVersion: %s", err) + } + if minSdkVersion >= 23 { + args = append(args, fmt.Sprintf("--extract-native-libs=%v", !uncompressedJNI)) + } else if uncompressedJNI { + ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it", + minSdkVersion) + } } // Inject minSdkVersion into the manifest diff --git a/java/app.go b/java/app.go index 08714f2c9..47f4f0d80 100644 --- a/java/app.go +++ b/java/app.go @@ -68,7 +68,11 @@ type appProperties struct { // list of native libraries that will be provided in or alongside the resulting jar Jni_libs []string `android:"arch_variant"` - EmbedJNI bool `blueprint:"mutated"` + // Store native libraries uncompressed in the APK and set the android:extractNativeLibs="false" manifest + // flag so that they are used from inside the APK at runtime. Defaults to true for android_test modules unless + // sdk_version or min_sdk_version is set to a version that doesn't support it (<23), defaults to false for other + // module types where the native libraries are generally preinstalled outside the APK. + Use_embedded_native_libs *bool } type AndroidApp struct { @@ -136,9 +140,21 @@ func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) { } func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) { + a.aapt.uncompressedJNI = a.shouldUncompressJNI(ctx) a.generateAndroidBuildActions(ctx) } +// shouldUncompressJNI returns true if the native libraries should be stored in the APK uncompressed and the +// extractNativeLibs application flag should be set to false in the manifest. +func (a *AndroidApp) shouldUncompressJNI(ctx android.ModuleContext) bool { + minSdkVersion, err := sdkVersionToNumber(ctx, a.minSdkVersion()) + if err != nil { + ctx.PropertyErrorf("min_sdk_version", "invalid value %q: %s", a.minSdkVersion(), err) + } + + return minSdkVersion >= 23 && Bool(a.appProperties.Use_embedded_native_libs) +} + // Returns whether this module should have the dex file stored uncompressed in the APK. func (a *AndroidApp) shouldUncompressDex(ctx android.ModuleContext) bool { if ctx.Config().UnbundledBuild() { @@ -230,10 +246,10 @@ func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path { func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath { var jniJarFile android.WritablePath if len(jniLibs) > 0 { - embedJni := ctx.Config().UnbundledBuild() || a.appProperties.EmbedJNI + embedJni := ctx.Config().UnbundledBuild() || Bool(a.appProperties.Use_embedded_native_libs) if embedJni { jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip") - TransformJniLibsToJar(ctx, jniJarFile, jniLibs) + TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.shouldUncompressJNI(ctx)) } else { a.installJniLibs = jniLibs } @@ -428,7 +444,7 @@ func AndroidTestFactory() android.Module { module.Module.properties.Instrument = true module.Module.properties.Installable = proptools.BoolPtr(true) - module.appProperties.EmbedJNI = true + module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true) module.Module.dexpreopter.isTest = true module.AddProperties( @@ -464,7 +480,7 @@ func AndroidTestHelperAppFactory() android.Module { module.Module.deviceProperties.Optimize.Enabled = proptools.BoolPtr(true) module.Module.properties.Installable = proptools.BoolPtr(true) - module.appProperties.EmbedJNI = true + module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true) module.Module.dexpreopter.isTest = true module.AddProperties( diff --git a/java/app_builder.go b/java/app_builder.go index 5b999d831..6cc21595c 100644 --- a/java/app_builder.go +++ b/java/app_builder.go @@ -200,14 +200,14 @@ func BuildBundleModule(ctx android.ModuleContext, outputFile android.WritablePat } func TransformJniLibsToJar(ctx android.ModuleContext, outputFile android.WritablePath, - jniLibs []jniLib) { + jniLibs []jniLib, uncompressJNI bool) { var deps android.Paths jarArgs := []string{ "-j", // junk paths, they will be added back with -P arguments } - if !ctx.Config().UnbundledBuild() { + if uncompressJNI { jarArgs = append(jarArgs, "-L 0") } diff --git a/java/builder.go b/java/builder.go index 7aac881ac..aa61a85e6 100644 --- a/java/builder.go +++ b/java/builder.go @@ -122,8 +122,8 @@ var ( zipalign = pctx.AndroidStaticRule("zipalign", blueprint.RuleParams{ - Command: "if ! ${config.ZipAlign} -c 4 $in > /dev/null; then " + - "${config.ZipAlign} -f 4 $in $out; " + + Command: "if ! ${config.ZipAlign} -c -p 4 $in > /dev/null; then " + + "${config.ZipAlign} -f -p 4 $in $out; " + "else " + "cp -f $in $out; " + "fi", diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py index 917f55b4a..83868e603 100755 --- a/scripts/manifest_fixer.py +++ b/scripts/manifest_fixer.py @@ -63,8 +63,12 @@ def parse_args(): help='manifest is for a package built against the platform') parser.add_argument('--use-embedded-dex', dest='use_embedded_dex', action='store_true', help=('specify if the app wants to use embedded dex and avoid extracted,' - 'locally compiled code. Should not be conflict if already declared ' + 'locally compiled code. Must not conflict if already declared ' 'in the manifest.')) + parser.add_argument('--extract-native-libs', dest='extract_native_libs', + default=None, type=lambda x: (str(x).lower() == 'true'), + help=('specify if the app wants to use embedded native libraries. Must not conflict ' + 'if already declared in the manifest.')) parser.add_argument('input', help='input AndroidManifest.xml file') parser.add_argument('output', help='output AndroidManifest.xml file') return parser.parse_args() @@ -295,6 +299,30 @@ def add_use_embedded_dex(doc): raise RuntimeError('existing attribute mismatches the option of --use-embedded-dex') +def add_extract_native_libs(doc, extract_native_libs): + manifest = parse_manifest(doc) + elems = get_children_with_tag(manifest, 'application') + application = elems[0] if len(elems) == 1 else None + if len(elems) > 1: + raise RuntimeError('found multiple tags') + elif not elems: + application = doc.createElement('application') + indent = get_indent(manifest.firstChild, 1) + first = manifest.firstChild + manifest.insertBefore(doc.createTextNode(indent), first) + manifest.insertBefore(application, first) + + value = str(extract_native_libs).lower() + attr = application.getAttributeNodeNS(android_ns, 'extractNativeLibs') + if attr is None: + attr = doc.createAttributeNS(android_ns, 'android:extractNativeLibs') + attr.value = value + application.setAttributeNode(attr) + elif attr.value != value: + raise RuntimeError('existing attribute extractNativeLibs="%s" conflicts with --extract-native-libs="%s"' % + (attr.value, value)) + + def write_xml(f, doc): f.write('\n') for node in doc.childNodes: @@ -325,6 +353,9 @@ def main(): if args.use_embedded_dex: add_use_embedded_dex(doc) + if args.extract_native_libs is not None: + add_extract_native_libs(doc, args.extract_native_libs) + with open(args.output, 'wb') as f: write_xml(f, doc) diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py index 1d8de5565..4ad9afaf3 100755 --- a/scripts/manifest_fixer_test.py +++ b/scripts/manifest_fixer_test.py @@ -381,5 +381,48 @@ class UseEmbeddedDexTest(unittest.TestCase): manifest_input = self.manifest_tmpl % self.use_embedded_dex('false') self.assertRaises(RuntimeError, self.run_test, manifest_input) + +class AddExtractNativeLibsTest(unittest.TestCase): + """Unit tests for add_extract_native_libs function.""" + + def run_test(self, input_manifest, value): + doc = minidom.parseString(input_manifest) + manifest_fixer.add_extract_native_libs(doc, value) + output = StringIO.StringIO() + manifest_fixer.write_xml(output, doc) + return output.getvalue() + + manifest_tmpl = ( + '\n' + '\n' + ' \n' + '\n') + + def extract_native_libs(self, value): + return ' android:extractNativeLibs="%s"' % value + + def test_set_true(self): + manifest_input = self.manifest_tmpl % '' + expected = self.manifest_tmpl % self.extract_native_libs('true') + output = self.run_test(manifest_input, True) + self.assertEqual(output, expected) + + def test_set_false(self): + manifest_input = self.manifest_tmpl % '' + expected = self.manifest_tmpl % self.extract_native_libs('false') + output = self.run_test(manifest_input, False) + self.assertEqual(output, expected) + + def test_match(self): + manifest_input = self.manifest_tmpl % self.extract_native_libs('true') + expected = manifest_input + output = self.run_test(manifest_input, True) + self.assertEqual(output, expected) + + def test_conflict(self): + manifest_input = self.manifest_tmpl % self.extract_native_libs('true') + self.assertRaises(RuntimeError, self.run_test, manifest_input, False) + + if __name__ == '__main__': unittest.main()