Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Implement a new process for defining ve…
Browse files Browse the repository at this point in the history
  • Loading branch information
dellis1972 committed Jul 12, 2017
1 parent 42ec1af commit c1d8e33
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 5 deletions.
52 changes: 52 additions & 0 deletions Documentation/build_process.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,58 @@ when packaing Release applications.

Added in Xamarin.Android 7.1.

- **AndroidVersionCodePattern** &ndsh; A string property which allows
the developer to customize the `versionCode` in the manifest when splitting
up the apk by abi.
See [Creating_the_Version_Code_for_the_APK](https://developer.xamarin.com/guides/android/advanced_topics/build-abi-specific-apks/#Creating_the_Version_Code_for_the_APK)
for information on deciding a `versionCode`.

Some examples, if `abi` is `armeabi` and `versionCode` in the manifest
is `123`

`{abi}{versionCode}`

will prodice a versionCode of `1123`.
If `abi` is `x86_64` and `versionCode` in the manifest
is `44`. This will produce `544`.

If we include some left padding semantics

`{abi}{versionCode:0000}`

it would produde `50044` because we are left padding the `versionCode`
with `0`.

Pre defined key items

- **abi** &ndsh; Inserts the targetted abi for the app
- 1 – `armeabi`
- 2 – `armeabi-v7a`
- 3 – `x86`
- 4 – `arm64-v8a`
- 5 – `x86_64`

- **minSDK** &ndsh; Inserts the minimum supported Sdk
value from the `AndroidManifest.xml` or `11` if none is
defined.

- **versionCode** &ndsh; Uses the version code direrctly from
`Properties\AndroidManifest.xml`.

You can define custom items using the [AndroidVersionCodeProperties] (#AndroidVersionCodeProperties)
property.

Added in Xamarin.Android 7.2.
- **AndroidVersionCodeProperties** &ndsh; A string property which allows
the developer to define custom items to use with the [AndroidVersionCodePattern] (#AndroidVersionCodePattern).
They are in the form of a `key=value` pair. All items in the `value` should
be integer values.

`screen=23;target=$(_SupportedApiLevel)`



Added in Xamarin.Android 7.2.
## Binding Project Build Properties

The following MSBuild properties are used with
Expand Down
24 changes: 22 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ public class Aapt : AsyncTask

public bool ExplicitCrunch { get; set; }

// pattern to use for the version code. Used in CreatePackagePerAbi
// eg. {abi:00}{dd}{version}
// known keyworks
// {abi} the value for the current abi
// {version} the version code from the manifest.
public string VersionCodePattern { get; set; }

// Name=Value pair seperated by ';'
// e.g screen=21;abi=11
public string VersionCodeProperties { get; set; }

public string AndroidSdkPlatform { get; set; }

Dictionary<string,string> resource_name_case_map = new Dictionary<string,string> ();

bool ManifestIsUpToDate (string manifestFile)
Expand Down Expand Up @@ -190,6 +203,8 @@ public override bool Execute ()
Log.LogDebugMessage (" ExtraArgs: {0}", ExtraArgs);
Log.LogDebugMessage (" CreatePackagePerAbi: {0}", CreatePackagePerAbi);
Log.LogDebugMessage (" ResourceNameCaseMap: {0}", ResourceNameCaseMap);
Log.LogDebugMessage (" VersionCodePattern: {0}", VersionCodePattern);
Log.LogDebugMessage (" VersionCodeProperties: {0}", VersionCodeProperties);
if (CreatePackagePerAbi)
Log.LogDebugMessage (" SupportedAbis: {0}", SupportedAbis);

Expand Down Expand Up @@ -244,8 +259,13 @@ protected string GenerateCommandLineCommands (string ManifestFile, string curren
Directory.CreateDirectory (manifestDir);
manifestFile = Path.Combine (manifestDir, Path.GetFileName (ManifestFile));
ManifestDocument manifest = new ManifestDocument (ManifestFile, this.Log);
if (currentAbi != null)
manifest.SetAbi (currentAbi);
manifest.SdkVersion = AndroidSdkPlatform;
if (currentAbi != null) {
if (!string.IsNullOrEmpty (VersionCodePattern))
manifest.CalculateVersionCode (currentAbi, VersionCodePattern, VersionCodeProperties);
else
manifest.SetAbi (currentAbi);
}
manifest.ApplicationName = ApplicationName;
manifest.Save (manifestFile);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using NUnit.Framework;
using Xamarin.ProjectTools;
Expand Down Expand Up @@ -264,6 +264,98 @@ public void DirectBootAwareAttribute ()
}
}

static object [] VersionCodeTestSource = new object [] {
new object[] {
/* seperateApk */ false,
/* abis */ "armeabi-v7a",
/* versionCode */ "123",
/* pattern */ null,
/* props */ null,
/* expected */ "123",
},
new object[] {
/* seperateApk */ false,
/* abis */ "armeabi-v7a",
/* versionCode */ "123",
/* pattern */ "{abi}{versionCode}",
/* props */ null,
/* expected */ "123",
},
new object[] {
/* seperateApk */ false,
/* abis */ "armeabi-v7a;x86",
/* versionCode */ "123",
/* pattern */ "{abi}{versionCode}",
/* props */ null,
/* expected */ "123",
},
new object[] {
/* seperateApk */ true,
/* abis */ "armeabi-v7a;x86",
/* versionCode */ "123",
/* pattern */ null,
/* props */ null,
/* expected */ "131195;196731",
},
new object[] {
/* seperateApk */ true,
/* abis */ "armeabi-v7a;x86",
/* versionCode */ "123",
/* pattern */ "{abi}{versionCode}",
/* props */ null,
/* expected */ "2123;3123",
},
new object[] {
/* seperateApk */ true,
/* abis */ "armeabi-v7a;x86",
/* versionCode */ "12",
/* pattern */ "{abi}{minSDK:00}{versionCode:000}",
/* props */ null,
/* expected */ "211012;311012",
},
};

[Test]
[TestCaseSource("VersionCodeTestSource")]
public void VersionCodeTests (bool seperateApk, string abis, string versionCode, string versionCodePattern, string versionCodeProperties, string expected)
{
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
};
proj.SetProperty (proj.ReleaseProperties, KnownProperties.AndroidCreatePackagePerAbi, seperateApk);
if (!string.IsNullOrEmpty (abis))
proj.SetProperty (proj.ReleaseProperties, KnownProperties.AndroidSupportedAbis, abis);
if (!string.IsNullOrEmpty (versionCodePattern))
proj.SetProperty (proj.ReleaseProperties, "AndroidVersionCodePattern", versionCodePattern);
else
proj.RemoveProperty (proj.ReleaseProperties, "AndroidVersionCodePattern");
if (!string.IsNullOrEmpty (versionCodeProperties))
proj.SetProperty (proj.ReleaseProperties, "AndroidVersionCodeProperties", versionCodeProperties);
else
proj.RemoveProperty (proj.ReleaseProperties, "AndroidVersionCodeProperties");
proj.AndroidManifest = proj.AndroidManifest.Replace ("android:versionCode=\"1\"", $"android:versionCode=\"{versionCode}\"");
using (var builder = CreateApkBuilder (Path.Combine ("temp", "VersionCodeTests"), false, false)) {
builder.Build (proj);
var abiItems = seperateApk ? abis.Split (';') : new string[1];
var expectedItems = expected.Split (';');
XNamespace aNS = "http://schemas.android.com/apk/res/android";
Assert.AreEqual (abiItems.Length, expectedItems.Length, "abis parameter should have matching elements for expected");
for (int i = 0; i < abiItems.Length; i++) {
var path = seperateApk ? Path.Combine ("android", abiItems[i], "AndroidManifest.xml") : Path.Combine ("android", "AndroidManifest.xml");
var manifest = builder.Output.GetIntermediaryAsText (Root, path);
var doc = XDocument.Parse (manifest);
var nsResolver = new XmlNamespaceManager (new NameTable ());
nsResolver.AddNamespace ("android", "http://schemas.android.com/apk/res/android");
var m = doc.XPathSelectElement ("/manifest") as XElement;
Assert.IsNotNull (m, "no manifest element found");
var vc = m.Attribute (aNS + "versionCode");
Assert.IsNotNull (vc, "no versionCode attribute found");
StringAssert.AreEqualIgnoringCase (expectedItems[i], vc.Value,
$"Version Code is incorrect. Found {vc.Value} expect {expectedItems[i]}");
}
}
}

[Test]
public void ManifestPlaceholders ()
{
Expand Down
48 changes: 46 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ internal class ManifestDocument
{
public static XNamespace AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";

const int maxVersionCode = 2100000000;

static XNamespace androidNs = AndroidXmlNamespace;

XDocument doc;
Expand Down Expand Up @@ -68,6 +70,19 @@ public string VersionCode {
doc.Root.SetAttributeValue (androidNs + "versionCode", value);
}
}
public string MinimumSdk {
get {
var uses = doc.Root.Element ("uses-sdk");
if (uses?.Attribute (androidNs + "minSdkVersion") == null) {
int minSdkVersion;
if (!int.TryParse (SdkVersionName, out minSdkVersion))
minSdkVersion = 11;
return Math.Min (minSdkVersion, 11).ToString ();
} else {
return uses.Attribute (androidNs + "minSdkVersion").Value;
}
}
}
TaskLoggingHelper log;

public ManifestDocument (string templateFilename, TaskLoggingHelper log) : base ()
Expand Down Expand Up @@ -839,11 +854,40 @@ public void SetAbi (string abi)
int code = 1;
if (!string.IsNullOrEmpty (VersionCode)) {
code = Convert.ToInt32 (VersionCode);
if (code > 0xffff || code < 0)
throw new ArgumentOutOfRangeException ("VersionCode", "VersionCode is outside 0, 65535 interval");
if (code > maxVersionCode || code < 0)
throw new ArgumentOutOfRangeException ("VersionCode", $"VersionCode is outside 0, {maxVersionCode} interval");
}
code |= GetAbiCode (abi) << 16;
VersionCode = code.ToString ();
}

public void CalculateVersionCode (string currentAbi, string versionCodePattern, string versionCodeProperties)
{
var regex = new Regex ("\\{(?<key>([A-Za-z]*)):?0*[\\}]");
var kvp = new Dictionary<string, int> ();
foreach (var item in versionCodeProperties?.Split (new char [] { ';' }) ?? new string [0]) {
var a = item.Split (new char [] { '=' });
kvp.Add (a [0], int.Parse (a [1]));
}
if (!kvp.ContainsKey ("abi"))
kvp.Add ("abi", GetAbiCode (currentAbi));
if (!kvp.ContainsKey ("versionCode"))
kvp.Add ("versionCode", int.Parse (VersionCode));
if (!kvp.ContainsKey ("minSDK")) {
kvp.Add ("minSDK", int.Parse (MinimumSdk));
}
var versionCode = String.Empty;
foreach (Match match in regex.Matches (versionCodePattern)) {
var key = match.Groups ["key"].Value;
var format = match.Value.Replace (key, "0");
if (!kvp.ContainsKey (key))
continue;
versionCode += string.Format (format, kvp [key]);
}
var code = Convert.ToInt32 (versionCode);
if (code > maxVersionCode || code < 0)
throw new ArgumentOutOfRangeException ("VersionCode", $"VersionCode {code} is outside 0, {maxVersionCode} interval");
VersionCode = versionCode;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,9 @@ because xbuild doesn't support framework reference assemblies.
CreatePackagePerAbi="$(AndroidCreatePackagePerAbi)"
YieldDuringToolExecution="$(YieldDuringToolExecution)"
ExplicitCrunch="$(AndroidExplicitCrunch)"
VersionCodePattern="$(AndroidVersionCodePattern)"
VersionCodeProperties="$(AndroidVersionCodeProperties)"
AndroidSdkPlatform="$(_AndroidApiLevel)"
/>
<Touch Files="$(_PackagedResources)" />
<!-- LibraryProjectJars must not be used for aapt in BuildApk*, or it will *bundle* the jar! -->
Expand Down

0 comments on commit c1d8e33

Please sign in to comment.