Skip to content

Commit

Permalink
[One .NET] Update .NET 6 defaults for bindings projects (#6066)
Browse files Browse the repository at this point in the history
Fixes: #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.
  • Loading branch information
jpobst committed Jul 14, 2021
1 parent fe98dee commit d60e717
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 36 deletions.
32 changes: 3 additions & 29 deletions Documentation/guides/OneDotNet.md
Expand Up @@ -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
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-android</TargetFramework>
</PropertyGroup>
</Project>
```

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

Expand Down
221 changes: 221 additions & 0 deletions 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
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-android</TargetFramework>
</PropertyGroup>
</Project>
```

**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.

### `<AndroidClassParser>`

`jar2xml` is no longer a valid option for `<AndroidClassParser>`. 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

### `<AndroidCodegenTarget>`

`XamarinAndroid` is no longer a valid option for `<AndroidCodegenTarget>`. `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 `<AndroidBoundInterfacesContainConstants>false</AndroidBoundInterfacesContainConstants>` 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 `<AndroidBoundInterfacesContainTypes>false</AndroidBoundInterfacesContainTypes>` 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
<attr path="/api/package[@name='my.package']/interface[@name='Foo']" name="unnest">true</attr>
```

Setting it to `false` will result in nested types remaining nested in the `interface` (.NET 6 behavior):

```xml
<attr path="/api/package[@name='my.package']/interface[@name='Foo']" name="unnest">false</attr>
```

Using this approach, you could leave `<AndroidBoundInterfacesContainTypes>` 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 `<AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods>false</AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods>` 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
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
```

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
29 changes: 29 additions & 0 deletions Documentation/guides/building-apps/build-properties.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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)"
/>

Expand Down
Expand Up @@ -14,18 +14,24 @@
<GenerateDependencyFile Condition=" '$(GenerateDependencyFile)' == '' ">false</GenerateDependencyFile>
<CopyLocalLockFileAssemblies Condition=" '$(CopyLocalLockFileAssemblies)' == '' ">false</CopyLocalLockFileAssemblies>
<ComputeNETCoreBuildOutputFiles Condition=" '$(ComputeNETCoreBuildOutputFiles)' == '' ">false</ComputeNETCoreBuildOutputFiles>
<!-- jar2xml is not supported -->
<AndroidClassParser>class-parse</AndroidClassParser>
<!-- mono-symbolicate is not supported -->
<MonoSymbolArchive>false</MonoSymbolArchive>
<AndroidCodegenTarget Condition=" '$(AndroidCodegenTarget)' == '' ">XAJavaInterop1</AndroidCodegenTarget>
<!--
Disable @(Content) from referenced projects
See: https://github.com/dotnet/sdk/blob/955c0fc7b06e2fa34bacd076ed39f61e4fb61716/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L16
-->
<_GetChildProjectCopyToPublishDirectoryItems>false</_GetChildProjectCopyToPublishDirectoryItems>
<UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' ">true</UseMonoRuntime>

<!-- Bindings properties -->
<!-- jar2xml is not supported -->
<AndroidClassParser>class-parse</AndroidClassParser>
<!-- Anything other than XAJavaInterop1 is not supported -->
<AndroidCodegenTarget>XAJavaInterop1</AndroidCodegenTarget>
<AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods Condition=" '$(AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods)' == '' ">true</AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods>
<AndroidBoundInterfacesContainTypes Condition=" '$(AndroidBoundInterfacesContainTypes)' == '' ">true</AndroidBoundInterfacesContainTypes>
<AndroidBoundInterfacesContainConstants Condition=" '$(AndroidBoundInterfacesContainConstants)' == '' ">true</AndroidBoundInterfacesContainConstants>

<!-- Mono components -->
<AndroidEnableProfiler Condition=" '$(AndroidEnableProfiler)' == ''">false</AndroidEnableProfiler>
</PropertyGroup>
Expand Down
12 changes: 9 additions & 3 deletions src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs
Expand Up @@ -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; }
Expand Down Expand Up @@ -172,10 +174,14 @@ protected override string GenerateCommandLineCommands ()
if (SupportsCSharp8) {
var features = new List<string> ();

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");
Expand Down
Expand Up @@ -51,6 +51,13 @@ Copyright (C) 2012 Xamarin Inc. All rights reserved.
<_AndroidIsBindingProject>True</_AndroidIsBindingProject>
</PropertyGroup>

<!-- Convert legacy DIM preview flag into new Enable* flags -->
<PropertyGroup Condition="'$(_EnableInterfaceMembers)' == 'True'">
<AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods Condition=" '$(AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods)' == '' ">True</AndroidBoundInterfacesContainStaticAndDefaultInterfaceMethods>
<AndroidBoundInterfacesContainTypes Condition=" '$(AndroidBoundInterfacesContainTypes)' == '' ">True</AndroidBoundInterfacesContainTypes>
<AndroidBoundInterfacesContainConstants Condition=" '$(AndroidBoundInterfacesContainConstants)' == '' ">True</AndroidBoundInterfacesContainConstants>
</PropertyGroup>

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

<PropertyGroup>
Expand Down

0 comments on commit d60e717

Please sign in to comment.