Skip to content

Commit f6c58d6

Browse files
dellis1972jonpryor
authored andcommitted
[Xamarin.Android.Build.Tasks] Add support for apksigner (#928)
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=57914 This commit adds support for the `apksigner` tool which ships with android. It is enabled by a new property $(AndroidUseApkSigner) When set to true the old `jarsigner` code will be bypassed. The new system examines the manifest file to figure out the min and max supported versions of android. This is then passed to the `apksigner` along with the other information (like keys etc) and allows `apksigner` to figure out what signing algorithm to use. Additional parameters can be passed to the tool by the developer via the `$(AndroidApkSignerAdditionalArguments)` property.
1 parent f96fcf9 commit f6c58d6

10 files changed

Lines changed: 205 additions & 12 deletions

File tree

Documentation/build_process.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,16 @@ when packaing Release applications.
613613

614614
Added in Xamarin.Android 8.1.
615615

616+
- **AndroidUseApkSigner** – A bool property which allows the developer to
617+
use the to the `apksigner` tool rather than the `jarsigner`.
618+
619+
Added in Xamarin.Android 8.2.
620+
621+
- **AndroidApkSignerAdditionalArguments** – A string property which allows
622+
the developer to provide additional arguments to the `apksigner` tool.
623+
624+
Added in Xamarin.Android 8.2.
625+
616626
## Binding Project Build Properties
617627

618628
The following MSBuild properties are used with

external/xamarin-android-tools

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.IO;
3+
using Microsoft.Build.Framework;
4+
using Microsoft.Build.Utilities;
5+
using Xamarin.Android.Tools;
6+
7+
namespace Xamarin.Android.Tasks
8+
{
9+
public class AndroidApkSigner : AndroidToolTask
10+
{
11+
[Required]
12+
public string ApkToSign { get; set; }
13+
14+
[Required]
15+
public ITaskItem ManifestFile { get; set; }
16+
17+
[Required]
18+
public string KeyStore { get; set; }
19+
20+
[Required]
21+
public string KeyAlias { get; set; }
22+
23+
[Required]
24+
public string KeyPass { get; set; }
25+
26+
[Required]
27+
public string StorePass { get; set; }
28+
29+
public string AdditionalArguments { get; set; }
30+
31+
public override bool Execute ()
32+
{
33+
Log.LogDebugMessage ("AndroidApkSigner:");
34+
Log.LogDebugMessage (" ApkToSign: {0}", ApkToSign);
35+
Log.LogDebugMessage (" ManifestFile: {0}", ManifestFile);
36+
Log.LogDebugMessage (" AdditionalArguments: {0}", AdditionalArguments);
37+
38+
if (!File.Exists (GenerateFullPathToTool ())) {
39+
Log.LogError ($"'{GenerateFullPathToTool ()}' does not exist. You need to install android-sdk build-tools 26.0.1 or above.");
40+
return false;
41+
}
42+
43+
return base.Execute ();
44+
}
45+
46+
protected override string GenerateCommandLineCommands ()
47+
{
48+
var cmd = new CommandLineBuilder ();
49+
50+
var manifest = AndroidAppManifest.Load (ManifestFile.ItemSpec, MonoAndroidHelper.SupportedVersions);
51+
int minSdk = MonoAndroidHelper.SupportedVersions.MinStableVersion.ApiLevel;
52+
int maxSdk = MonoAndroidHelper.SupportedVersions.MaxStableVersion.ApiLevel;
53+
if (manifest.MinSdkVersion.HasValue)
54+
minSdk = manifest.MinSdkVersion.Value;
55+
56+
if (manifest.TargetSdkVersion.HasValue)
57+
maxSdk = manifest.TargetSdkVersion.Value;
58+
59+
cmd.AppendSwitch ("sign");
60+
cmd.AppendSwitchIfNotNull ("--ks ", KeyStore);
61+
cmd.AppendSwitchIfNotNull ("--ks-pass pass:", StorePass);
62+
cmd.AppendSwitchIfNotNull ("--ks-key-alias ", KeyAlias);
63+
cmd.AppendSwitchIfNotNull ("--key-pass pass:", KeyPass);
64+
cmd.AppendSwitchIfNotNull ("--min-sdk-version ", minSdk.ToString ());
65+
cmd.AppendSwitchIfNotNull ("--max-sdk-version ", maxSdk.ToString ());
66+
cmd.AppendSwitchIfNotNull ("--in ", ApkToSign);
67+
68+
if (!string.IsNullOrEmpty (AdditionalArguments))
69+
cmd.AppendSwitch (AdditionalArguments);
70+
71+
return cmd.ToString ();
72+
}
73+
74+
protected override string GenerateFullPathToTool ()
75+
{
76+
return Path.Combine (ToolPath, ToolExe);
77+
}
78+
79+
protected override void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
80+
{
81+
singleLine = singleLine.Trim ();
82+
if (singleLine.Length == 0)
83+
return;
84+
85+
if (singleLine.StartsWith ("Warning:", StringComparison.OrdinalIgnoreCase)) {
86+
Log.LogWarning (singleLine);
87+
return;
88+
}
89+
90+
Log.LogMessage (singleLine, importance);
91+
}
92+
93+
protected override string ToolName {
94+
get {
95+
return ToolExe;
96+
}
97+
}
98+
}
99+
}

src/Xamarin.Android.Build.Tasks/Tasks/ResolveSdksTask.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,18 @@ public class ResolveSdks : Task
110110
[Output]
111111
public string LintToolPath { get; set; }
112112

113+
[Output]
114+
public string ApkSignerToolExe { get; set; }
115+
116+
[Output]
117+
public bool AndroidUseApkSigner { get; set; }
118+
113119
static bool IsWindows = Path.DirectorySeparatorChar == '\\';
114120
static readonly string ZipAlign = IsWindows ? "zipalign.exe" : "zipalign";
115121
static readonly string Aapt = IsWindows ? "aapt.exe" : "aapt";
116122
static readonly string Android = IsWindows ? "android.bat" : "android";
117123
static readonly string Lint = IsWindows ? "lint.bat" : "lint";
124+
static readonly string ApkSigner = "apksigner";
118125

119126

120127
public override bool Execute ()
@@ -215,6 +222,9 @@ public bool RunTask ()
215222
return false;
216223
}
217224

225+
ApkSignerToolExe = MonoAndroidHelper.GetExecutablePath (AndroidSdkBuildToolsBinPath, ApkSigner);
226+
AndroidUseApkSigner = File.Exists (Path.Combine (AndroidSdkBuildToolsBinPath, ApkSignerToolExe));
227+
218228
if (string.IsNullOrEmpty (ZipAlignPath) || !Directory.Exists (ZipAlignPath)) {
219229
ZipAlignPath = new[]{
220230
Path.Combine (AndroidSdkBuildToolsPath),

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,5 +201,27 @@ public void ExplicitPackageNamingPolicy ()
201201
Assert.IsTrue (text.Contains ("package unnamedproject;"), "expected package not found in the source.");
202202
}
203203
}
204+
205+
[Test]
206+
public void CheckSignApk ([Values(true, false)] bool useApkSigner)
207+
{
208+
string ext = Environment.OSVersion.Platform != PlatformID.Unix ? ".exe" : "";
209+
if (useApkSigner && !File.Exists (Path.Combine (AndroidSdkPath, "build-tools", "26.0.1", "apksigner"+ ext))) {
210+
Assert.Ignore ("Skipping test. Required build-tools verison 26.0.1 is not installed.");
211+
}
212+
var proj = new XamarinAndroidApplicationProject () {
213+
IsRelease = true,
214+
};
215+
if (useApkSigner) {
216+
proj.SetProperty ("AndroidUseApkSigner", "true");
217+
proj.SetProperty ("AndroidBuildToolsVersion", "26.0.1");
218+
} else {
219+
proj.RemoveProperty ("AndroidUseApkSigner");
220+
}
221+
using (var b = CreateApkBuilder (Path.Combine ("temp", TestContext.CurrentContext.Test.Name))) {
222+
b.Verbosity = Microsoft.Build.Framework.LoggerVerbosity.Diagnostic;
223+
Assert.IsTrue (b.Build (proj), "build failed");
224+
}
225+
}
204226
}
205227
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ public string TestName {
6565
}
6666
}
6767

68+
public static string AndroidSdkPath {
69+
get {
70+
var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
71+
var sdkPath = Environment.GetEnvironmentVariable ("ANDROID_SDK_PATH");
72+
if (string.IsNullOrEmpty (sdkPath))
73+
sdkPath = Path.Combine (home, "android-toolchain", "sdk");
74+
return sdkPath;
75+
}
76+
}
77+
6878
protected void WaitFor(int milliseconds)
6979
{
7080
var pause = new ManualResetEvent(false);
@@ -74,11 +84,7 @@ protected void WaitFor(int milliseconds)
7484
protected static string RunAdbCommand (string command, bool ignoreErrors = true)
7585
{
7686
string ext = Environment.OSVersion.Platform != PlatformID.Unix ? ".exe" : "";
77-
var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
78-
var sdkPath = Environment.GetEnvironmentVariable ("ANDROID_SDK_PATH");
79-
if (string.IsNullOrEmpty (sdkPath))
80-
sdkPath = Path.Combine (home, "android-toolchain", "sdk");
81-
string adb = Path.Combine (sdkPath, "platform-tools", "adb" + ext);
87+
string adb = Path.Combine (AndroidSdkPath, "platform-tools", "adb" + ext);
8288
var proc = System.Diagnostics.Process.Start (new System.Diagnostics.ProcessStartInfo (adb, command) { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false });
8389
proc.WaitForExit ();
8490
var result = proc.StandardOutput.ReadToEnd ().Trim () + proc.StandardError.ReadToEnd ().Trim ();

src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,13 @@ public string VersionCode {
9999
}
100100
}
101101
public string GetMinimumSdk () {
102+
int defaultMinSdkVersion = MonoAndroidHelper.SupportedVersions.MinStableVersion.ApiLevel;
102103
var minAttr = doc.Root.Element ("uses-sdk")?.Attribute (androidNs + "minSdkVersion");
103104
if (minAttr == null) {
104105
int minSdkVersion;
105106
if (!int.TryParse (SdkVersionName, out minSdkVersion))
106-
minSdkVersion = 11;
107-
return Math.Min (minSdkVersion, 11).ToString ();
107+
minSdkVersion = defaultMinSdkVersion;
108+
return Math.Min (minSdkVersion, defaultMinSdkVersion).ToString ();
108109
}
109110
return minAttr.Value;
110111
}

src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,5 +507,28 @@ public static string [] GetProguardEnvironmentVaribles (string proguardHome)
507507
// On the other hand, xbuild has a bug and fails to parse '=' in the value, so we skip JAVA_TOOL_OPTIONS on Mono runtime.
508508
new string [] { proguardHomeVariable, "JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8" };
509509
}
510+
511+
public static string GetExecutablePath (string dir, string exe)
512+
{
513+
if (string.IsNullOrEmpty (dir))
514+
return exe;
515+
foreach (var e in Executables (exe))
516+
if (File.Exists (Path.Combine (dir, e)))
517+
return e;
518+
return exe;
519+
}
520+
521+
public static IEnumerable<string> Executables (string executable)
522+
{
523+
yield return executable;
524+
var pathExt = Environment.GetEnvironmentVariable ("PATHEXT");
525+
var pathExts = pathExt?.Split (new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
526+
527+
if (pathExts == null)
528+
yield break;
529+
530+
foreach (var ext in pathExts)
531+
yield return Path.ChangeExtension (executable, ext);
532+
}
510533
}
511534
}

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@
490490
<Compile Include="Utilities\AssemblyIdentityMap.cs" />
491491
<Compile Include="Utilities\GdbPaths.cs" />
492492
<Compile Include="Utilities\SatelliteAssembly.cs" />
493+
<Compile Include="Tasks\AndroidApkSigner.cs" />
493494
</ItemGroup>
494495
<ItemGroup>
495496
<_MonoAndroidEnum Include="$(AndroidGeneratedClassDirectory)\Android.Content.PM.LaunchMode.cs" />

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
2222
<UsingTask TaskName="Xamarin.Android.Tasks.AndroidSignPackage" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
2323
<UsingTask TaskName="Xamarin.Android.Tasks.AndroidCreateDebugKey" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
2424
<UsingTask TaskName="Xamarin.Android.Tasks.AndroidZipAlign" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
25+
<UsingTask TaskName="Xamarin.Android.Tasks.AndroidApkSigner" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
2526
<UsingTask TaskName="Xamarin.Android.Tasks.GetAndroidPackageName" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
2627
<UsingTask TaskName="Xamarin.Android.Tasks.ResolveSdks" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
2728
<UsingTask TaskName="Xamarin.Android.Tasks.ReadResolvedSdksCache" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
@@ -649,6 +650,8 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
649650
<Output TaskParameter="AndroidSdkBuildToolsBinPath" PropertyName="AndroidSdkBuildToolsBinPath" Condition="'$(AndroidSdkBuildToolsBinPath)' == ''" />
650651
<Output TaskParameter="AndroidSequencePointsMode" PropertyName="_SequencePointsMode" Condition="'$(_SequencePointsMode)' == ''" />
651652
<Output TaskParameter="LintToolPath" PropertyName="LintToolPath" Condition="'$(LintToolPath)' == ''" />
653+
<Output TaskParameter="ApkSignerToolExe" PropertyName="ApkSignerToolExe" Condition="'$(ApkSignerToolExe)' == ''" />
654+
<Output TaskParameter="AndroidUseApkSigner" PropertyName="AndroidUseApkSigner" Condition="'$(AndroidUseApkSigner)' == ''" />
652655
</ResolveSdks>
653656
<CreateProperty Value="$(TargetFrameworkIdentifier),Version=$(_TargetFrameworkVersion),Profile=$(TargetFrameworkProfile)">
654657
<Output TaskParameter="Value" PropertyName="TargetFrameworkMoniker"
@@ -2428,7 +2431,7 @@ because xbuild doesn't support framework reference assemblies.
24282431
ToolExe="$(KeytoolToolExe)"
24292432
Command="-list"
24302433
Condition="'$(AndroidKeyStore)'==''" />
2431-
<AndroidSignPackage
2434+
<AndroidSignPackage Condition=" '$(AndroidUseApkSigner)' != 'true' "
24322435
UnsignedApk="%(ApkAbiFilesIntermediate.FullPath)"
24332436
SignedApkDirectory="$(OutDir)"
24342437
KeyStore="$(_ApkKeyStore)"
@@ -2441,18 +2444,36 @@ because xbuild doesn't support framework reference assemblies.
24412444
TimestampAuthorityCertificateAlias="$(JarsignerTimestampAuthorityCertificateAlias)"
24422445
SigningAlgorithm="$(AndroidApkSigningAlgorithm)"
24432446
/>
2447+
<Delete Files="$(ApkFileSigned)" Condition=" '$(AndroidUseApkSigner)' == 'true' "/>
2448+
<AndroidZipAlign Condition=" '$(AndroidUseApkSigner)' == 'true' "
2449+
Source="%(ApkAbiFilesIntermediate.FullPath)"
2450+
DestinationDirectory="$(OutDir)"
2451+
ToolPath="$(ZipAlignToolPath)"
2452+
ToolExe="$(ZipalignToolExe)"
2453+
/>
2454+
<AndroidApkSigner Condition=" '$(AndroidUseApkSigner)' == 'true' "
2455+
ApkToSign="$(ApkFileSigned)"
2456+
KeyStore="$(_ApkKeyStore)"
2457+
KeyAlias="$(_ApkKeyAlias)"
2458+
KeyPass="$(_ApkKeyPass)"
2459+
StorePass="$(_ApkStorePass)"
2460+
ToolPath="$(AndroidSdkBuildToolsBinPath)"
2461+
ToolExe="$(ApkSignerToolExe)"
2462+
ManifestFile="$(IntermediateOutputPath)android\AndroidManifest.xml"
2463+
AdditionalArguments="$(AndroidApkSignerAdditionalArguments)"
2464+
/>
24442465
<Message Text="Signed android package '$(ApkFileSigned)'" />
24452466
<ItemGroup>
24462467
<ApkAbiFilesSigned Include="$(ApkFileSigned)" />
24472468
<ApkAbiFilesSigned Condition="'$(AndroidCreatePackagePerAbi)' == 'true'" Include="$(OutDir)$(_AndroidPackage)*-Signed.apk" />
24482469
</ItemGroup>
2449-
<Delete Files="%(ApkAbiFilesSigned.FullPath)" />
2470+
<Delete Files="%(ApkAbiFilesSigned.FullPath)" Condition=" '$(AndroidUseApkSigner)' != 'true' "/>
24502471
<ItemGroup>
24512472
<ApkAbiFilesUnaligned Include="$(OutDir)$(_AndroidPackage)-Signed-Unaligned.apk" />
24522473
<ApkAbiFilesUnaligned Condition="'$(AndroidCreatePackagePerAbi)' == 'true'" Include="$(OutDir)$(_AndroidPackage)*-Signed-Unaligned.apk" />
24532474
</ItemGroup>
2454-
<Message Text="Unaligned android package '%(ApkAbiFilesUnaligned.FullPath)'" />
2455-
<AndroidZipAlign
2475+
<Message Text="Unaligned android package '%(ApkAbiFilesUnaligned.FullPath)'" Condition=" '$(AndroidUseApkSigner)' != 'true' "/>
2476+
<AndroidZipAlign Condition=" '$(AndroidUseApkSigner)' != 'true' "
24562477
Source="%(ApkAbiFilesUnaligned.FullPath)"
24572478
DestinationDirectory="$(OutDir)"
24582479
ToolPath="$(ZipAlignToolPath)"

0 commit comments

Comments
 (0)