diff --git a/Documentation/build_process.md b/Documentation/build_process.md
index 48a26dc6a72..f038b9e3e84 100644
--- a/Documentation/build_process.md
+++ b/Documentation/build_process.md
@@ -10,10 +10,10 @@ dateupdated: 2017-06-22
The Xamarin.Android build process is responsible for gluing everything
together:
-[generating `Resource.designer.cs`](/guides/android/advanced_topics/api_design#Resources),
+[generating `Resource.designer.cs`](https://developer.xamarin.com/guides/android/advanced_topics/api_design#Resources),
supporting the `AndroidAsset`, `AndroidResource`, and other
[build actions](#Build_Actions), generating
-[Android-callable wrappers](/guides/android/advanced_topics/java_integration_overview/android_callable_wrappers),
+[Android-callable wrappers](https://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/android_callable_wrappers),
and generating a `.apk` for execution on Android devices.
@@ -247,7 +247,7 @@ when packaing Release applications.
Added in Xamarin.Android 6.1.
- **AndroidHttpClientHandlerType** – Allow setting the value of the
- [`XA_HTTP_CLIENT_HANDLER_TYPE` environment variable](/guides/android/advanced_topics/environment/#XA_HTTP_CLIENT_HANDLER_TYPE).
+ [`XA_HTTP_CLIENT_HANDLER_TYPE` environment variable](https://developer.xamarin.com/guides/android/advanced_topics/environment/#XA_HTTP_CLIENT_HANDLER_TYPE).
This value will not override an explicitly specified
`XA_HTTP_CLIENT_HANDLER_TYPE` value. An
`XA_HTTP_CLIENT_HANDLER_TYPE` environment variable value specified
@@ -278,7 +278,7 @@ when packaing Release applications.
**Experimental**. Added in Xamarin.Android 7.1.
- **AndroidLinkMode** – Specifies which type of
- [linking](/guides/android/advanced_topics/linking/) should be
+ [linking](https://developer.xamarin.com/guides/android/advanced_topics/linking/) should be
performed on assemblies contained within the Android package. Only
used in Android Application projects. The default value is
*SdkOnly*. Valid values are:
@@ -310,7 +310,7 @@ when packaing Release applications.
- **AndroidManifest** – Specifies a filename to use as the
template for the app's
- [`AndroidManifest.xml`](/guides/android/advanced_topics/working_with_androidmanifest.xml/).
+ [`AndroidManifest.xml`](https://developer.xamarin.com/guides/android/advanced_topics/working_with_androidmanifest.xml/).
During the build, any other necessary values will be merged into to
produce the actual `AndroidManifest.xml`.
The `$(AndroidManifest)` must contain the package name in the `/manifest/@package` attribute.
@@ -501,6 +501,71 @@ when packaing Release applications.
Added in Xamarin.Android 7.1.
+
+- **AndroidVersionCodePattern** – A string property which allows
+ the developer to customize the `versionCode` in the manifest.
+ 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 produce a versionCode of `1123` when `$(AndroidCreatePackagePerAbi)`
+ is True, otherwise will produce a value of 123.
+ If `abi` is `x86_64` and `versionCode` in the manifest
+ is `44`. This will produce `544` when `$(AndroidCreatePackagePerAbi)`
+ is True, otherwise will produce a value of `44`.
+
+ If we include a left padding format string
+
+ {abi}{versionCode:0000}
+
+ it would produde `50044` because we are left padding the `versionCode`
+ with `0`. Alternatively you can use the decimal padding such as
+
+ {abi}{versionCode:D4}
+
+ which does the same as the previous example.
+
+ Only '0' and 'Dx' padding format strings are supported since the value
+ MUST be an integer.
+
+ Pre defined key items
+
+ - **abi** – Inserts the targetted abi for the app
+ - 1 – `armeabi`
+ - 2 – `armeabi-v7a`
+ - 3 – `x86`
+ - 4 – `arm64-v8a`
+ - 5 – `x86_64`
+
+ - **minSDK** – Inserts the minimum supported Sdk
+ value from the `AndroidManifest.xml` or `11` if none is
+ defined.
+
+ - **versionCode** – 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** – 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)
+
+ As you can see you can make use of existing or custom MSBuild properties
+ in the string.
+
+ Added in Xamarin.Android 7.2.
+
## Binding Project Build Properties
The following MSBuild properties are used with
@@ -675,7 +740,7 @@ within the project and control how the file is processed.
## AndroidEnvironment
Files with a Build action of `AndroidEnvironment` are used
-to [initialize environment variables and system properties during process startup](/guides/android/advanced_topics/environment/).
+to [initialize environment variables and system properties during process startup](https://developer.xamarin.com/guides/android/advanced_topics/environment/).
The `AndroidEnvironment` Build action may be applied to
multiple files, and they will be evaluated in no particular order (so don't
specify the same environment variable or system property in multiple
@@ -748,7 +813,7 @@ example:
## AndroidNativeLibrary
-[Native libraries](/guides/android/advanced_topics/cpu_architecture/#Android_Native_Library_Installation)
+[Native libraries](https://developer.xamarin.com/guides/android/advanced_topics/cpu_architecture/#Android_Native_Library_Installation)
are added to the build by setting their Build action to
`AndroidNativeLibrary`.
@@ -787,7 +852,7 @@ Build action will result in a `XA0101` warning.
## LinkDescription
Files with a *LinkDescription* build action are used to
-[control linker behavior](/guides/cross-platform/advanced/custom_linking/).
+[control linker behavior](https://developer.xamarin.com/guides/cross-platform/advanced/custom_linking/).
@@ -797,7 +862,7 @@ Files with a *LinkDescription* build action are used to
Files with a *ProguardConfiguration* build action contain options which
are used to control `proguard` behavior. For more information about
this build action, see
-[ProGuard](/guides/android/deployment,_testing,_and_metrics/proguard/).
+[ProGuard](https://developer.xamarin.com/guides/android/deployment,_testing,_and_metrics/proguard/).
These files are ignored unless the `$(EnableProguard)` MSBuild property
is `True`.
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs
index de9ff551bd8..54215c603c7 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs
@@ -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 resource_name_case_map = new Dictionary ();
bool ManifestIsUpToDate (string manifestFile)
@@ -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);
@@ -244,8 +259,15 @@ 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);
+ } else if (!string.IsNullOrEmpty (VersionCodePattern)) {
+ manifest.CalculateVersionCode (null, VersionCodePattern, VersionCodeProperties);
+ }
manifest.ApplicationName = ApplicationName;
manifest.Save (manifestFile);
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs
index ea6a91dd4e7..99e5003c49d 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using NUnit.Framework;
using Xamarin.ProjectTools;
@@ -264,6 +264,144 @@ public void DirectBootAwareAttribute ()
}
}
+ static object [] VersionCodeTestSource = new object [] {
+ new object[] {
+ /* seperateApk */ false,
+ /* abis */ "armeabi-v7a",
+ /* versionCode */ "123",
+ /* pattern */ null,
+ /* props */ null,
+ /* shouldBuild */ true,
+ /* expected */ "123",
+ },
+ new object[] {
+ /* seperateApk */ false,
+ /* abis */ "armeabi-v7a",
+ /* versionCode */ "123",
+ /* pattern */ "{abi}{versionCode}",
+ /* props */ null,
+ /* shouldBuild */ true,
+ /* expected */ "123",
+ },
+ new object[] {
+ /* seperateApk */ false,
+ /* abis */ "armeabi-v7a",
+ /* versionCode */ "1",
+ /* pattern */ "{abi}{versionCode}",
+ /* props */ "versionCode=123",
+ /* shouldBuild */ true,
+ /* expected */ "123",
+ },
+ new object[] {
+ /* seperateApk */ false,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "123",
+ /* pattern */ "{abi}{versionCode}",
+ /* props */ null,
+ /* shouldBuild */ true,
+ /* expected */ "123",
+ },
+ new object[] {
+ /* seperateApk */ true,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "123",
+ /* pattern */ null,
+ /* props */ null,
+ /* shouldBuild */ true,
+ /* expected */ "131195;196731",
+ },
+ new object[] {
+ /* seperateApk */ true,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "123",
+ /* pattern */ "{abi}{versionCode}",
+ /* props */ null,
+ /* shouldBuild */ true,
+ /* expected */ "2123;3123",
+ },
+ new object[] {
+ /* seperateApk */ true,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "12",
+ /* pattern */ "{abi}{minSDK:00}{versionCode:000}",
+ /* props */ null,
+ /* shouldBuild */ true,
+ /* expected */ "211012;311012",
+ },
+ new object[] {
+ /* seperateApk */ true,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "12",
+ /* pattern */ "{abi}{minSDK:00}{screen}{versionCode:000}",
+ /* props */ "screen=24",
+ /* shouldBuild */ true,
+ /* expected */ "21124012;31124012",
+ },
+ new object[] {
+ /* seperateApk */ true,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "12",
+ /* pattern */ "{abi}{minSDK:00}{screen}{foo:0}{versionCode:000}",
+ /* props */ "screen=24;foo=$(Foo)",
+ /* shouldBuild */ true,
+ /* expected */ "211241012;311241012",
+ },
+ new object[] {
+ /* seperateApk */ true,
+ /* abis */ "armeabi-v7a;x86",
+ /* versionCode */ "12",
+ /* pattern */ "{abi}{minSDK:00}{screen}{foo:00}{versionCode:000}",
+ /* props */ "screen=24;foo=$(Foo)",
+ /* shouldBuild */ false,
+ /* expected */ "2112401012;3112401012",
+ },
+ };
+
+ [Test]
+ [TestCaseSource("VersionCodeTestSource")]
+ public void VersionCodeTests (bool seperateApk, string abis, string versionCode, string versionCodePattern, string versionCodeProperties, bool shouldBuild, string expectedVersionCode)
+ {
+ var proj = new XamarinAndroidApplicationProject () {
+ IsRelease = true,
+ };
+ proj.SetProperty ("Foo", "1");
+ 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.ThrowOnBuildFailure = false;
+ Assert.AreEqual (shouldBuild, builder.Build (proj), shouldBuild ? "Build should have succeeded." : "Build should have failed.");
+ if (!shouldBuild)
+ return;
+ var abiItems = seperateApk ? abis.Split (';') : new string[1];
+ var expectedItems = expectedVersionCode.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", "manifest", "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 ()
{
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
index 7bac3b1e53e..b05e022ebf5 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
@@ -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;
@@ -68,6 +70,17 @@ public string VersionCode {
doc.Root.SetAttributeValue (androidNs + "versionCode", value);
}
}
+ public string GetMinimumSdk () {
+ var minAttr = doc.Root.Element ("uses-sdk")?.Attribute (androidNs + "minSdkVersion");
+ if (minAttr == null) {
+ int minSdkVersion;
+ if (!int.TryParse (SdkVersionName, out minSdkVersion))
+ minSdkVersion = 11;
+ return Math.Min (minSdkVersion, 11).ToString ();
+ }
+ return minAttr.Value;
+ }
+
TaskLoggingHelper log;
public ManifestDocument (string templateFilename, TaskLoggingHelper log) : base ()
@@ -839,11 +852,45 @@ 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 ("\\{(?([A-Za-z]+)):?[D0-9]*[\\}]");
+ var kvp = new Dictionary ();
+ foreach (var item in versionCodeProperties?.Split (new char [] { ';', ':' }) ?? new string [0]) {
+ var keyValue = item.Split (new char [] { '=' });
+ int val;
+ if (!int.TryParse (keyValue [1], out val))
+ continue;
+ kvp.Add (keyValue [0], val);
+ }
+ if (!kvp.ContainsKey ("abi") && !string.IsNullOrEmpty (currentAbi))
+ kvp.Add ("abi", GetAbiCode (currentAbi));
+ if (!kvp.ContainsKey ("versionCode"))
+ kvp.Add ("versionCode", int.Parse (VersionCode));
+ if (!kvp.ContainsKey ("minSDK")) {
+ kvp.Add ("minSDK", int.Parse (GetMinimumSdk ()));
+ }
+ 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]);
+ }
+ int code;
+ if (!int.TryParse (versionCode, out code))
+ throw new ArgumentOutOfRangeException ("VersionCode", $"VersionCode {versionCode} is invalid. It must be an integer value.");
+ if (code > maxVersionCode || code < 0)
+ throw new ArgumentOutOfRangeException ("VersionCode", $"VersionCode {code} is outside 0, {maxVersionCode} interval");
+ VersionCode = versionCode;
+ }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index 68050ff759f..78db70caaa3 100755
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -1810,6 +1810,9 @@ because xbuild doesn't support framework reference assemblies.
CreatePackagePerAbi="$(AndroidCreatePackagePerAbi)"
YieldDuringToolExecution="$(YieldDuringToolExecution)"
ExplicitCrunch="$(AndroidExplicitCrunch)"
+ VersionCodePattern="$(AndroidVersionCodePattern)"
+ VersionCodeProperties="$(AndroidVersionCodeProperties)"
+ AndroidSdkPlatform="$(_AndroidApiLevel)"
/>