Skip to content

Commit

Permalink
[Android] Introduce AndroidApkFileReplacerTask task (#44993)
Browse files Browse the repository at this point in the history
  • Loading branch information
EgorBo authored Nov 23, 2020
1 parent 8861c41 commit 65d42f0
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class AndroidApkFileReplacerTask : Task
{
[Required]
public string FilePath { get; set; } = ""!;

[Required]
public string OutputDir { get; set; } = ""!;

public string? AndroidSdk { get; set; }

public string? MinApiLevel { get; set; }

public string? BuildToolsVersion { get; set; }

public string? KeyStorePath { get; set; }


public override bool Execute()
{
var apkBuilder = new ApkBuilder();
apkBuilder.OutputDir = OutputDir;
apkBuilder.AndroidSdk = AndroidSdk;
apkBuilder.MinApiLevel = MinApiLevel;
apkBuilder.BuildToolsVersion = BuildToolsVersion;
apkBuilder.KeyStorePath = KeyStorePath;
apkBuilder.ReplaceFileInApk(FilePath);
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ public class AndroidAppBuilderTask : Task
[Required]
public string RuntimeIdentifier { get; set; } = ""!;

public string? ProjectName { get; set; }
[Required]
public string OutputDir { get; set; } = ""!;

public string? OutputDir { get; set; }
public string? ProjectName { get; set; }

public string? AndroidSdk { get; set; }

Expand Down Expand Up @@ -78,20 +79,13 @@ public override bool Execute()
return true;
}

private string DetermineAbi()
{
switch (RuntimeIdentifier)
private string DetermineAbi() =>
RuntimeIdentifier switch
{
case "android-x86":
return "x86";
case "android-x64":
return "x86_64";
case "android-arm":
return "armeabi-v7a";
case "android-arm64":
return "arm64-v8a";
default:
throw new ArgumentException(RuntimeIdentifier + " is not supported for Android");
}
}
"android-x86" => "x86",
"android-x64" => "x86_64",
"android-arm" => "armeabi-v7a",
"android-arm64" => "arm64-v8a",
_ => throw new ArgumentException($"{RuntimeIdentifier} is not supported for Android"),
};
}
64 changes: 48 additions & 16 deletions tools-local/tasks/mobile.tasks/AndroidAppBuilder/ApkBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class ApkBuilder
public string? MinApiLevel { get; set; }
public string? BuildApiLevel { get; set; }
public string? BuildToolsVersion { get; set; }
public string? OutputDir { get; set; }
public string OutputDir { get; set; } = ""!;
public bool StripDebugSymbols { get; set; }
public string? NativeMainSource { get; set; }
public string? KeyStorePath { get; set; }
Expand Down Expand Up @@ -50,9 +50,6 @@ public class ApkBuilder
ProjectName = Path.GetFileNameWithoutExtension(entryPointLib);
}

if (string.IsNullOrEmpty(OutputDir))
OutputDir = Path.Combine(sourceDir, "bin-" + abi);

if (ProjectName.Contains(' '))
throw new ArgumentException($"ProjectName='{ProjectName}' shouldn't not contain spaces.");

Expand Down Expand Up @@ -134,7 +131,6 @@ public class ApkBuilder
string apksigner = Path.Combine(buildToolsFolder, "apksigner");
string androidJar = Path.Combine(AndroidSdk, "platforms", "android-" + BuildApiLevel, "android.jar");
string androidToolchain = Path.Combine(AndroidNdk, "build", "cmake", "android.toolchain.cmake");
string keytool = "keytool";
string javac = "javac";
string cmake = "cmake";
string zip = "zip";
Expand Down Expand Up @@ -244,30 +240,66 @@ public class ApkBuilder
// we don't need the unaligned one any more
File.Delete(apkFile);

// 5. Generate key
// 5. Generate key (if needed) & sign the apk
SignApk(alignedApk, apksigner);

Utils.LogInfo($"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n");

return (alignedApk, packageId);
}

private void SignApk(string apkPath, string apksigner)
{
string defaultKey = Path.Combine(OutputDir, "debug.keystore");
string signingKey = string.IsNullOrEmpty(KeyStorePath) ?
defaultKey : Path.Combine(KeyStorePath, "debug.keystore");

string signingKey = Path.Combine(OutputDir, "debug.keystore");
if (!string.IsNullOrEmpty(KeyStorePath))
signingKey = Path.Combine(KeyStorePath, "debug.keystore");
if (!File.Exists(signingKey))
{
Utils.RunProcess(keytool, "-genkey -v -keystore debug.keystore -storepass android -alias " +
Utils.RunProcess("keytool", "-genkey -v -keystore debug.keystore -storepass android -alias " +
"androiddebugkey -keypass android -keyalg RSA -keysize 2048 -noprompt " +
"-dname \"CN=Android Debug,O=Android,C=US\"", workingDir: OutputDir, silent: true);
}
else
else if (Path.GetFullPath(signingKey) != Path.GetFullPath(defaultKey))
{
File.Copy(signingKey, Path.Combine(OutputDir, "debug.keystore"));
}
Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " +
$"--ks-pass pass:android --key-pass pass:android {apkPath}", workingDir: OutputDir);
}

// 6. Sign APK
public void ReplaceFileInApk(string file)
{
if (string.IsNullOrEmpty(AndroidSdk))
AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");

Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " +
$"--ks-pass pass:android --key-pass pass:android {alignedApk}", workingDir: OutputDir);
if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or incorrect (can be set via ANDROID_SDK_ROOT envvar).");

Utils.LogInfo($"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n");
if (string.IsNullOrEmpty(BuildToolsVersion))
BuildToolsVersion = GetLatestBuildTools(AndroidSdk);

return (alignedApk, packageId);
if (string.IsNullOrEmpty(MinApiLevel))
MinApiLevel = DefaultMinApiLevel;

string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);
string aapt = Path.Combine(buildToolsFolder, "aapt");
string apksigner = Path.Combine(buildToolsFolder, "apksigner");

string apkPath = "";
if (string.IsNullOrEmpty(ProjectName))
apkPath = Directory.GetFiles(Path.Combine(OutputDir, "bin"), "*.apk").First();
else
apkPath = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");

if (!File.Exists(apkPath))
throw new Exception($"{apkPath} was not found");

Utils.RunProcess(aapt, $"remove -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir);
Utils.RunProcess(aapt, $"add -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir);

// we need to re-sign the apk
SignApk(apkPath, apksigner);
}

/// <summary>
Expand Down

0 comments on commit 65d42f0

Please sign in to comment.