Navigation Menu

Skip to content

Commit

Permalink
[One .NET] implement $(SupportedPlatformOSVersion) for minSdkVersion (#…
Browse files Browse the repository at this point in the history
…6111)

Context: #6107 (comment)

In .NET 6, we are wanting `AndroidManifest.xml` in project templates
to no longer contain:

    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />

This is because we can define these values such as:

    <TargetFramework>net6.0-android30</TargetFramework>
    <SupportedPlatformOSVersion>21.0</SupportedPlatformOSVersion>

There is not really a reason to put this information in two places.
`$(SupportedPlatformOSVersion)` is also required for enabling C#
compiler warnings such as:

    MainActivity.cs(28,4): warning CA1416: This call site is reachable on: 'Android' 21.0 and later. 'View.AccessibilityTraversalAfter.get' is only supported on: 'android' 22.0 and later.

This is an example of calling an API from 22, with a
`$(SupportedPlatformOSVersion)` for 21.

If `<uses-sdk/>` is still *used* in `AndroidManifest.xml` in a
project, it will be preferred as the final source of truth.

One other improvement is that `$(SupportedPlatformOSVersion)` is
expected to include `.0`, or you get the warning:

    UnnamedProject.AssemblyInfo.cs(21,12): warning CA1418: Version '21' is not valid for platform 'Android'. Use a version with 2-4 parts for this platform.

Android unique in that this value is always an integer. In order for
users to be able to specify `21`, I added a check in
`Microsoft.Android.Sdk.DefaultProperties.targets` so that we append
`.0` when needed.

I added some tests around this scenario.
  • Loading branch information
jonathanpeppers committed Jul 30, 2021
1 parent d2a31d1 commit a66013b
Show file tree
Hide file tree
Showing 17 changed files with 102 additions and 38 deletions.
Expand Up @@ -15,5 +15,14 @@
"primaryOutputs": [
{ "path": "AndroidBinding1.csproj" }
],
"symbols": {
"supportedOSVersion": {
"type": "parameter",
"description": "Overrides $(SupportedOSPlatformVersion) in the project",
"datatype": "string",
"replaces": "SUPPORTED_OS_PLATFORM_VERSION",
"defaultValue": "21"
}
},
"defaultName": "AndroidBinding1"
}
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-android</TargetFramework>
<SupportedOSPlatformVersion>SUPPORTED_OS_PLATFORM_VERSION</SupportedOSPlatformVersion>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">AndroidBinding1</RootNamespace>
</PropertyGroup>
</Project>
Expand Up @@ -28,6 +28,13 @@
"description": "Overrides the package name in the AndroidManifest.xml",
"datatype": "string",
"replaces": "com.companyname.AndroidApp1"
},
"supportedOSVersion": {
"type": "parameter",
"description": "Overrides $(SupportedOSPlatformVersion) in the project",
"datatype": "string",
"replaces": "SUPPORTED_OS_PLATFORM_VERSION",
"defaultValue": "21"
}
},
"defaultName": "AndroidApp1"
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.Android.Templates/android/AndroidApp1.csproj
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-android</TargetFramework>
<SupportedOSPlatformVersion>SUPPORTED_OS_PLATFORM_VERSION</SupportedOSPlatformVersion>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">AndroidApp1</RootNamespace>
<OutputType>Exe</OutputType>
</PropertyGroup>
Expand Down
Expand Up @@ -3,7 +3,6 @@
android:versionCode="1"
android:versionName="1.0"
package="com.companyname.AndroidApp1">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true">
</application>
</manifest>
Expand Up @@ -15,5 +15,14 @@
"primaryOutputs": [
{ "path": "AndroidLib1.csproj" }
],
"symbols": {
"supportedOSVersion": {
"type": "parameter",
"description": "Overrides $(SupportedOSPlatformVersion) in the project",
"datatype": "string",
"replaces": "SUPPORTED_OS_PLATFORM_VERSION",
"defaultValue": "21"
}
},
"defaultName": "AndroidLib1"
}
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-android</TargetFramework>
<SupportedOSPlatformVersion>SUPPORTED_OS_PLATFORM_VERSION</SupportedOSPlatformVersion>
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">AndroidLib1</RootNamespace>
</PropertyGroup>
</Project>
Expand Up @@ -99,6 +99,7 @@ Copyright (C) 2016 Xamarin. All rights reserved.
FrameworkDirectories="$(_XATargetFrameworkDirectories);$(_XATargetFrameworkDirectories)Facades"
AcwMapFile="$(_AcwMapFile)"
SupportedAbis="$(_BuildTargetAbis)"
SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)"
InstantRunEnabled="$(_InstantRunEnabled)">
</GenerateJavaStubs>
<ManifestMerger
Expand Down
Expand Up @@ -23,6 +23,8 @@
-->
<_GetChildProjectCopyToPublishDirectoryItems>false</_GetChildProjectCopyToPublishDirectoryItems>
<UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' ">true</UseMonoRuntime>
<!-- $(SupportedOSPlatformVersion) must be '21.0', but we should support integer values like '21' -->
<SupportedOSPlatformVersion Condition=" '$(SupportedOSPlatformVersion)' != '' and !$(SupportedOSPlatformVersion.Contains('.')) ">$(SupportedOSPlatformVersion).0</SupportedOSPlatformVersion>

<!-- Bindings properties -->
<!-- jar2xml is not supported -->
Expand Down
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs
Expand Up @@ -263,7 +263,7 @@ protected string GenerateCommandLineCommands (string ManifestFile, string curren
Directory.CreateDirectory (manifestDir);
manifestFile = Path.Combine (manifestDir, Path.GetFileName (ManifestFile));
ManifestDocument manifest = new ManifestDocument (ManifestFile);
manifest.SdkVersion = AndroidSdkPlatform;
manifest.TargetSdkVersion = AndroidSdkPlatform;
if (!string.IsNullOrEmpty (VersionCodePattern)) {
try {
manifest.CalculateVersionCode (currentAbi, VersionCodePattern, VersionCodeProperties);
Expand Down
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs
Expand Up @@ -139,7 +139,7 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
Directory.CreateDirectory (manifestDir);
string manifestFile = Path.Combine (manifestDir, Path.GetFileName (ManifestFile));
ManifestDocument manifest = new ManifestDocument (ManifestFile);
manifest.SdkVersion = AndroidSdkPlatform;
manifest.TargetSdkVersion = AndroidSdkPlatform;
if (!string.IsNullOrEmpty (VersionCodePattern)) {
try {
manifest.CalculateVersionCode (currentAbi, VersionCodePattern, VersionCodeProperties);
Expand Down
11 changes: 10 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
Expand Up @@ -82,6 +82,8 @@ public class GenerateJavaStubs : AndroidTask

public string CheckedBuild { get; set; }

public string SupportedOSPlatformVersion { get; set; }

[Output]
public string [] GeneratedBinaryTypeMaps { get; set; }

Expand Down Expand Up @@ -259,6 +261,12 @@ void Run (DirectoryAssemblyResolver res)
Log.LogCodedError ("XA4215", Properties.Resources.XA4215_Details, kvp.Key, typeName);
}

// NOTE: $(SupportedOSPlatformVersion) will potentially be 21.0
string minSdkVersion = null;
if (Version.TryParse (SupportedOSPlatformVersion, out var version)) {
minSdkVersion = version.Major.ToString ();
}

// Step 3 - Merge [Activity] and friends into AndroidManifest.xml
var manifest = new ManifestDocument (ManifestTemplate) {
PackageName = PackageName,
Expand All @@ -267,7 +275,8 @@ void Run (DirectoryAssemblyResolver res)
Placeholders = ManifestPlaceholders,
Resolver = res,
SdkDir = AndroidSdkDir,
SdkVersion = AndroidSdkPlatform,
TargetSdkVersion = AndroidSdkPlatform,
MinSdkVersion = minSdkVersion,
Debug = Debug,
MultiDex = MultiDex,
NeedsInternet = NeedsInternet,
Expand Down
@@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Mono.Cecil;
using NUnit.Framework;
using Xamarin.Android.Tasks;
Expand Down Expand Up @@ -474,6 +474,15 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot)
}
}

// Check AndroidManifest.xml
var manifestPath = Path.Combine (intermediateOutputPath, "android", "AndroidManifest.xml");
FileAssert.Exists (manifestPath);
var manifest = XDocument.Load (manifestPath);
XNamespace ns = "http://schemas.android.com/apk/res/android";
var uses_sdk = manifest.Root.Element ("uses-sdk");
Assert.AreEqual ("21", uses_sdk.Attribute (ns + "minSdkVersion").Value);
Assert.AreEqual ("30", uses_sdk.Attribute (ns + "targetSdkVersion").Value);

bool expectEmbeddedAssembies = !(CommercialBuildAvailable && !isRelease);
var apkPath = Path.Combine (outputPath, $"{proj.PackageName}.apk");
FileAssert.Exists (apkPath);
Expand All @@ -494,6 +503,32 @@ public void DotNetBuild (string runtimeIdentifiers, bool isRelease, bool aot)
}
}

[Test]
public void SupportedOSPlatformVersion ([Values (21, 30)] int minSdkVersion)
{
var proj = new XASdkProject {
SupportedOSPlatformVersion = minSdkVersion.ToString (),
};
// Call AccessibilityTraversalAfter from API level 22
// https://developer.android.com/reference/android/view/View#getAccessibilityTraversalAfter()
proj.MainActivity = proj.DefaultMainActivity.Replace ("button.Click", "button.AccessibilityTraversalAfter.ToString ();\nbutton.Click");

var dotnet = CreateDotNetBuilder (proj);
Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");

if (minSdkVersion < 22) {
StringAssertEx.Contains ("warning CA1416", dotnet.LastBuildOutput, "Should get warning about Android 22 API");
} else {
dotnet.AssertHasNoWarnings ();
}

var manifestPath = Path.Combine (FullProjectDirectory, proj.IntermediateOutputPath, "android", "AndroidManifest.xml");
FileAssert.Exists (manifestPath);
var manifest = XDocument.Load (manifestPath);
XNamespace ns = "http://schemas.android.com/apk/res/android";
Assert.AreEqual (minSdkVersion.ToString (), manifest.Root.Element ("uses-sdk").Attribute (ns + "minSdkVersion").Value);
}

[Test]
[Category ("SmokeTests")]
public void DotNetBuildXamarinForms ([Values (true, false)] bool useInterpreter)
Expand Down
Expand Up @@ -17,6 +17,7 @@ public static class KnownProperties
public const string RuntimeIdentifier = "RuntimeIdentifier";
public const string RuntimeIdentifiers = "RuntimeIdentifiers";
public const string PublishTrimmed = "PublishTrimmed";
public const string SupportedOSPlatformVersion = "SupportedOSPlatformVersion";

public const string Deterministic = "Deterministic";
public const string BundleAssemblies = "BundleAssemblies";
Expand Down
Expand Up @@ -56,8 +56,7 @@ public XASdkProject (string outputType = "Exe", [CallerMemberName] string packag
{
Sdk = "Microsoft.NET.Sdk";
TargetFramework = "net6.0-android";

TargetSdkVersion = AndroidSdkResolver.GetMaxInstalledPlatform ().ToString ();
SupportedOSPlatformVersion = "21";
PackageName = $"com.xamarin.{(packageName ?? ProjectName).ToLower ()}";
JavaPackageName = JavaPackageName ?? PackageName.ToLowerInvariant ();
GlobalPackagesFolder = FileSystemUtils.FindNugetGlobalPackageFolder ();
Expand Down Expand Up @@ -89,34 +88,19 @@ public XASdkProject (string outputType = "Exe", [CallerMemberName] string packag
public string AndroidManifest { get; set; } = default_android_manifest;

/// <summary>
/// Defaults to AndroidSdkResolver.GetMaxInstalledPlatform ()
/// </summary>
public string TargetSdkVersion { get; set; }

/// <summary>
/// Defaults to API 19
/// Defaults to 21.0
/// </summary>
public string MinSdkVersion { get; set; } = "19";
public string SupportedOSPlatformVersion {
get { return GetProperty (KnownProperties.SupportedOSPlatformVersion); }
set { SetProperty (KnownProperties.SupportedOSPlatformVersion, value); }
}

public virtual string ProcessManifestTemplate ()
{
var uses_sdk = new StringBuilder ("<uses-sdk ");
if (!string.IsNullOrEmpty (MinSdkVersion)) {
uses_sdk.Append ("android:minSdkVersion=\"");
uses_sdk.Append (MinSdkVersion);
uses_sdk.Append ("\" ");
}
if (!string.IsNullOrEmpty (TargetSdkVersion)) {
uses_sdk.Append ("android:targetSdkVersion=\"");
uses_sdk.Append (TargetSdkVersion);
uses_sdk.Append ("\" ");
}
uses_sdk.Append ("/>");

return AndroidManifest
.Replace ("${PROJECT_NAME}", ProjectName)
.Replace ("${PACKAGENAME}", PackageName)
.Replace ("${USES_SDK}", uses_sdk.ToString ());
.Replace ("${USES_SDK}", "");
}

public override string ProcessSourceTemplate (string source)
Expand Down
24 changes: 14 additions & 10 deletions src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
Expand Up @@ -84,7 +84,8 @@ internal class ManifestDocument
public List<string> Assemblies { get; set; }
public DirectoryAssemblyResolver Resolver { get; set; }
public string SdkDir { get; set; }
public string SdkVersion { get; set; }
public string TargetSdkVersion { get; set; }
public string MinSdkVersion { get; set; }
public bool Debug { get; set; }
public bool MultiDex { get; set; }
public bool NeedsInternet { get; set; }
Expand Down Expand Up @@ -118,7 +119,7 @@ internal class ManifestDocument
var minAttr = doc.Root.Element ("uses-sdk")?.Attribute (androidNs + "minSdkVersion");
if (minAttr == null) {
int minSdkVersion;
if (!int.TryParse (SdkVersionName, out minSdkVersion))
if (!int.TryParse (MinSdkVersionName, out minSdkVersion))
minSdkVersion = defaultMinSdkVersion;
return Math.Min (minSdkVersion, defaultMinSdkVersion).ToString ();
}
Expand All @@ -129,7 +130,7 @@ public string GetTargetSdk ()
{
var targetAttr = doc.Root.Element ("uses-sdk")?.Attribute (androidNs + "targetSdkVersion");
if (targetAttr == null) {
return SdkVersionName;
return TargetSdkVersionName;
}
return targetAttr.Value;
}
Expand All @@ -150,9 +151,12 @@ public ManifestDocument (string templateFilename) : base ()
}
}

string SdkVersionName {
get { return MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (SdkVersion); }
}
string TargetSdkVersionName => MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (TargetSdkVersion);

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

string ToFullyQualifiedName (string typeName)
{
Expand Down Expand Up @@ -293,16 +297,16 @@ public IList<string> Merge (TaskLoggingHelper log, TypeDefinitionCache cache, Li
if (!manifest.Elements ("uses-sdk").Any ()) {
manifest.AddFirst (
new XElement ("uses-sdk",
new XAttribute (androidNs + "minSdkVersion", SdkVersionName),
new XAttribute (androidNs + "targetSdkVersion", SdkVersionName)));
new XAttribute (androidNs + "minSdkVersion", MinSdkVersionName),
new XAttribute (androidNs + "targetSdkVersion", TargetSdkVersionName)));
}

// If no minSdkVersion is specified, set it to TargetFrameworkVersion
var uses = manifest.Element ("uses-sdk");

if (uses.Attribute (androidNs + "minSdkVersion") == null) {
int minSdkVersion;
if (!int.TryParse (SdkVersionName, out minSdkVersion))
if (!int.TryParse (MinSdkVersionName, out minSdkVersion))
minSdkVersion = XABuildConfig.NDKMinimumApiAvailable;
minSdkVersion = Math.Min (minSdkVersion, XABuildConfig.NDKMinimumApiAvailable);
uses.SetAttributeValue (androidNs + "minSdkVersion", minSdkVersion.ToString ());
Expand All @@ -313,7 +317,7 @@ public IList<string> Merge (TaskLoggingHelper log, TypeDefinitionCache cache, Li
if (tsv != null)
targetSdkVersion = tsv.Value;
else {
targetSdkVersion = SdkVersionName;
targetSdkVersion = TargetSdkVersionName;
uses.AddBeforeSelf (new XComment ("suppress UsesMinSdkAttributes"));
}

Expand Down
Expand Up @@ -1449,6 +1449,7 @@ because xbuild doesn't support framework reference assemblies.
FrameworkDirectories="$(_XATargetFrameworkDirectories);$(_XATargetFrameworkDirectories)Facades"
AcwMapFile="$(_AcwMapFile)"
SupportedAbis="@(_BuildTargetAbis)"
SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)"
SkipJniAddNativeMethodRegistrationAttributeScan="$(_SkipJniAddNativeMethodRegistrationAttributeScan)"
CheckedBuild="$(_AndroidCheckedBuild)">
<Output TaskParameter="GeneratedBinaryTypeMaps" ItemName="_AndroidTypeMapping" Condition=" '$(_InstantRunEnabled)' == 'True' " />
Expand Down

0 comments on commit a66013b

Please sign in to comment.