Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Xamarin.Android.Build.Tasks] Prevent $(AndroidUseLatestPlatformSdk) from using Unstable APIs #1228

Merged
merged 15 commits into from
Feb 23, 2018
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of this make sense. :-(

The use case is this: "soon", Google will presumably release API-P in alpha form, for release as API-28 this fall.

When we provide a binding, it will probably be $(TargetFrameworkVersion)=v8.99.

Assuming the user has v8.99 installed -- how that's done is "elsewhere" -- the user should be able to opt in to using it by setting $(TargetFrameworkVersion)=v8.99.

Thus, !string.IsNullOrWhiteSpace (TargetFrameworkVersion) (line 433) will be true.

userSelected (line 434), meanwhile, will be null, as there is no API level for unstable APIs. Relatedly, MonoAndroidHelper.SupportedVersions.GetIdFromFrameworkVersion("v8.99") will return "P".

Similarly, maxInstalled comes from int.TryParse() comes from GetMaxInstalledApiLevel(), which only looks at values which can be parsed into an int. As such, API-P will not be present or used, meaning -- for our current exercise -- AndroidApiLevel is ~useless. Consequently, maxInstalled is likewise useless.

In short, I don't see how this will do what we want it to do. :-(

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