Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Introduce AndroidApkFileReplacerTask task #44993

Merged
merged 5 commits into from
Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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", $"{apkPath}.apk");
EgorBo marked this conversation as resolved.
Show resolved Hide resolved

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

Utils.RunProcess(aapt, $"remove -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
Utils.RunProcess(aapt, $"add -v bin/{Path.GetFileName(apkPath)} {file}", workingDir: OutputDir);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved

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

/// <summary>
Expand Down