Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Configuration.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
<!-- The min API level supported by non-Mono runtimes (CoreCLR/NativeAOT) -->
<AndroidMinimumNonMonoApiLevel Condition="'$(AndroidMinimumNonMonoApiLevel)' == ''">24</AndroidMinimumNonMonoApiLevel>
<!-- *Latest* *stable* API level binding that we support; used when building src/Xamarin.Android.Build.Tasks -->
<AndroidLatestStableApiLevel Condition="'$(AndroidLatestStableApiLevel)' == ''">36</AndroidLatestStableApiLevel>
<AndroidLatestStableApiLevel Condition="'$(AndroidLatestStableApiLevel)' == ''">36.1</AndroidLatestStableApiLevel>
<AndroidLatestStablePlatformId Condition="'$(AndroidLatestStablePlatformId)' == ''">$(AndroidLatestStableApiLevel)</AndroidLatestStablePlatformId>
<AndroidLatestStableFrameworkVersion Condition="'$(AndroidLatestStableFrameworkVersion)'==''">v16.0</AndroidLatestStableFrameworkVersion>
<AndroidLatestStableApiLevel2 Condition="'$(AndroidLatestStableApiLevel2)' == ''">36.1</AndroidLatestStableApiLevel2>
<AndroidLatestStableFrameworkVersion Condition="'$(AndroidLatestStableFrameworkVersion)'==''">v16.1</AndroidLatestStableFrameworkVersion>
<!-- If there is a second *stable* API level (e.g. 37.1 alongside 37), set it here:
<AndroidLatestStableApiLevel2 Condition="'$(AndroidLatestStableApiLevel2)' == ''">37.1</AndroidLatestStableApiLevel2>
-->
<!-- *Latest* *unstable* API level binding that we support; this can be the same as *stable* -->
<AndroidLatestUnstableApiLevel Condition="'$(AndroidLatestUnstableApiLevel)' == ''">36.1</AndroidLatestUnstableApiLevel>
<AndroidLatestUnstablePlatformId Condition="'$(AndroidLatestUnstablePlatformId)' == ''">$(AndroidLatestUnstableApiLevel)</AndroidLatestUnstablePlatformId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public AndroidToolchain ()
new AndroidPlatformComponent ("platform-33-ext3_r03", apiLevel: "33", pkgRevision: "3"),
new AndroidPlatformComponent ("platform-34-ext7_r02", apiLevel: "34", pkgRevision: "2"),
new AndroidPlatformComponent ("platform-35_r02", apiLevel: "35", pkgRevision: "2"),
new AndroidPlatformComponent ("platform-36_r02", apiLevel: "36", pkgRevision: "2", isLatestStable: true),
new AndroidPlatformComponent ("platform-36_r02", apiLevel: "36", pkgRevision: "2"),
new AndroidPlatformComponent ("platform-36.1_r01", apiLevel: "36.1", pkgRevision: "1", isLatestStable: true),

new AndroidToolchainComponent ("source-36_r01",
Expand Down
2 changes: 1 addition & 1 deletion src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<PropertyGroup>
<IsUnstableVersion Condition=" $([MSBuild]::VersionGreaterThan($(AndroidApiLevel), $(AndroidLatestStableApiLevel)))
And $([MSBuild]::VersionGreaterThan($(AndroidApiLevel), $(AndroidLatestStableApiLevel2)))
And ( '$(AndroidLatestStableApiLevel2)' == '' Or $([MSBuild]::VersionGreaterThan($(AndroidApiLevel), $(AndroidLatestStableApiLevel2))) )
"
>true</IsUnstableVersion>
<DefineConstants Condition=" '$(IsUnstableVersion)' == 'True' ">$(DefineConstants);ANDROID_UNSTABLE</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"packs": [
"Microsoft.Android.Sdk.net11",
"Microsoft.Android.Sdk.net10",
"Microsoft.Android.Ref.36",
"Microsoft.Android.Runtime.36.android",
"Microsoft.Android.Runtime.Mono.36.android-arm",
"Microsoft.Android.Runtime.Mono.36.android-arm64",
"Microsoft.Android.Runtime.Mono.36.android-x86",
"Microsoft.Android.Runtime.Mono.36.android-x64",
"Microsoft.Android.Runtime.CoreCLR.36.android-arm64",
"Microsoft.Android.Runtime.CoreCLR.36.android-x64",
"Microsoft.Android.Runtime.NativeAOT.36.android-arm64",
"Microsoft.Android.Runtime.NativeAOT.36.android-x64",
"Microsoft.Android.Ref.36.1",
"Microsoft.Android.Runtime.36.1.android",
"Microsoft.Android.Runtime.Mono.36.1.android-arm",
"Microsoft.Android.Runtime.Mono.36.1.android-arm64",
"Microsoft.Android.Runtime.Mono.36.1.android-x86",
"Microsoft.Android.Runtime.Mono.36.1.android-x64",
"Microsoft.Android.Runtime.CoreCLR.36.1.android-arm64",
"Microsoft.Android.Runtime.CoreCLR.36.1.android-x64",
"Microsoft.Android.Runtime.NativeAOT.36.1.android-arm64",
"Microsoft.Android.Runtime.NativeAOT.36.1.android-x64",
"Microsoft.Android.Templates"
],
"platforms": [ "win-x64", "win-arm64", "linux-x64", "linux-arm64", "osx-x64", "osx-arm64" ],
Expand Down Expand Up @@ -54,51 +54,43 @@
"linux-arm64": "Microsoft.Android.Sdk.Linux"
}
},
"Microsoft.Android.Ref.36": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Ref.36.1": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.36.android": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.36.1.android": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.Mono.36.android-arm": {
"Microsoft.Android.Runtime.Mono.36.1.android-arm": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.Mono.36.android-arm64": {
"Microsoft.Android.Runtime.Mono.36.1.android-arm64": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.Mono.36.android-x86": {
"Microsoft.Android.Runtime.Mono.36.1.android-x86": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.Mono.36.android-x64": {
"Microsoft.Android.Runtime.Mono.36.1.android-x64": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.CoreCLR.36.android-arm64": {
"Microsoft.Android.Runtime.CoreCLR.36.1.android-arm64": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.CoreCLR.36.android-x64": {
"Microsoft.Android.Runtime.CoreCLR.36.1.android-x64": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.NativeAOT.36.android-arm64": {
"Microsoft.Android.Runtime.NativeAOT.36.1.android-arm64": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
"Microsoft.Android.Runtime.NativeAOT.36.android-x64": {
"Microsoft.Android.Runtime.NativeAOT.36.1.android-x64": {
"kind": "framework",
"version": "@WORKLOAD_VERSION@"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ public class CalculateProjectDependencies : AndroidTask

public string? CommandLineToolsVersion { get; set; }

public string? AndroidApiLevel { get; set; }

[Required]
public string TargetFrameworkVersion { get; set; } = "";
public string AndroidApiLevel { get; set; } = "";

[Required]
public ITaskItem ManifestFile { get; set; } = null!;
Expand Down Expand Up @@ -59,16 +57,23 @@ public override bool RunTask ()
{
var dependencies = new List<ITaskItem> ();
var javaDependencies = new List<ITaskItem> ();
var targetApiLevel = AndroidApiLevel.IsNullOrEmpty () ?
MonoAndroidHelper.SupportedVersions.GetApiLevelFromFrameworkVersion (TargetFrameworkVersion) :
MonoAndroidHelper.SupportedVersions.GetApiLevelFromId (AndroidApiLevel);
if (!MonoAndroidHelper.TryParseApiLevel (AndroidApiLevel, out var targetVersion)) {
Log.LogDebugMessage ($"Failed to parse AndroidApiLevel '{AndroidApiLevel}', defaulting to {DefaultMinSDKVersion}");
targetVersion = new Version (DefaultMinSDKVersion, 0);
}
var targetApiLevel = targetVersion.Major;
var manifestApiLevel = DefaultMinSDKVersion;
if (File.Exists (ManifestFile.ItemSpec)) {
var manifest = AndroidAppManifest.Load (ManifestFile.ItemSpec, MonoAndroidHelper.SupportedVersions);
manifestApiLevel = manifest.TargetSdkVersion ?? manifest.MinSdkVersion ?? DefaultMinSDKVersion;
}
var sdkVersion = Math.Max (targetApiLevel ?? DefaultMinSDKVersion, manifestApiLevel);
dependencies.Add (CreateAndroidDependency ($"platforms/android-{sdkVersion}", $""));
var sdkVersion = Math.Max (targetApiLevel, manifestApiLevel);
// Use the platform Id (e.g. "36.1") for the directory name when the target has a minor version,
// since Google ships platforms/android-36.1 as a separate SDK platform from platforms/android-36.
var platformId = sdkVersion == targetVersion.Major
? (MonoAndroidHelper.SupportedVersions.GetIdFromVersionCodeFull (targetVersion) ?? sdkVersion.ToString ())
: sdkVersion.ToString ();
dependencies.Add (CreateAndroidDependency ($"platforms/android-{platformId}", ""));
Comment thread
jonathanpeppers marked this conversation as resolved.
dependencies.Add (CreateAndroidDependency ($"build-tools/{BuildToolsVersion}", BuildToolsVersion));
if (!PlatformToolsVersion.IsNullOrEmpty ()) {
dependencies.Add (CreateAndroidDependency ("platform-tools", PlatformToolsVersion));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,15 +944,15 @@ public void IfAndroidJarDoesNotExistThrowXA5207 ([Values(true, false)] bool buil
[Test]
public void MinorApiLevelFallbackThrowsXA5207 ()
{
int apiLevel = XABuildConfig.AndroidLatestStableApiLevel.Major;
var stableApiLevel = XABuildConfig.AndroidLatestStableApiLevel;
// Verifies that when targeting a minor API level (like 36.1), we don't fall back to the major version (36)
// if the minor version platform is not installed. See: https://github.com/dotnet/android/issues/10720
var path = Path.Combine ("temp", TestName);
// Create a fake SDK with only android-36 (not android-36.1)
var AndroidSdkDirectory = CreateFauxAndroidSdkDirectory (Path.Combine (path, "android-sdk"), $"{apiLevel}.0.0", [new ApiInfo { Id = $"{apiLevel}" }]);
var AndroidSdkDirectory = CreateFauxAndroidSdkDirectory (Path.Combine (path, "android-sdk"), $"{stableApiLevel.Major}.0.0", [new ApiInfo { Id = $"{stableApiLevel.Major}" }]);
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
TargetFramework = $"{XABuildConfig.LatestDotNetTargetFramework}-android{apiLevel}.1",
TargetFramework = $"{XABuildConfig.LatestDotNetTargetFramework}-android{stableApiLevel}",
ExtraNuGetConfigSources = {
Path.Combine (XABuildPaths.BuildOutputDirectory, "nuget-unsigned"),
},
Expand All @@ -961,25 +961,25 @@ public void MinorApiLevelFallbackThrowsXA5207 ()
using (var builder = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
builder.ThrowOnBuildFailure = false;
Assert.IsFalse (builder.Build (proj, parameters: [
$"AndroidSdkBuildToolsVersion={apiLevel}.0.0",
$"AndroidSdkBuildToolsVersion={stableApiLevel.Major}.0.0",
$"AndroidSdkDirectory={AndroidSdkDirectory}",
]), "Build should have failed because android-36.1 is not installed");
]), $"Build should have failed because android-{stableApiLevel} is not installed");
Assert.IsTrue (builder.LastBuildOutput.ContainsText ("error XA5207:"), "XA5207 should have been raised.");
Assert.IsTrue (builder.LastBuildOutput.ContainsText ($"{apiLevel}.1"), "Error message should mention the minor API level.");
Assert.IsTrue (builder.LastBuildOutput.ContainsText ($"{stableApiLevel}"), "Error message should mention the API level.");
}
Directory.Delete (AndroidSdkDirectory, recursive: true);
}

[Test]
public void BuildWithDeviceLockedStateListener ()
{
int apiLevel = XABuildConfig.AndroidLatestStableApiLevel.Major;
var stableApiLevel = XABuildConfig.AndroidLatestStableApiLevel;

// Verifies that implementing DeviceLockedStateListener (introduced in API 36.1) builds successfully.
// This is a regression test for https://github.com/dotnet/android/issues/10720
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
TargetFramework = $"{XABuildConfig.LatestDotNetTargetFramework}-android{apiLevel}.1",
TargetFramework = $"{XABuildConfig.LatestDotNetTargetFramework}-android{stableApiLevel}",
ExtraNuGetConfigSources = {
Path.Combine (XABuildPaths.BuildOutputDirectory, "nuget-unsigned"),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ static IEnumerable<object[]> Get_SupportedOSTestSources_Data ()
);

AddTestData (
minSdkVersion: $"{XABuildConfig.AndroidDefaultTargetDotnetApiLevel}.0",
minSdkVersion: $"{XABuildConfig.AndroidLatestStableApiLevel}",
removeUsesSdkElement: false,
runtime: runtime
);
Expand Down Expand Up @@ -1545,6 +1545,7 @@ public void PropertyTest (Type type, string expected)

var manifest = new ManifestDocument (null) {
PackageName = "dummy.packageid",
TargetSdkVersion = "21",
VersionResolver = new MockVersionResolver (),
};

Expand Down Expand Up @@ -1619,11 +1620,61 @@ public void UsesPermissionFlagsAttribute ([Values] AndroidRuntime runtime)
}
}

/// <summary>
/// Regression test: when AndroidApiLevel is "36.1", the manifest must have targetSdkVersion="36".
/// GetIdFromApiLevel(36) can return "36.1" when multiple versions share the same ApiLevel,
/// so TargetSdkVersionName must not round-trip through it.
/// </summary>
[Test]
public void TargetSdkVersion_361In_36Out ()
{
// GetIdFromApiLevel("36") returns "36.1" — simulates the real bug
var resolver = new MockVersionResolver {
GetIdFromApiLevelFunc = apiLevel => apiLevel == "36" ? "36.1" : apiLevel,
GetApiLevelFromIdFunc = id => id == "36" || id == "36.1" ? 36 : 99,
};

// GenerateMainAndroidManifest strips "36.1" → "36" before setting TargetSdkVersion
string targetSdkVersion = "36.1";
if (MonoAndroidHelper.TryParseApiLevel (targetSdkVersion, out var version)) {
targetSdkVersion = version.Major.ToString ();
}

var templateFile = Path.GetTempFileName ();
try {
File.WriteAllText (templateFile, string.Format (TargetSdkManifest, "36"));

var manifest = new ManifestDocument (templateFile) {
PackageName = "com.test.targetsdkversion",
TargetSdkVersion = targetSdkVersion,
MinSdkVersion = "21",
VersionResolver = resolver,
};

var sb = new StringWriter ();
manifest.Save (null, sb);

var doc = XDocument.Parse (sb.ToString ());
var ns = XNamespace.Get ("http://schemas.android.com/apk/res/android");
var usesSdk = doc.Root.Element ("uses-sdk");
Assert.IsNotNull (usesSdk, "uses-sdk element should exist");

var targetSdkAttr = usesSdk.Attribute (ns + "targetSdkVersion");
Assert.IsNotNull (targetSdkAttr, "targetSdkVersion attribute should exist");
Assert.AreEqual ("36", targetSdkAttr.Value, "targetSdkVersion should be '36', not '36.1'");
} finally {
File.Delete (templateFile);
}
}

class MockVersionResolver : IVersionResolver
{
public int? GetApiLevelFromId (string id) => 99;
public Func<string, int?> GetApiLevelFromIdFunc { get; set; } = _ => 99;
public Func<string, string> GetIdFromApiLevelFunc { get; set; } = _ => "API-99";

public int? GetApiLevelFromId (string id) => GetApiLevelFromIdFunc (id);

public string GetIdFromApiLevel (string apiLevel) => "API-99";
public string GetIdFromApiLevel (string apiLevel) => GetIdFromApiLevelFunc (apiLevel);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public void CheckNdkBundle ([Values(true, false)] bool ndkRequred)
BuildEngine = engine
};

task.AndroidApiLevel = "26";
task.PlatformToolsVersion = "26.0.3";
task.NdkVersion = "12.1";
task.NdkRequired = ndkRequred;
task.BuildToolsVersion = "26.0.1";
task.TargetFrameworkVersion = "v8.0";
task.ManifestFile = new TaskItem (Path.Combine (path, "AndroidManifest.xml"));
Assert.IsTrue (task.Execute ());
Assert.IsNotNull (task.Dependencies);
Expand Down Expand Up @@ -65,11 +65,11 @@ public void ManifestFileDoesNotExist ()
BuildEngine = engine
};

task.AndroidApiLevel = "26";
task.PlatformToolsVersion = "26.0.3";
task.NdkVersion = "12.1";
task.NdkRequired = true;
task.BuildToolsVersion = "26.0.1";
task.TargetFrameworkVersion = "v8.0";
task.ManifestFile = new TaskItem (Path.Combine (path, "AndroidManifest.xml"));
Assert.IsTrue (task.Execute ());
Assert.IsNotNull (task.Dependencies);
Expand Down Expand Up @@ -106,11 +106,11 @@ public void ManifestFileExists ()
<uses-sdk android:minSdkVersion='21' />
</manifest>");

task.AndroidApiLevel = "26";
task.PlatformToolsVersion = "26.0.3";
task.NdkVersion = "12.1";
task.NdkRequired = true;
task.BuildToolsVersion = "26.0.1";
task.TargetFrameworkVersion = "v8.0";
task.ManifestFile = new TaskItem (manifestFile);
Assert.IsTrue(task.Execute ());
Assert.IsNotNull (task.Dependencies);
Expand All @@ -126,5 +126,29 @@ public void ManifestFileExists ()

Directory.Delete (path, recursive: true);
}

[Test]
public void MinorApiLevelEmitsPlatformDirectory ()
{
var path = Path.Combine ("temp", TestName);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new ApiInfo [] {
new ApiInfo () { Id = "36.1", Level = 36, Name = "Baklava", FrameworkVersion = "v16.1", Stable = true },
});
MonoAndroidHelper.RefreshSupportedVersions (new string [] { referencePath });
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
var task = new CalculateProjectDependencies {
BuildEngine = engine
};

task.AndroidApiLevel = "36.1";
task.PlatformToolsVersion = "36.0.0";
task.BuildToolsVersion = "36.0.0";
task.ManifestFile = new TaskItem (Path.Combine (path, "AndroidManifest.xml"));
Assert.IsTrue (task.Execute ());
Assert.IsNotNull (task.Dependencies);
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "platforms/android-36.1"),
"Dependencies should contain platform android-36.1 when AndroidApiLevel has a minor version");
Directory.Delete (Path.Combine (Root, path), recursive: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,6 @@ static IEnumerable<object[]> Get_DotNetTargetFrameworks_Data ()
runtime: runtime
);

AddTestData (
dotnetVersion: XABuildConfig.LatestDotNetTargetFramework,
platform: $"android{XABuildConfig.AndroidDefaultTargetDotnetApiLevel.Major}",
apiLevel: XABuildConfig.AndroidDefaultTargetDotnetApiLevel,
runtime: runtime
);

AddTestData (
dotnetVersion: XABuildConfig.LatestDotNetTargetFramework,
platform: $"android{XABuildConfig.AndroidDefaultTargetDotnetApiLevel}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,17 @@ public ManifestDocument (string templateFilename) : base ()
}
}

string TargetSdkVersionName => VersionResolver.GetIdFromApiLevel (TargetSdkVersion);
// TargetSdkVersion and MinSdkVersion are already resolved to integer API level
// strings (e.g. "36") by GenerateMainAndroidManifest. Don't round-trip through
// GetIdFromApiLevel() because when multiple API versions share the same ApiLevel
// (e.g. 36.0 and 36.1 both have ApiLevel=36), it can return the wrong Id
// (e.g. "36.1" instead of "36").
string TargetSdkVersionName => TargetSdkVersion;

string MinSdkVersionName =>
string.IsNullOrEmpty (MinSdkVersion) ?
TargetSdkVersionName :
VersionResolver.GetIdFromApiLevel (MinSdkVersion);
MinSdkVersion;

string ToFullyQualifiedName (string typeName)
{
Expand Down
Loading
Loading