From 218a708e6572856aa8ce0c63496921754eae8fad Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 23 Jul 2021 09:17:21 -0500 Subject: [PATCH] [One .NET] support latest C# 10 language features Context: https://github.com/dotnet/sdk/issues/19521 Fixes: https://github.com/xamarin/xamarin-android/issues/6075 Fixes: https://github.com/xamarin/xamarin-android/issues/6076 We need to make two sets of changes for C# 10: 1. Support "global usings". Our .NET 6 templates should have no `using` statements at the top of `.cs` files. 2. Use `$(Nullable)` `enable` by default in project templates. To test this, our .NET 6 MSBuild tests use `Nullable=enable` and `ImplicitUsings=enable` by default and do not include `using` statements in `.cs` files. I've made a new `MainActivity.cs` for our .NET 6 MSBuild tests. The "legacy" Xamarin.Android tests will use the original file. Our default `global using` are: global using global::Android.App; global using global::Android.Widget; global using Bundle = global::Android.OS.Bundle; The last one is intentionally not bringing in `Android.OS`, because `Android.OS.Environment` would conflict with `System.Environment`. So `AutoImport.props` should become: So these items are present at the time `.csproj` files are evaluated. Any templates will add: enable enable If users want to configure these settings, they can remove `$(ImplicitUsings)` from the `.csproj` completely or remove specific `@(Using)` items: --- .../android-activity/Activity1.cs | 7 +- .../android-bindinglib/AndroidBinding1.csproj | 2 + .../android/AndroidApp1.csproj | 2 + .../android/MainActivity.cs | 7 +- .../androidlib/AndroidLib1.csproj | 2 + .../androidlib/Class1.cs | 2 - .../Sdk/AutoImport.props | 6 ++ .../Utilities/DeviceTest.cs | 1 + .../Xamarin.Android.Build.Tests/XASdkTests.cs | 5 +- .../Android/KnownProperties.cs | 2 + .../Android/XASdkProject.cs | 4 +- .../XamarinAndroidApplicationProject.cs | 7 +- .../Android/XamarinAndroidLibraryProject.cs | 5 +- .../XamarinFormsAndroidApplicationProject.cs | 4 ++ .../Android/XamarinFormsXASdkProject.cs | 2 + .../BuildReleaseArm64SimpleDotNet.apkdesc | 2 +- .../Resources/DotNet/MainActivity.cs | 24 +++++++ .../Xamarin.ProjectTools.csproj | 67 +------------------ .../Tests/InstallAndRunTests.cs | 15 +++-- .../Tests/XASdkDeployTests.cs | 6 +- 20 files changed, 76 insertions(+), 96 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/DotNet/MainActivity.cs diff --git a/src/Microsoft.Android.Templates/android-activity/Activity1.cs b/src/Microsoft.Android.Templates/android-activity/Activity1.cs index f974a451ba3..e6d6f33c9a5 100644 --- a/src/Microsoft.Android.Templates/android-activity/Activity1.cs +++ b/src/Microsoft.Android.Templates/android-activity/Activity1.cs @@ -1,14 +1,9 @@ -using Android.App; -using Android.OS; -using Android.Runtime; -using Android.Widget; - namespace AndroidApp1 { [Activity(Label = "@string/app_name", MainLauncher = true)] public class Activity1 : Activity { - protected override void OnCreate(Bundle savedInstanceState) + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); diff --git a/src/Microsoft.Android.Templates/android-bindinglib/AndroidBinding1.csproj b/src/Microsoft.Android.Templates/android-bindinglib/AndroidBinding1.csproj index 5a6a74ace70..2cf3b2f456a 100644 --- a/src/Microsoft.Android.Templates/android-bindinglib/AndroidBinding1.csproj +++ b/src/Microsoft.Android.Templates/android-bindinglib/AndroidBinding1.csproj @@ -3,5 +3,7 @@ net6.0-android SUPPORTED_OS_PLATFORM_VERSION AndroidBinding1 + enable + enable \ No newline at end of file diff --git a/src/Microsoft.Android.Templates/android/AndroidApp1.csproj b/src/Microsoft.Android.Templates/android/AndroidApp1.csproj index 1fd328a5c21..906e1314de1 100644 --- a/src/Microsoft.Android.Templates/android/AndroidApp1.csproj +++ b/src/Microsoft.Android.Templates/android/AndroidApp1.csproj @@ -4,5 +4,7 @@ SUPPORTED_OS_PLATFORM_VERSION AndroidApp1 Exe + enable + enable \ No newline at end of file diff --git a/src/Microsoft.Android.Templates/android/MainActivity.cs b/src/Microsoft.Android.Templates/android/MainActivity.cs index 7e49926946e..b48279dbee4 100644 --- a/src/Microsoft.Android.Templates/android/MainActivity.cs +++ b/src/Microsoft.Android.Templates/android/MainActivity.cs @@ -1,14 +1,9 @@ -using Android.App; -using Android.OS; -using Android.Runtime; -using Android.Widget; - namespace AndroidApp1 { [Activity(Label = "@string/app_name", MainLauncher = true)] public class MainActivity : Activity { - protected override void OnCreate(Bundle savedInstanceState) + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); diff --git a/src/Microsoft.Android.Templates/androidlib/AndroidLib1.csproj b/src/Microsoft.Android.Templates/androidlib/AndroidLib1.csproj index 5eb4df59b23..46aa993fc7f 100644 --- a/src/Microsoft.Android.Templates/androidlib/AndroidLib1.csproj +++ b/src/Microsoft.Android.Templates/androidlib/AndroidLib1.csproj @@ -3,5 +3,7 @@ net6.0-android SUPPORTED_OS_PLATFORM_VERSION AndroidLib1 + enable + enable \ No newline at end of file diff --git a/src/Microsoft.Android.Templates/androidlib/Class1.cs b/src/Microsoft.Android.Templates/androidlib/Class1.cs index 1004d5770c0..5eebfbb4e60 100644 --- a/src/Microsoft.Android.Templates/androidlib/Class1.cs +++ b/src/Microsoft.Android.Templates/androidlib/Class1.cs @@ -1,5 +1,3 @@ -using System; - namespace AndroidLib1 { public class Class1 diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/Sdk/AutoImport.props b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/Sdk/AutoImport.props index fb26abb21e9..90843410620 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/Sdk/AutoImport.props +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/Sdk/AutoImport.props @@ -20,6 +20,12 @@ https://github.com/dotnet/designs/blob/4703666296f5e59964961464c25807c727282cae/ <_DefaultJavaSourceJarPattern>**\*-source.jar;**\*-sources.jar;**\*-src.jar + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs index 6a714d545d5..b4e41cc4d60 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs @@ -67,6 +67,7 @@ protected override void CleanupTest () { if (HasDevices && TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed && TestOutputDirectories.TryGetValue (TestContext.CurrentContext.Test.ID, out string outputDir)) { + Directory.CreateDirectory (outputDir); string local = Path.Combine (outputDir, "screenshot.png"); string deviceLog = Path.Combine (outputDir, "logcat-failed.log"); string remote = "/data/local/tmp/screenshot.png"; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs index f2606c22da5..ee76e764879 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs @@ -205,6 +205,7 @@ public void DotNetNew ([Values ("android", "androidlib", "android-bindinglib")] Assert.IsTrue (dotnet.New ("android-activity"), "`dotnet new android-activity` should succeed"); Assert.IsTrue (dotnet.New ("android-layout", Path.Combine (dotnet.ProjectDirectory, "Resources", "layout")), "`dotnet new android-layout` should succeed"); Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed"); + dotnet.AssertHasNoWarnings (); } [Test] @@ -519,7 +520,7 @@ public void SupportedOSPlatformVersion ([Values (21, 30)] int minSdkVersion) }; // 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"); + proj.MainActivity = proj.DefaultMainActivity.Replace ("button!.Click", "button!.AccessibilityTraversalAfter.ToString ();\nbutton!.Click"); var dotnet = CreateDotNetBuilder (proj); Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed"); @@ -614,7 +615,7 @@ void CreateEmptyFile (params string [] paths) public void XamarinLegacySdk () { var proj = new XASdkProject (outputType: "Library") { - Sdk = "Xamarin.Legacy.Sdk/0.1.0-alpha2", + Sdk = "Xamarin.Legacy.Sdk/0.1.0-alpha4", Sources = { new AndroidItem.AndroidLibrary ("javaclasses.jar") { BinaryContent = () => ResourceData.JavaSourceJarTestJar, diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs index 43c92db5284..73c8cf3f888 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownProperties.cs @@ -36,6 +36,8 @@ public static class KnownProperties public const string AndroidFastDeploymentType = "AndroidFastDeploymentType"; public const string AndroidClassParser = "AndroidClassParser"; public const string _AndroidAllowDeltaInstall = "_AndroidAllowDeltaInstall"; + public const string Nullable = "Nullable"; + public const string ImplicitUsings = "ImplicitUsings"; } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XASdkProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XASdkProject.cs index 6882724244f..623a21df08d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XASdkProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XASdkProject.cs @@ -28,7 +28,7 @@ static XASdkProject () var assembly = typeof (XASdkProject).Assembly; using (var sr = new StreamReader (assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.Base.AndroidManifest.xml"))) default_android_manifest = sr.ReadToEnd (); - using (var sr = new StreamReader (assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.Base.MainActivity.cs"))) + using (var sr = new StreamReader (assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.DotNet.MainActivity.cs"))) default_main_activity_cs = sr.ReadToEnd (); using (var sr = new StreamReader (assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.Base.LayoutMain.axml"))) default_layout_main = sr.ReadToEnd (); @@ -61,6 +61,8 @@ public XASdkProject (string outputType = "Exe", [CallerMemberName] string packag JavaPackageName = JavaPackageName ?? PackageName.ToLowerInvariant (); GlobalPackagesFolder = FileSystemUtils.FindNugetGlobalPackageFolder (); SetProperty (KnownProperties.OutputType, outputType); + SetProperty (KnownProperties.Nullable, "enable"); + SetProperty (KnownProperties.ImplicitUsings, "enable"); // Add relevant Android content to our project without writing it to the .csproj file if (outputType == "Exe") { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs index a02e24fdd1b..2d1be00bb30 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidApplicationProject.cs @@ -24,7 +24,8 @@ public class XamarinAndroidApplicationProject : XamarinAndroidCommonProject static XamarinAndroidApplicationProject () { - using (var sr = new StreamReader (typeof(XamarinAndroidApplicationProject).Assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.Base.MainActivity.cs"))) + var folder = Builder.UseDotNet ? "DotNet" : "Base"; + using (var sr = new StreamReader (typeof(XamarinAndroidApplicationProject).Assembly.GetManifestResourceStream ($"Xamarin.ProjectTools.Resources.{folder}.MainActivity.cs"))) default_main_activity_cs = sr.ReadToEnd (); using (var sr = new StreamReader (typeof(XamarinAndroidApplicationProject).Assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.Base.MainActivity.fs"))) default_main_activity_fs = sr.ReadToEnd (); @@ -40,6 +41,8 @@ public XamarinAndroidApplicationProject (string debugConfigurationName = "Debug" { if (Builder.UseDotNet) { SetProperty (KnownProperties.OutputType, "Exe"); + SetProperty (KnownProperties.Nullable, "enable"); + SetProperty (KnownProperties.ImplicitUsings, "enable"); SetProperty ("XamarinAndroidSupportSkipVerifyVersions", "True"); SetProperty ("_FastDeploymentDiagnosticLogging", "True"); @@ -81,7 +84,7 @@ public XamarinAndroidApplicationProject (string debugConfigurationName = "Debug" } // it is exposed as public because we may want to slightly modify this. - public string DefaultMainActivity { + public virtual string DefaultMainActivity { get { return Language == XamarinAndroidProjectLanguage.FSharp ? default_main_activity_fs : default_main_activity_cs; } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidLibraryProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidLibraryProject.cs index 86b3142c557..358a0f47d1f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidLibraryProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidLibraryProject.cs @@ -14,7 +14,10 @@ public class XamarinAndroidLibraryProject : XamarinAndroidCommonProject public XamarinAndroidLibraryProject (string debugConfigurationName = "Debug", string releaseConfigurationName = "Release") : base (debugConfigurationName, releaseConfigurationName) { - if (!Builder.UseDotNet) { + if (Builder.UseDotNet) { + SetProperty (KnownProperties.Nullable, "enable"); + SetProperty (KnownProperties.ImplicitUsings, "enable"); + } else { SetProperty ("AndroidApplication", "False"); SetProperty ("AndroidResgenFile", Path.Combine ("Resources", "Resource.designer.cs")); } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsAndroidApplicationProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsAndroidApplicationProject.cs index 1d740f477cf..9981e78a859 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsAndroidApplicationProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsAndroidApplicationProject.cs @@ -42,6 +42,8 @@ public XamarinFormsAndroidApplicationProject (string debugConfigurationName = "D : base (debugConfigurationName, releaseConfigurationName, packageName) { if (Builder.UseDotNet) { + // Don't opt into ImplicitUsings + RemoveProperty (KnownProperties.ImplicitUsings); PackageReferences.Add (KnownPackages.XamarinForms_4_7_0_1142); } else { PackageReferences.Add (KnownPackages.XamarinForms_4_0_0_425677); @@ -76,6 +78,8 @@ public XamarinFormsAndroidApplicationProject (string debugConfigurationName = "D MainPage = MainPage_xaml_cs; } + public override string DefaultMainActivity => default_main_activity_cs; + public string MainPage { get; set; } protected virtual string MainPageXaml () => ProcessSourceTemplate (MainPage_xaml); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsXASdkProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsXASdkProject.cs index e8f0e8ccc73..ddfb338d0ba 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsXASdkProject.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinFormsXASdkProject.cs @@ -44,6 +44,8 @@ static XamarinFormsXASdkProject () public XamarinFormsXASdkProject (string outputType = "Exe", [CallerMemberName] string packageName = "") : base (outputType, packageName) { + // Don't opt into ImplicitUsings + RemoveProperty (KnownProperties.ImplicitUsings); PackageReferences.Add (KnownPackages.XamarinForms_4_7_0_1142); // Workaround for AndroidX, see: https://github.com/xamarin/AndroidSupportComponents/pull/239 diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 75bc7b32c50..d2639b16021 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -23,7 +23,7 @@ "Size": 2681 }, "assemblies/UnnamedProject.dll": { - "Size": 3186 + "Size": 3535 }, "classes.dex": { "Size": 345240 diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/DotNet/MainActivity.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/DotNet/MainActivity.cs new file mode 100644 index 00000000000..ed408f5cf1b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/DotNet/MainActivity.cs @@ -0,0 +1,24 @@ +namespace ${ROOT_NAMESPACE} +{ + [Android.Runtime.Register ("${JAVA_PACKAGENAME}.MainActivity"), Activity (Label = "${PROJECT_NAME}", MainLauncher = true, Icon = "@drawable/icon")] + public class MainActivity : Activity + { + int count = 1; + + protected override void OnCreate (Bundle? bundle) + { + base.OnCreate (bundle); + + // Set our view from the "main" layout resource + SetContentView (Resource.Layout.Main); + + var button = FindViewById