From d60e7176dffef5ea75bbf7a552df1a449aa058ea Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Wed, 14 Jul 2021 14:00:02 -0500 Subject: [PATCH] [One .NET] Update .NET 6 defaults for bindings projects (#6066) Fixes: https://github.com/xamarin/xamarin-android/issues/4657 With .NET 6 we want to turn on the following binding project enhancements added with C# 8 by default: - Interface constants: xamarin/java.interop@a3b74568 - Interface static methods: xamarin/java.interop@855b7e92 - Interface default methods: xamarin/java.interop@29f97075 - Interface nested types: xamarin/java.interop@28b1fc97 - Nullable Reference Types: xamarin/java.interop@01d06845 However, each of these features previously had a workaround that we performed so the code could be used by .NET. This means that users who are updating their existing bindings may find API breaks as we now use the new method instead of the workaround. Thus each of these needs a way for the user to opt out of the new behavior to preserve API compatibility. ~~ New MSBuild Properties ~~ - `$(AndroidBoundInterfacesContainConstants)`: Controls whether constants on interfaces will be supported, or the old workaround of creating a `IMyInterfaceConsts` class will be used. - `$(AndroidBoundInterfacesContainTypes)`: Controls whether types nested in interfaces will be supported, or the old workaround of creating a non-nested type like `IMyInterfaceMyNestedClass`. - `$(AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods)`: Controls whether default and static members on interfaces will be supported, or the old workaround of creating a sibling class containing static members like `abstract class MyInterface`. ~~ .NET 6 ~~ For .NET 6, the above properties all default to `true` if not explicitly set by the user. ~~ Legacy ~~ For legacy, the above properties all default to `false`. The "legacy" way of enabling these features was a single preview flag `$(_EnableInterfaceMembers)`. We will continue to support this flag for legacy only, and it will enable all the above flags, unless a user has explicitly set one of the flags. ~~ AndroidCodegenTarget ~~ .NET 6 binding projects will only support `XAJavaInterop1`, so other values are ignored. --- Documentation/guides/OneDotNet.md | 32 +-- .../guides/OneDotNetBindingProjects.md | 221 ++++++++++++++++++ .../guides/building-apps/build-properties.md | 29 +++ .../Xamarin.Android.Bindings.Core.targets | 4 +- ...soft.Android.Sdk.DefaultProperties.targets | 12 +- .../Tasks/Generator.cs | 12 +- .../Xamarin.Android.Bindings.targets | 7 + 7 files changed, 281 insertions(+), 36 deletions(-) create mode 100644 Documentation/guides/OneDotNetBindingProjects.md diff --git a/Documentation/guides/OneDotNet.md b/Documentation/guides/OneDotNet.md index 7fde717434d..c3619205f5b 100644 --- a/Documentation/guides/OneDotNet.md +++ b/Documentation/guides/OneDotNet.md @@ -22,37 +22,11 @@ See the [Target Framework Names in .NET 5][net5spec] spec for details. [net5spec]: https://github.com/dotnet/designs/blob/5e921a9dc8ecce33b3195dcdb6f10ef56ef8b9d7/accepted/2020/net5/net5.md -## Consolidation of binding projects +## Bindings projects -In .NET 6, there will no longer be a concept of a [binding -project][binding] as a separate project type. Any of the MSBuild item -groups or build actions that currently work in binding projects will -be supported through a .NET 6 Android application or library. +Changes for bindings projects are documented [here][binding]. -For example, a binding library would be identical to a class library: - -```xml - - - net6.0-android - - -``` - -Along with the file structure: - - Transforms/ - Metadata.xml - foo.jar - -`Transforms\*.xml` files are automatically included as a -`@(TransformFile)` item, and `.jar` files are automatically included -as a `@(AndroidLibrary)` item. - -This will bind C# types for the Java types found in `foo.jar` using -the metadata fixups from `Transforms\Metadata.xml`. - -[binding]: https://docs.microsoft.com/xamarin/android/platform/binding-java-library/ +[binding]: OneDotNetBindingProjects.md ## .NET Configuration Files diff --git a/Documentation/guides/OneDotNetBindingProjects.md b/Documentation/guides/OneDotNetBindingProjects.md new file mode 100644 index 00000000000..f0881f518e4 --- /dev/null +++ b/Documentation/guides/OneDotNetBindingProjects.md @@ -0,0 +1,221 @@ +# .NET 6 - Xamarin.Android Binding Migration + +## Consolidation of binding projects + +In .NET 6, there is no longer a concept of a [binding +project][binding] as a separate project type. Any of the MSBuild item +groups or build actions that currently work in binding projects will +be supported through a .NET 6 Android application or library. + +For example, a binding library would be identical to a class library: + +```xml + + + net6.0-android + + +``` + +**Note:** It is still recommended that you create separate project(s) for binding your +libraries, just that the project file will look the same as an application. + +[binding]: https://docs.microsoft.com/xamarin/android/platform/binding-java-library/ + +## Unsupported Legacy Options + +The following legacy options are no longer supported. The supported alternatives +have been available for several years, and the smoothest migration option is to update + and test your current projects with these options **first** before migrating them to .NET 6. + +### `` + +`jar2xml` is no longer a valid option for ``. As `class-parse` is +now the only valid option, this setting will no longer affect anything, and `class-parse` +will always be used. + +`class-parse` takes advantage of many new modern features not available in `jar2xml`, such as: + +* Automatic parameter names for class methods (if your Java code is compiled with `javac -parameters`) +* Kotlin support +* Static/Default interface member (DIM) support +* Java Nullable reference type (NRT) annotations support + +### `` + +`XamarinAndroid` is no longer a valid option for ``. `XAJavaInterop1` +is now the default and only supported option. + +If you have hand-bound code in your `Additions` files that interacts with the generated binding +plumbing (which is rare), it may need to be updated to be compatible with `XAJavaInterop1`. + +## Default file inclusion + +File structure: + + Transforms/ + Metadata.xml + foo.jar + +`Transforms\*.xml` files are automatically included as a +`@(TransformFile)` item, and `.jar`/`.aar` files are automatically included +as a `@(AndroidLibrary)` item. + +This will bind C# types for the Java types found in `foo.jar` using +the metadata fixups from `Transforms\Metadata.xml`. + +Default Android related file globbing behavior is defined in [AutoImport.props][default-items]. +This behavior can be disabled for Android items by setting `$(EnableDefaultAndroidItems)` to `false`, or +all default item inclusion behavior can be disabled by setting `$(EnableDefaultItems)` to `false`. + +[default-items]: https://github.com/xamarin/xamarin-android/blob/main/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/Sdk/AutoImport.props + +## Migration Considerations + +There are several new features set by default to help produce new bindings that better match their +Java counterparts. However, if you are migrating an existing binding project, these features may create +bindings that are not API compatible with your existing released bindings. In order to maintain +compatibility, you may wish to disable or modify these new features. + +### Interface Constants + +Traditionally, C# has not allowed constants to be declared in an `interface`, which is a common pattern +in Java code: + +```java +public interface Foo { + public static int BAR = 1; +} +``` + +This pattern was previously supported by creating an alternative `class` that contains the constant(s): + +```csharp +public abstract class Foo : Java.Lang.Object +{ + public static int Bar = 1; +} +``` + +With C# 8, we can now put these constants on the `interface` just like Java: + +```csharp +public interface IFoo { + public static int Bar = 1; +} +``` + +However this means we no longer generate the alternative class that existing code may depend on. + +Setting `false` will revert to the legacy behavior. + +### Nested Interface Types + +Traditionally, C# has not allowed nested types to be declared in an `interface`, which is allowed +in Java code: + +```java +public interface Foo { + public class Bar { } +} +``` + +This pattern was supported by moving the nested type to a top-level type with a generated name composed +of the interface and nested type name: + +```csharp +public interface IFoo { } + +public class IFooBar : Java.Lang.Object { } +``` + +With C# 8, we can now put these nested type in the `interface` just like Java: + +```csharp +public interface IFoo { + public class Bar : Java.Lang.Object { } +} +``` + +However this means we no longer generate the top-level class that existing code may depend on. + +Setting `false` will globally +revert to the old behavior. + +If you wish to use a hybrid approach, for example, to keep existing nested types moved to a top-level +type, but allow any future nested types to remain nested, you can specify this at the `interface` level using +`metadata` to set the `unnest` attribute. + +Setting it to `true` will result in "un-nesting" any nested types (legacy behavior): + +```xml +true +``` + +Setting it to `false` will result in nested types remaining nested in the `interface` (.NET 6 behavior): + +```xml +false +``` + +Using this approach, you could leave `` as `true` and set `unnest` to +`true` for every `interface` with nested types you have **today**. These will always remain top-level +types, while any new nested types introduced later will be nested. + +### Static and Default Interface Members (DIM) + +Traditionally, C# has not allowed interfaces to contain `static` members and `default` methods. + +```java +public interface Foo { + public static void Bar () { ... } + public default void Baz () { ... } +} +``` + +Static members on interfaces has been supported by moving them to a sibling `class`: + +```csharp +public interface IFoo { } + +public class Foo { + public static void Bar () { ... } +} +``` + +`default` interface methods have traditionally not been bound, since they are not required and there +wasn't a C# construct to support them. + +With C# 8, `static` and `default` members are supported on interfaces, mirroring the Java interface: + +```csharp +public interface IFoo { + public static void Bar () { ... } + public default void Baz () { ... } +} +``` + +However this means the alternative sibling `class` containing `static` members will no longer be generated. + +Setting `false` will globally +revert to the old behavior. + + +### Nullable Reference Types + +Support for Nullable Reference Types (NRT) was added in [Xamarin.Android 11.0][11-0-release-notes]. + +This continues to be enabled/disabled using the same mechanism as all .NET projects: + +```xml + + enable + +``` + +As the default for .NET 6 is `disable`, the same applies for Xamarin Android projects. + +Use `enable` as shown above to enable NRT support. + + +[11-0-release-notes]: https://docs.microsoft.com/en-us/xamarin/android/release-notes/11/11.0 diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md index 202d96c6408..06e038b28df 100644 --- a/Documentation/guides/building-apps/build-properties.md +++ b/Documentation/guides/building-apps/build-properties.md @@ -169,6 +169,32 @@ implements a .NET type or interface in terms of Java types, for example Added in Xamarin.Android 10.2. +## AndroidBoundInterfacesContainConstants + +A boolean property that +determines whether binding constants on interfaces will be supported, +or the workaround of creating an `IMyInterfaceConsts` class +will be used. + +Defaults to `True` in .NET 6 and `False` for legacy. + +## AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods + +A boolean property that +whether default and static members on interfaces will be supported, +or old workaround of creating a sibling class containing static +members like `abstract class MyInterface`. + +Defaults to `True` in .NET 6 and `False` for legacy. + +## AndroidBoundInterfacesContainTypes + +A boolean property that +whether types nested in interfaces will be supported, or the workaround +of creating a non-nested type like `IMyInterfaceMyNestedClass`. + +Defaults to `True` in .NET 6 and `False` for legacy. + ## AndroidBuildApplicationPackage A boolean value that @@ -263,6 +289,9 @@ The benefits of `XAJavaInterop1` include: The default value is `XAJavaInterop1`. +Support for `XamarinAndroid` is obsolete, and support for `XamarinAndroid` will be removed +as part of .NET 6. + ## AndroidCreatePackagePerAbi A boolean property that determines if a *set* of files -- on per ABI diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets index 696536dbd59..4d755999ea8 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets @@ -68,7 +68,9 @@ It is shared between "legacy" binding projects and .NET 5 projects. ToolPath="$(MonoAndroidToolsDirectory)" ToolExe="$(BindingsGeneratorToolExe)" LangVersion="$(LangVersion)" - EnableInterfaceMembersPreview="$(_EnableInterfaceMembers)" + EnableBindingStaticAndDefaultInterfaceMethods="$(AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods)" + EnableBindingNestedInterfaceTypes="$(AndroidBoundInterfacesContainTypes)" + EnableBindingInterfaceConstants="$(AndroidBoundInterfacesContainConstants)" Nullable="$(Nullable)" /> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index 205c6414c5d..8dcefaf317b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -14,11 +14,8 @@ false false false - - class-parse false - XAJavaInterop1 + + class-parse + + XAJavaInterop1 + true + true + true + false diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs index f7fc458989c..bea8abcacfc 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs @@ -47,7 +47,9 @@ public class BindingsGenerator : AndroidDotnetToolTask public string LangVersion { get; set; } - public bool EnableInterfaceMembersPreview { get; set; } + public bool EnableBindingStaticAndDefaultInterfaceMethods { get; set; } + public bool EnableBindingNestedInterfaceTypes { get; set; } + public bool EnableBindingInterfaceConstants { get; set; } public string Nullable { get; set; } public ITaskItem[] TransformFiles { get; set; } @@ -172,10 +174,14 @@ protected override string GenerateCommandLineCommands () if (SupportsCSharp8) { var features = new List (); - if (EnableInterfaceMembersPreview) { + if (EnableBindingInterfaceConstants) features.Add ("interface-constants"); + + if (EnableBindingNestedInterfaceTypes) + features.Add ("nested-interface-types"); + + if (EnableBindingStaticAndDefaultInterfaceMethods) features.Add ("default-interface-methods"); - } if (string.Equals (Nullable, "enable", StringComparison.OrdinalIgnoreCase)) features.Add ("nullable-reference-types"); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets index 040a1d53f76..39d627f9cfa 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets @@ -51,6 +51,13 @@ Copyright (C) 2012 Xamarin Inc. All rights reserved. <_AndroidIsBindingProject>True + + + True + True + True + +