Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] $(AndroidUseLatestPlatformSdk) uses S…
Browse files Browse the repository at this point in the history
…TABLE levels (#1228)

Fixes: #1221

Currently when a user sets `$(AndroidUseLatestPlatformSdk)` it will
automatically use the maximum known API level. This is not always a
stable ApiLevel, e.g. when a preview API level has been bound, and
the binding has been installed.

Users will generally want to use the maximum *stable* API level when
`$(AndroidUseLatestPlatformSdk)` is True.

Alters things so that we use the maximum Stable API level when
`$(AndroidUseLatestPlatformSdk)` is True.

However, allow users to use unstable API levels if desired. This can
be achieved by setting `$(TargetFrameworkVersion)` to the desired
unstable framework version. (For example, when API-O was in preview,
this would be `v7.99`. When API-P is presumably announced, it will be
bound as `v8.99`.)

This will require a *manual* change to the `.csproj`, as we do not
anticipate the IDEs showing unstable API levels (though this could
plausibly be done with sufficient warnings...).
  • Loading branch information
dellis1972 authored and jonpryor committed Feb 23, 2018
1 parent 353b79c commit 0780e27
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 18 deletions.
25 changes: 19 additions & 6 deletions src/Xamarin.Android.Build.Tasks/Tasks/ResolveSdksTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,13 +423,21 @@ bool ValidateApiLevels ()

if (UseLatestAndroidPlatformSdk) {
AndroidApiLevel = GetMaxInstalledApiLevel ().ToString ();
SupportedApiLevel = GetMaxSupportedApiLevel (AndroidApiLevel);
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}. " +
"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) {
SupportedApiLevel = userSelected.ToString ();
}
}
TargetFrameworkVersion = GetTargetFrameworkVersionFromApiLevel ();
return TargetFrameworkVersion != null;
}
Expand Down Expand Up @@ -461,17 +469,17 @@ bool ValidateApiLevels ()
int GetMaxInstalledApiLevel ()
{
string platformsDir = Path.Combine (AndroidSdkPath, "platforms");
var apiLevels = Directory.EnumerateDirectories (platformsDir)
var apiIds = Directory.EnumerateDirectories (platformsDir)
.Select (platformDir => Path.GetFileName (platformDir))
.Where (dir => dir.StartsWith ("android-", StringComparison.OrdinalIgnoreCase))
.Select (dir => dir.Substring ("android-".Length))
.Select (apiName => MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (apiName));
int maxApiLevel = int.MinValue;
foreach (var level in apiLevels) {
int v;
if (!int.TryParse (level, NumberStyles.Integer, CultureInfo.InvariantCulture, out v))
foreach (var id in apiIds) {
int? v = MonoAndroidHelper.SupportedVersions.GetApiLevelFromId (id);
if (!v.HasValue)
continue;
maxApiLevel = Math.Max (maxApiLevel, v);
maxApiLevel = Math.Max (maxApiLevel, v.Value);
}
if (maxApiLevel < 0)
Log.LogCodedError ("XA5300",
Expand All @@ -480,6 +488,11 @@ int GetMaxInstalledApiLevel ()
return maxApiLevel;
}

int GetMaxStableApiLevel ()
{
return MonoAndroidHelper.SupportedVersions.MaxStableVersion.ApiLevel;
}

string GetMaxSupportedApiLevel (string apiLevel)
{
int level = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2228,6 +2228,64 @@ public void IfAndroidJarDoesNotExistThrowXA5207 ()
Directory.Delete (AndroidSdkDirectory, recursive: true);
}

[Test]
public void ValidateUseLatestAndroid ()
{
var path = Path.Combine ("temp", TestName);
var androidSdkPath = CreateFauxAndroidSdkDirectory (Path.Combine (path, "android-sdk"),
"23.0.6", minApiLevel: 10, maxApiLevel: 28, alphaApiLevel: "P");
var referencesPath = CreateFauxReferencesDirectory (Path.Combine (path, "xbuild-frameworks"), new ApiInfo [] {
new ApiInfo () { Id = "23", Level = 23, Name = "Marshmallow", FrameworkVersion = "v6.0", Stable = true },
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 = "P", Level = 28, Name = "P", FrameworkVersion="v8.99", Stable = false },
});
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
TargetFrameworkVersion = "v8.0",
UseLatestPlatformSdk = false,
};
var parameters = new string [] {
$"TargetFrameworkRootPath={referencesPath}",
$"AndroidSdkDirectory={androidSdkPath}",
};
var envVar = new Dictionary<string, string> {
{ "XBUILD_FRAMEWORK_FOLDERS_PATH", referencesPath },
};
using (var builder = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
builder.ThrowOnBuildFailure = false;
builder.Target = "_SetLatestTargetFrameworkVersion";
Assert.True (builder.Build (proj, parameters: parameters, environmentVariables: envVar),
string.Format ("First Build should have succeeded"));
Assert.IsTrue (builder.LastBuildOutput.ContainsOccurances ("TargetFrameworkVersion: v8.0", 2), "TargetFrameworkVersion should be v8.0");

proj.TargetFrameworkVersion = "v8.0";
Assert.True (builder.Build (proj, parameters: parameters, environmentVariables: envVar),
string.Format ("Second Build should have succeeded"));
Assert.IsTrue (builder.LastBuildOutput.ContainsOccurances ("TargetFrameworkVersion: v8.0", 2), "TargetFrameworkVersion should be v8.0");

proj.UseLatestPlatformSdk = true;
proj.TargetFrameworkVersion = "v8.1";
Assert.True (builder.Build (proj, parameters: parameters, environmentVariables: envVar),
string.Format ("Third Build should have succeeded"));
Assert.IsTrue (builder.LastBuildOutput.ContainsOccurances ("TargetFrameworkVersion: v8.1", 2), "TargetFrameworkVersion should be v8.1");

proj.UseLatestPlatformSdk = true;
proj.TargetFrameworkVersion = "v8.99";
Assert.True (builder.Build (proj, parameters: parameters, environmentVariables: envVar),
string.Format ("Third Build should have succeeded"));
Assert.IsTrue (builder.LastBuildOutput.ContainsOccurances ("TargetFrameworkVersion: v8.99", 2), "TargetFrameworkVersion should be v8.99");

proj.UseLatestPlatformSdk = true;
proj.TargetFrameworkVersion = "v6.0";
Assert.True (builder.Build (proj, parameters: parameters, environmentVariables: envVar),
string.Format ("Forth Build should have succeeded"));
Assert.IsTrue (builder.LastBuildOutput.ContainsOccurances ("TargetFrameworkVersion: v6.0", 1), "TargetFrameworkVersion should initially be v6.0");
Assert.IsTrue (builder.LastBuildOutput.ContainsOccurances ("TargetFrameworkVersion: v8.1", 1), "TargetFrameworkVersion should be v8.1");
}
Directory.Delete (referencesPath, recursive: true);
}

[Test]
[NonParallelizable]
public void BuildAMassiveApp()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void CheckNdkBundle ([Values(true, false)] bool ndkRequred)
{
var path = Path.Combine ("temp", TestName);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new ApiInfo [] {
new ApiInfo () { Id = 26, Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
});
MonoAndroidHelper.RefreshSupportedVersions (new string [] { referencePath });
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
Expand Down Expand Up @@ -59,7 +59,7 @@ public void ManifestFileDoesNotExist ()
{
var path = Path.Combine ("temp", TestName);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new ApiInfo[] {
new ApiInfo () { Id = 26, Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
} );
MonoAndroidHelper.RefreshSupportedVersions (new string [] { referencePath });
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
Expand Down Expand Up @@ -94,7 +94,7 @@ public void ManifestFileExists ()
{
var path = Path.Combine (Root, "temp", TestName);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new ApiInfo[] {
new ApiInfo () { Id = 26, Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
new ApiInfo () { Id = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
} );
MonoAndroidHelper.RefreshSupportedVersions (new string [] { referencePath });
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Xamarin.ProjectTools;
using System.IO;
Expand All @@ -11,8 +12,169 @@
namespace Xamarin.Android.Build.Tests {

[TestFixture]
[Parallelizable (ParallelScope.Children)]
[Parallelizable (ParallelScope.Self)]
public class ResolveSdksTaskTests : BaseTest {
#pragma warning disable 414

static ApiInfo [] apiInfoSelection = 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 = "P", Level = 28, Name = "P", FrameworkVersion = "v8.99", Stable = false },
};

static object [] UseLatestAndroidSdkTestCases = new object [] {
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ true,
/* targetFrameworkVersion */ "v8.99",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.99",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ true,
/* targetFrameworkVersion */ "v8.0",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.1",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ true,
/* targetFrameworkVersion */ "v8.1",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.1",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ true,
/* targetFrameworkVersion */ "v6.0",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.1",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ true,
/* targetFrameworkVersion */ null,
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.1",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ false,
/* targetFrameworkVersion */ "v8.99",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.99",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ false,
/* targetFrameworkVersion */ "v8.1",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.1",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ false,
/* targetFrameworkVersion */ "v8.0",
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.0",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ false,
/* targetFrameworkVersion */ null,
/* expectedTaskResult */ true,
/* expectedTargetFramework */ "v8.1",
/* expectedError */ "",
/* expectedErrorMessage */ "",
},
new object[] {
/* buildtools */ "26.0.3",
/* jdk */ "1.8.0",
/* apis*/ apiInfoSelection,
/* useLatestAndroidSdk */ false,
/* targetFrameworkVersion */ "v6.0",
/* expectedTaskResult */ false,
/* expectedTargetFramework */ "v6.0",
/* expectedError */ "XA0001",
/* expectedErrorMessage */ "Unsupported or invalid $(TargetFrameworkVersion) value of 'v6.0'. Please update your Project Options.",
},
};
#pragma warning restore 414
[Test]
[TestCaseSource(nameof(UseLatestAndroidSdkTestCases))]
public void UseLatestAndroidSdk (string buildtools, string jdk, ApiInfo[] apis, bool useLatestAndroidSdk, string targetFrameworkVersion, bool expectedTaskResult, string expectedTargetFramework, string expectedError = "", string expectedErrorMessage = "")
{
var path = Path.Combine ("temp", "UseLatestAndroidSdk_" + Guid.NewGuid ());
var androidSdkPath = CreateFauxAndroidSdkDirectory (Path.Combine (path, "android-sdk"), buildtools, minApiLevel: 26, maxApiLevel: 27, alphaApiLevel: "P");
string javaExe = string.Empty;
var javaPath = CreateFauxJavaSdkDirectory (Path.Combine (path, "jdk"), jdk, out javaExe);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), apis);
var errors = new List<BuildErrorEventArgs> ();
IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors);
var task = new ResolveSdks {
BuildEngine = engine
};
task.AndroidSdkPath = androidSdkPath;
task.AndroidNdkPath = androidSdkPath;
task.JavaSdkPath = javaPath;
task.TargetFrameworkVersion = targetFrameworkVersion;
task.AndroidSdkBuildToolsVersion = buildtools;
task.BuildingInsideVisualStudio = "true";
task.UseLatestAndroidPlatformSdk = useLatestAndroidSdk;
task.AotAssemblies = false;
task.LatestSupportedJavaVersion = "1.8.0";
task.MinimumSupportedJavaVersion = "1.7.0";
task.ReferenceAssemblyPaths = new string [] {
Path.Combine (referencePath, "MonoAndroid"),
};
task.CacheFile = Path.Combine (Root, path, "sdk.xml");
task.SequencePointsMode = "None";
task.JavaToolExe = javaExe;
Assert.AreEqual (expectedTaskResult, task.Execute (), $"Task should have {(expectedTaskResult ? "succeeded" : "failed" )}.");
Assert.AreEqual (expectedTargetFramework, task.TargetFrameworkVersion, $"TargetFrameworkVersion should be {expectedTargetFramework} but was {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}");
Assert.AreEqual (expectedErrorMessage, errors [0].Message, $"Expected error code {expectedErrorMessage} but found {errors [0].Message}");
}
Directory.Delete (Path.Combine (Root, path), recursive: true);
}

[Test]
public void ResolveSdkTiming ()
{
Expand All @@ -21,8 +183,8 @@ public void ResolveSdkTiming ()
string javaExe = string.Empty;
var javaPath = CreateFauxJavaSdkDirectory (Path.Combine (path, "jdk"), "1.8.0", out javaExe);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), 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 = "26", Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
new ApiInfo () { Id = "27", Level = 27, Name = "Oreo", FrameworkVersion = "v8.1", Stable = true },
});
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
var task = new ResolveSdks {
Expand Down Expand Up @@ -75,6 +237,7 @@ public void ResolveSdkTiming ()
Assert.AreEqual (task.AndroidUseApkSigner, false, "AndroidUseApkSigner should be false");
Assert.AreEqual (task.JdkVersion, "1.8.0", "JdkVersion should be 1.8.0");
Assert.AreEqual (task.MinimumRequiredJdkVersion, "1.8", "MinimumRequiredJdkVersion should be 1.8");
Directory.Delete (Path.Combine (Root, path), recursive: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ protected string RunProcess (string exe, string args) {
return result;
}

protected string CreateFauxAndroidSdkDirectory (string path, string buildToolsVersion, int minApiLevel = 10, int maxApiLevel = 26)
protected string CreateFauxAndroidSdkDirectory (string path, string buildToolsVersion, int minApiLevel = 10, int maxApiLevel = 26, string alphaApiLevel = "")
{
var androidSdkDirectory = Path.Combine (Root, path);
var androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools");
Expand All @@ -129,11 +129,16 @@ protected string CreateFauxAndroidSdkDirectory (string path, string buildToolsVe
Directory.CreateDirectory(dir);
File.WriteAllText (Path.Combine (dir, "android.jar"), "");
}
if (!string.IsNullOrEmpty (alphaApiLevel)) {
var dir = Path.Combine (androidSdkPlatformsPath, $"android-{alphaApiLevel}");
Directory.CreateDirectory (dir);
File.WriteAllText (Path.Combine (dir, "android.jar"), "");
}
return androidSdkDirectory;
}

public struct ApiInfo {
public int Id;
public string Id;
public int Level;
public string Name;
public string FrameworkVersion;
Expand All @@ -142,11 +147,12 @@ public struct ApiInfo {

protected string CreateFauxReferencesDirectory (string path, ApiInfo [] versions)
{

string referencesDirectory = Path.Combine (Root, path);
Directory.CreateDirectory (referencesDirectory);
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", "v1.0"));
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", "v1.0", "RedistList"));
File.WriteAllText (Path.Combine (referencesDirectory, "MonoAndroid", "v1.0", "mscorlib.dll"), "");
File.WriteAllText (Path.Combine (referencesDirectory, "MonoAndroid", "v1.0", "RedistList", "FrameworkList.xml"),
$"<FileList Redist=\"MonoAndroid\" Name=\"Xamarin.Android Base Class Libraries\"></FileList>");
foreach (var v in versions) {
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion));
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion, "RedistList"));
Expand Down
Loading

0 comments on commit 0780e27

Please sign in to comment.