[mono] Shrink Android APK size (#36437)
authorEgor Bogatov <egorbo@gmail.com>
Thu, 14 May 2020 16:56:11 +0000 (19:56 +0300)
committerGitHub <noreply@github.com>
Thu, 14 May 2020 16:56:11 +0000 (16:56 +0000)
* Shrink Android apk size

* bump xharness cli, use cmake config

.config/dotnet-tools.json
eng/testing/tests.mobile.targets
src/mono/msbuild/AndroidAppBuilder/AndroidAppBuilder.cs
src/mono/msbuild/AndroidAppBuilder/ApkBuilder.cs

index 49e4750..73baf5d 100644 (file)
@@ -15,7 +15,7 @@
       ]
     },
     "microsoft.dotnet.xharness.cli": {
-      "version": "1.0.0-prerelease.20254.3",
+      "version": "1.0.0-prerelease.20264.2",
       "commands": [
         "xharness"
       ]
index 4e6ceb2..da84cdf 100644 (file)
@@ -36,6 +36,7 @@
         ProjectName="$(AssemblyName)"
         MonoRuntimeHeaders="$(RuntimePackNativeDir)include\mono-2.0"
         MainLibraryFileName="AndroidTestRunner.dll"
+        StripDebugSymbols="False"
         OutputDir="$(BundleDir)"
         SourceDir="$(PublishDir)">
         <Output TaskParameter="ApkPackageId"  PropertyName="ApkPackageId" />
index 57e1f90..cc915ae 100644 (file)
@@ -41,6 +41,8 @@ public class AndroidAppBuilderTask : Task
     
     public string? BuildToolsVersion { get; set; }
 
+    public bool StripDebugSymbols { get; set; }
+
     [Output]
     public string ApkBundlePath { get; set; } = ""!;
     
@@ -59,6 +61,7 @@ public class AndroidAppBuilderTask : Task
         apkBuilder.MinApiLevel = MinApiLevel;
         apkBuilder.BuildApiLevel = BuildApiLevel;
         apkBuilder.BuildToolsVersion = BuildToolsVersion;
+        apkBuilder.StripDebugSymbols = StripDebugSymbols;
         (ApkBundlePath, ApkPackageId) = apkBuilder.BuildApk(SourceDir, Abi, MainLibraryFileName, MonoRuntimeHeaders);
         
         return true;
index 715d4e7..4330c74 100644 (file)
@@ -14,6 +14,7 @@ public class ApkBuilder
     public string? BuildApiLevel { get; set; }
     public string? BuildToolsVersion { get; set; }
     public string? OutputDir { get; set; }
+    public bool StripDebugSymbols { get; set; }
 
     public (string apk, string packageId) BuildApk(
         string sourceDir, string abi, string entryPointLib, string monoRuntimeHeaders)
@@ -81,15 +82,28 @@ public class ApkBuilder
         Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
         Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));
         
+        var extensionsToIgnore = new List<string> { ".so", ".a", ".gz" };
+        if (StripDebugSymbols)
+        {
+            extensionsToIgnore.Add(".pdb");
+            extensionsToIgnore.Add(".dbg");
+        }
+
         // Copy AppDir to OutputDir/assets (ignore native files)
         Utils.DirectoryCopy(sourceDir, Path.Combine(OutputDir, "assets"), file =>
         {
             string fileName = Path.GetFileName(file);
             string extension = Path.GetExtension(file);
-            // ignore native files, those go to lib/%abi%
-            if (extension == ".so" || extension == ".a")
+
+            if (file.Any(s => s >= 128))
+            {
+                // non-ascii files/folders are not allowed
+                return false;
+            }
+            if (extensionsToIgnore.Contains(extension))
             {
-                // ignore ".pdb" and ".dbg" to make APK smaller
+                // ignore native files, those go to lib/%abi%
+                // also, aapt is not happy about zip files
                 return false;
             }
             if (fileName.StartsWith("."))
@@ -129,10 +143,25 @@ public class ApkBuilder
             .Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib));
         File.WriteAllText(Path.Combine(OutputDir, "runtime-android.c"), runtimeAndroidSrc);
         
-        Utils.RunProcess(cmake, workingDir: OutputDir,
-            args: $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " + 
-            $"-DANDROID_NATIVE_API_LEVEL={MinApiLevel} -B runtime-android");
-        Utils.RunProcess("make", workingDir: Path.Combine(OutputDir, "runtime-android"));
+        string cmakeGenArgs = $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " + 
+            $"-DANDROID_NATIVE_API_LEVEL={MinApiLevel} -B runtime-android";
+
+        string cmakeBuildArgs = "--build runtime-android";
+        
+        if (StripDebugSymbols)
+        {
+            // Use "-s" to strip debug symbols, it complains it's unused but it works
+            cmakeGenArgs+= " -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_C_FLAGS=\"-s -Wno-unused-command-line-argument\"";
+            cmakeBuildArgs += " --config MinSizeRel";
+        }
+        else
+        {
+            cmakeGenArgs += " -DCMAKE_BUILD_TYPE=Debug";
+            cmakeBuildArgs += " --config Debug";
+        }
+
+        Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeGenArgs);
+        Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeBuildArgs);
 
         // 2. Compile Java files
 
@@ -168,7 +197,16 @@ public class ApkBuilder
         Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
         foreach (var dynamicLib in dynamicLibs)
         {
-            string destRelative = Path.Combine("lib", abi, Path.GetFileName(dynamicLib));
+            string dynamicLibName = Path.GetFileName(dynamicLib);
+            if (dynamicLibName == "libmonosgen-2.0.so")
+            {
+                // we link mono runtime statically into libruntime-android.so
+                continue;
+            }
+
+            // NOTE: we can run android-strip tool from NDK to shrink native binaries here even more.
+
+            string destRelative = Path.Combine("lib", abi, dynamicLibName);
             File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true);
             Utils.RunProcess(aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir);
         }
@@ -194,6 +232,8 @@ public class ApkBuilder
         Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " + 
             $"--ks-pass pass:android --key-pass pass:android {alignedApk}", workingDir: OutputDir);
 
+        Utils.LogInfo($"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n");
+
         return (alignedApk, packageId);
     }