diff --git a/Documentation/guides/BuildProcess.md b/Documentation/guides/BuildProcess.md index f8f32090fa0..4fae4caa383 100644 --- a/Documentation/guides/BuildProcess.md +++ b/Documentation/guides/BuildProcess.md @@ -624,7 +624,7 @@ when packaing Release applications. allows the developer to define custom items to use with the `AndroidVersionCodePattern`. They are in the form of a `key=value` pair. All items in the `value` should be integer values. For - example: `screen=23;target=$(_SupportedApiLevel)`. As you can see + example: `screen=23;target=$(_AndroidApiLevel)`. As you can see you can make use of existing or custom MSBuild properties in the string. diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs index 85481b95e24..a6b7620425d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs @@ -40,9 +40,6 @@ public class ResolveAndroidTooling : Task [Output] public string AndroidApiLevelName { get; set; } - [Output] - public string SupportedApiLevel { get; set; } - [Output] public string AndroidSdkBuildToolsPath { get; set; } @@ -208,7 +205,6 @@ public override bool Execute () Log.LogDebugMessage ($" {nameof (TargetFrameworkVersion)}: {TargetFrameworkVersion}"); Log.LogDebugMessage ($" {nameof (AndroidApiLevel)}: {AndroidApiLevel}"); Log.LogDebugMessage ($" {nameof (AndroidApiLevelName)}: {AndroidApiLevelName}"); - Log.LogDebugMessage ($" {nameof (SupportedApiLevel)}: {SupportedApiLevel}"); Log.LogDebugMessage ($" {nameof (AndroidSdkBuildToolsPath)}: {AndroidSdkBuildToolsPath}"); Log.LogDebugMessage ($" {nameof (AndroidSdkBuildToolsBinPath)}: {AndroidSdkBuildToolsBinPath}"); Log.LogDebugMessage ($" {nameof (ZipAlignPath)}: {ZipAlignPath}"); @@ -272,24 +268,36 @@ bool ValidateApiLevels () (string.IsNullOrWhiteSpace (AndroidApiLevel) && string.IsNullOrWhiteSpace (TargetFrameworkVersion)); if (UseLatestAndroidPlatformSdk) { - AndroidApiLevel = GetMaxInstalledApiLevel ().ToString (); - SupportedApiLevel = GetMaxStableApiLevel ().ToString (); - int maxInstalled, maxSupported = 0; - if (int.TryParse (AndroidApiLevel, out maxInstalled) && int.TryParse (SupportedApiLevel, out maxSupported) && maxInstalled > maxSupported) { - Log.LogDebugMessage ($"API Level {AndroidApiLevel} is greater than the maximum supported API level of {SupportedApiLevel}. " + + int maxInstalled = GetMaxInstalledApiLevel (); + int maxSupported = GetMaxStableApiLevel (); + AndroidApiLevel = maxInstalled.ToString (); + if (maxInstalled > maxSupported) { + Log.LogDebugMessage ($"API Level {maxInstalled} is greater than the maximum supported API level of {maxSupported}. " + "Support for this API will be added in a future release."); - AndroidApiLevel = SupportedApiLevel; } if (!string.IsNullOrWhiteSpace (TargetFrameworkVersion)) { var userSelected = MonoAndroidHelper.SupportedVersions.GetApiLevelFromFrameworkVersion (TargetFrameworkVersion); // overwrite using user version only if it is // above the maxStableApi and a valid apiLevel. if (userSelected != null && userSelected > maxSupported && userSelected <= maxInstalled) { + maxInstalled = + maxSupported = userSelected.Value; AndroidApiLevel = userSelected.ToString (); - SupportedApiLevel = userSelected.ToString (); } } - TargetFrameworkVersion = GetTargetFrameworkVersionFromApiLevel (); + + for (int apiLevel = maxSupported; apiLevel >= MonoAndroidHelper.SupportedVersions.MinStableVersion.ApiLevel; apiLevel--) { + var id = MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (apiLevel); + var apiPlatformDir = MonoAndroidHelper.AndroidSdk.TryGetPlatformDirectoryFromApiLevel (id, MonoAndroidHelper.SupportedVersions); + if (apiPlatformDir != null && Directory.Exists (apiPlatformDir)) { + var targetFramework = MonoAndroidHelper.SupportedVersions.GetFrameworkVersionFromId (id); + if (targetFramework != null && MonoAndroidHelper.SupportedVersions.InstalledBindingVersions.Any (b => b.FrameworkVersion == targetFramework)) { + AndroidApiLevel = apiLevel.ToString (); + TargetFrameworkVersion = targetFramework; + break; + } + } + } return TargetFrameworkVersion != null; } @@ -303,13 +311,11 @@ bool ValidateApiLevels () return false; } AndroidApiLevel = MonoAndroidHelper.SupportedVersions.GetApiLevelFromId (id).ToString (); - SupportedApiLevel = AndroidApiLevel; return true; } if (!string.IsNullOrWhiteSpace (AndroidApiLevel)) { AndroidApiLevel = AndroidApiLevel.Trim (); - SupportedApiLevel = GetMaxSupportedApiLevel (AndroidApiLevel); TargetFrameworkVersion = GetTargetFrameworkVersionFromApiLevel (); return TargetFrameworkVersion != null; } @@ -369,8 +375,7 @@ string GetMaxSupportedApiLevel (string apiLevel) string GetTargetFrameworkVersionFromApiLevel () { - string targetFramework = MonoAndroidHelper.SupportedVersions.GetFrameworkVersionFromId (SupportedApiLevel) ?? - MonoAndroidHelper.SupportedVersions.GetFrameworkVersionFromId (AndroidApiLevel); + string targetFramework = MonoAndroidHelper.SupportedVersions.GetFrameworkVersionFromId (AndroidApiLevel); if (targetFramework != null) return targetFramework; Log.LogCodedError ("XA0000", diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ResolveSdksTaskTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ResolveSdksTaskTests.cs index 4e6ebf9decf..8f1fcda546f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ResolveSdksTaskTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ResolveSdksTaskTests.cs @@ -190,7 +190,7 @@ public void UseLatestAndroidSdk (string buildtools, string jdk, ApiInfo[] apis, SequencePointsMode = "None", }; Assert.AreEqual (expectedTaskResult, resolveSdks.Execute () && validateJavaVersion.Execute () && androidTooling.Execute (), $"Tasks should have {(expectedTaskResult ? "succeeded" : "failed" )}."); - Assert.AreEqual (expectedTargetFramework, androidTooling.TargetFrameworkVersion, $"TargetFrameworkVersion should be {expectedTargetFramework} but was {targetFrameworkVersion}"); + Assert.AreEqual (expectedTargetFramework, androidTooling.TargetFrameworkVersion, $"TargetFrameworkVersion should be {expectedTargetFramework} but was {androidTooling.TargetFrameworkVersion}"); if (!string.IsNullOrWhiteSpace (expectedError)) { Assert.AreEqual (1, errors.Count (), "An error should have been raised."); Assert.AreEqual (expectedError, errors [0].Code, $"Expected error code {expectedError} but found {errors [0].Code}"); @@ -240,7 +240,7 @@ public void ResolveSdkTiming () UseLatestAndroidPlatformSdk = false, AotAssemblies = false, SequencePointsMode = "None", - };; + }; var start = DateTime.UtcNow; Assert.IsTrue (resolveSdks.Execute (), "ResolveSdks should succeed!"); Assert.IsTrue (validateJavaVersion.Execute (), "ValidateJavaVersion should succeed!"); @@ -250,7 +250,6 @@ public void ResolveSdkTiming () Assert.AreEqual (androidTooling.AndroidApiLevel, "26", "AndroidApiLevel should be 26"); Assert.AreEqual (androidTooling.TargetFrameworkVersion, "v8.0", "TargetFrameworkVersion should be v8.0"); Assert.AreEqual (androidTooling.AndroidApiLevelName, "26", "AndroidApiLevelName should be 26"); - Assert.AreEqual (androidTooling.SupportedApiLevel, "26", "SupportedApiLevel should be 26"); Assert.NotNull (resolveSdks.ReferenceAssemblyPaths, "ReferenceAssemblyPaths should not be null."); Assert.AreEqual (resolveSdks.ReferenceAssemblyPaths.Length, 1, "ReferenceAssemblyPaths should have 1 entry."); Assert.AreEqual (resolveSdks.ReferenceAssemblyPaths[0], Path.Combine (referencePath, "MonoAndroid"), $"ReferenceAssemblyPaths should be {Path.Combine (referencePath, "MonoAndroid")}."); @@ -278,5 +277,135 @@ public void ResolveSdkTiming () Assert.AreEqual (validateJavaVersion.MinimumRequiredJdkVersion, "1.8", "MinimumRequiredJdkVersion should be 1.8"); Directory.Delete (Path.Combine (Root, path), recursive: true); } + + static object [] TargetFrameworkPairingParameters = new [] { + //We support 28, but only 27 is installed + new object [] { + "Older API Installed", //description + // androidSdks + new [] { + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + }, + // targetFrameworks + new ApiInfo [] { + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = true }, + }, + null, //userSelected + "27", //androidApiLevel + "27", //androidApiLevelName + "v8.1", //targetFrameworkVersion + }, + //28 is installed but we only support 27 + new object [] { + "Newer API Installed", //description + // androidSdks + new [] { + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = true }, + }, + // targetFrameworks + new ApiInfo [] { + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + }, + null, //userSelected + "27", //androidApiLevel + "27", //androidApiLevelName + "v8.1", //targetFrameworkVersion + }, + //A paired downgrade to API 26 + new object [] { + "Paired Downgrade", //description + // androidSdks + new [] { + new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + }, + // targetFrameworks + new ApiInfo [] { + new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = true }, + }, + null, //userSelected + "26", //androidApiLevel + "26", //androidApiLevelName + "v8.0", //targetFrameworkVersion + }, + //A new API level 28 is not stable yet + new object [] { + "New Unstable API", //description + // androidSdks + new [] { + new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = false }, + }, + // targetFrameworks + new ApiInfo [] { + new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = false }, + }, + null, //userSelected + "27", //androidApiLevel + "27", //androidApiLevelName + "v8.1", //targetFrameworkVersion + }, + //User selected a new API level 28 is not stable yet + new object [] { + "User Selected Unstable API", //description + // androidSdks + new [] { + new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = false }, + }, + // targetFrameworks + new ApiInfo [] { + new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true }, + new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true }, + new ApiInfo () { Id = "28", Level = 28, Name = "P", FrameworkVersion = "v9.0", Stable = false }, + }, + "v9.0", //userSelected + "28", //androidApiLevel + "28", //androidApiLevelName + "v9.0", //targetFrameworkVersion + }, + }; + + [Test] + [TestCaseSource (nameof (TargetFrameworkPairingParameters))] + public void TargetFrameworkPairing (string description, ApiInfo[] androidSdk, ApiInfo[] targetFrameworks, string userSelected, string androidApiLevel, string androidApiLevelName, string targetFrameworkVersion) + { + var path = Path.Combine ("temp", $"{nameof (TargetFrameworkPairing)}_{description}"); + var androidSdkPath = CreateFauxAndroidSdkDirectory (Path.Combine (path, "android-sdk"), "26.0.3", androidSdk); + string javaExe = string.Empty; + string javacExe; + var javaPath = CreateFauxJavaSdkDirectory (Path.Combine (path, "jdk"), "1.8.0", out javaExe, out javacExe); + var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), targetFrameworks); + IBuildEngine engine = new MockBuildEngine (TestContext.Out); + var resolveSdks = new ResolveSdks { + BuildEngine = engine, + AndroidSdkPath = androidSdkPath, + AndroidNdkPath = androidSdkPath, + JavaSdkPath = javaPath, + ReferenceAssemblyPaths = new [] { + Path.Combine (referencePath, "MonoAndroid"), + }, + }; + var androidTooling = new ResolveAndroidTooling { + BuildEngine = engine, + AndroidSdkPath = androidSdkPath, + AndroidNdkPath = androidSdkPath, + UseLatestAndroidPlatformSdk = true, + TargetFrameworkVersion = userSelected, + }; + Assert.IsTrue (resolveSdks.Execute (), "ResolveSdks should succeed!"); + Assert.IsTrue (androidTooling.Execute (), "ResolveAndroidTooling should succeed!"); + Assert.AreEqual (androidApiLevel, androidTooling.AndroidApiLevel, $"AndroidApiLevel should be {androidApiLevel}"); + Assert.AreEqual (androidApiLevelName, androidTooling.AndroidApiLevelName, $"AndroidApiLevelName should be {androidApiLevelName}"); + Assert.AreEqual (targetFrameworkVersion, androidTooling.TargetFrameworkVersion, $"TargetFrameworkVersion should be {targetFrameworkVersion}"); + Directory.Delete (Path.Combine (Root, path), recursive: true); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 62709fb6e58..da364b9e28f 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -726,7 +726,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - @@ -1008,7 +1007,7 @@ because xbuild doesn't support framework reference assemblies. - +