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
27 changes: 27 additions & 0 deletions Documentation/release-notes/master.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,35 @@ Description of new feature

* Description of known issue in the new feature, if applicable

### Static/Default Interface Methods (Preview)

With the new support for default interface methods in C# 8.0 we can now produce bindings that better match Java libraries
that use these features. This includes:

* Default interface methods
* Static interface methods
* Interface constants

To enable this preview in your bindings project, add the following properties to your `.csproj`:

```
<LangVersion>preview</LangVersion>
<_EnableInterfaceMembers>True</_EnableInterfaceMembers>
```

Note that enabling this only adds new members, it does not remove the existing alternatives previously used to expose
these methods/fields.


### Build and deployment performance

* Bindings projects should now build considerably faster:
* [GitHub PR 440](https://github.com/xamarin/java.interop/pull/440)
* [GitHub PR 441](https://github.com/xamarin/java.interop/pull/441)
* [GitHub PR 442](https://github.com/xamarin/java.interop/pull/442)
* [GitHub PR 448](https://github.com/xamarin/java.interop/pull/448)
* [GitHub PR 449](https://github.com/xamarin/java.interop/pull/449)
* [GitHub PR 452](https://github.com/xamarin/java.interop/pull/452)
* [GitHub PR nnnn](https://github.com/xamarin/xamarin-android/pull/nnnn):
Description of improvement

Expand Down
29 changes: 29 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public class BindingsGenerator : AndroidToolTask
[Required]
public string MonoAndroidFrameworkDirectories { get; set; }

public string LangVersion { get; set; }

public bool EnableInterfaceMembersPreview { get; set; }

public ITaskItem[] TransformFiles { get; set; }
public ITaskItem[] ReferencedManagedLibraries { get; set; }
public ITaskItem[] AnnotationsZipFiles { get; set; }
Expand Down Expand Up @@ -131,6 +135,9 @@ protected override string GenerateCommandLineCommands ()
if (UseShortFileNames)
cmd.AppendSwitch ("--use-short-file-names");

if (EnableInterfaceMembersPreview && SupportsCSharp8)
cmd.AppendSwitch ("--lang-features=interface-constants,default-interface-methods");

return cmd.ToString ();
}

Expand All @@ -142,5 +149,27 @@ protected override string GenerateFullPathToTool ()
{
return Path.Combine (ToolPath, ToolExe);
}

bool SupportsCSharp8 {
get {
// These are the values that pre-date C# 8. We assume any
// new value we encounter is something that supports it.
switch (LangVersion) {
case "7.3":
case "7.2":
case "7.1":
case "7":
case "6":
case "5":
case "4":
case "3":
case "ISO-2":
case "ISO-1":
return false;
}

return true;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -510,5 +510,63 @@ public void DesignTimeBuild (string classParser)
}
}
}

[Test]
[TestCaseSource (nameof (ClassParseOptions))]
public void BindDefaultInterfaceMethods (string classParser)
{
var proj = new XamarinAndroidBindingProject {
IsRelease = true,
};

// The sources for the .jar is in the jar itself.
string classesJarBase64 = @"
UEsDBBQACAgIANWk6UwAAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAAAA
AAFBLAwQUAAgICADVpOlMAAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0xLLS
7RDUstKs7Mz7NSMNQz4OVyLkpNLElN0XWqBAlY6BnEG5obKmj4FyUm56QqOOcXFeQXJZYA1WvycvFyA
QBQSwcIFGFrLUQAAABFAAAAUEsDBAoAAAgAAK2k6UwAAAAAAAAAAAAAAAAEAAAAY29tL1BLAwQKAAAI
AACtpOlMAAAAAAAAAAAAAAAADAAAAGNvbS94YW1hcmluL1BLAwQKAAAIAACwpOlMAAAAAAAAAAAAAAA
AEQAAAGNvbS94YW1hcmluL3Rlc3QvUEsDBBQACAgIAJmk6UwAAAAAAAAAAAAAAAAuAAAAY29tL3hhbW
FyaW4vdGVzdC9EZWZhdWx0SW50ZXJmYWNlTWV0aG9kcy5jbGFzc3WOvU7DMBSFjxsnKeWnXUE8QLrgh
ScAhBSJnwHE7qQ3JVUSC8dGFc/EwsrAA/BQiOuqLKnw8Pn4+LuWv38+vwCcY5biKMVUIKqMYWbzXEBe
mgUJTG/qju58W5B91EXDTbIkd6HtX3jj0G+DzPL5E28v3q8FJg/G25Ku6zB1ekWV9o3LO0e20iXdkns
2i/5spV+1QFaaVq11q23dKUe9U//4ArMwoRrdLdV9saLSJQICI4QVc4ogmTGfThBugFH0zuR/MpNNE7
x015NDL3C868VDL2XuYbL1joMTjI+BNpYC+/xcaA820uEvUEsHCIw1aijpAAAAhQEAAFBLAwQUAAgIC
ACYpOlMAAAAAAAAAAAAAAAAHAAAAERlZmF1bHRJbnRlcmZhY2VNZXRob2RzLmphdmF1zLEOwiAQBuCd
p7hRl0Zd2YyLgw9xwlGJFCocTWPTdxdSHarxxv///utR3bElUKFrRuwwWt8wJZZC9PnqrALrmaJBRXA
ig9nx+RNciG9BJzEJKKeXtnowIcBmCxNE4hw97CTMP6glPmJcuf1f91y5w7cbgtWQ3rCOBnSZymJhNX
nkPJYnUsziBVBLBwgzfz2miQAAAPUAAABQSwECFAAUAAgICADVpOlMAAAAAAIAAAAAAAAACQAEAAAAA
AAAAAAAAAAAAAAATUVUQS1JTkYv/soAAFBLAQIUABQACAgIANWk6UwUYWstRAAAAEUAAAAUAAAAAAAA
AAAAAAAAAD0AAABNRVRBLUlORi9NQU5JRkVTVC5NRlBLAQIKAAoAAAgAAK2k6UwAAAAAAAAAAAAAAAA
EAAAAAAAAAAAAAAAAAMMAAABjb20vUEsBAgoACgAACAAAraTpTAAAAAAAAAAAAAAAAAwAAAAAAAAAAA
AAAAAA5QAAAGNvbS94YW1hcmluL1BLAQIKAAoAAAgAALCk6UwAAAAAAAAAAAAAAAARAAAAAAAAAAAAA
AAAAA8BAABjb20veGFtYXJpbi90ZXN0L1BLAQIUABQACAgIAJmk6UyMNWoo6QAAAIUBAAAuAAAAAAAA
AAAAAAAAAD4BAABjb20veGFtYXJpbi90ZXN0L0RlZmF1bHRJbnRlcmZhY2VNZXRob2RzLmNsYXNzUEs
BAhQAFAAICAgAmKTpTDN/PaaJAAAA9QAAABwAAAAAAAAAAAAAAAAAgwIAAERlZmF1bHRJbnRlcmZhY2
VNZXRob2RzLmphdmFQSwUGAAAAAAcABwDOAQAAVgMAAAAA
";
proj.Jars.Add (new AndroidItem.EmbeddedJar ("dim.jar") {
BinaryContent = () => Convert.FromBase64String (classesJarBase64)
});

proj.AndroidClassParser = classParser;

proj.SetProperty ("_EnableInterfaceMembers", "True");
proj.SetProperty ("LangVersion", "preview");

using (var b = CreateDllBuilder (Path.Combine ("temp", TestName), false, false)) {
proj.NuGetRestore (b.ProjectDirectory);
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");

string asmpath = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (new Uri (GetType ().Assembly.CodeBase).LocalPath), b.ProjectDirectory, b.Output.OutputPath, (proj.AssemblyName ?? proj.ProjectName) + ".dll"));
Assert.IsTrue (File.Exists (asmpath), "assembly does not exist");

var cs = b.Output.GetIntermediaryAsText (Path.Combine ("generated", "src", "Com.Xamarin.Test.IDefaultInterfaceMethods.cs"));
Assert.IsTrue (cs.Contains ("int Quux ();"), "Quux not generated.");
Assert.IsTrue (cs.Contains ("virtual unsafe int Foo ()"), "Foo not generated.");
Assert.IsTrue (cs.Contains ("virtual unsafe int Bar {"), "Bar not generated.");
Assert.IsTrue (cs.Contains ("set {"), "(Baz) setter not generated.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ Copyright (C) 2012 Xamarin Inc. All rights reserved.
ToolPath="$(MonoAndroidToolsDirectory)"
ToolExe="$(BindingsGeneratorToolExe)"
UseShortFileNames="$(UseShortGeneratorFileNames)"
LangVersion="$(LangVersion)"
EnableInterfaceMembersPreview="$(_EnableInterfaceMembers)"
/>

<!-- Write a flag so we won't redo this target if nothing changed -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using Com.Xamarin.Android;
using Java.Lang;
using NUnit.Framework;

namespace Xamarin.Android.JcwGenTests
{
[TestFixture]
public class DimTest
{
[Test]
public void TestDefaultInterfaceMethods ()
{
var empty = new EmptyOverrideClass ();
var iface = empty as IDefaultMethodsInterface;

Assert.AreEqual (0, iface.Foo ());
Assert.AreEqual (2, iface.Bar);
Assert.DoesNotThrow (() => iface.Bar = 5);

Assert.AreEqual (0, iface.InvokeFoo ());

Assert.Throws<UnsupportedOperationException> (() => iface.ToImplement ());
}

[Test]
public void TestOverriddenDefaultInterfaceMethods ()
{
var over = new ImplementedOverrideClass ();
var iface = over as IDefaultMethodsInterface;

Assert.AreEqual (6, over.Foo ());
Assert.AreEqual (100, over.Bar);
Assert.DoesNotThrow (() => over.Bar = 5);

Assert.AreEqual (6, iface.InvokeFoo ());
}

[Test]
public void TestManagedEmptyDefaultInterfaceMethods ()
{
// Test using empty C# implementing interface
var empty = new ManagedEmptyDefault ();
var iface = empty as IDefaultMethodsInterface;

Assert.AreEqual (0, iface.Foo ());

Assert.AreEqual (0, iface.InvokeFoo ());
}

[Test]
public void TestManagedOverriddenDefaultInterfaceMethods ()
{
// Test using method overridden in C#
var over = new ManagedOverrideDefault ();
var iface = over as IDefaultMethodsInterface;

Assert.AreEqual (15, over.Foo ());
Assert.AreEqual (15, iface.Foo ());

Assert.AreEqual (15, iface.InvokeFoo ());
}

[Test]
public void TestStaticMethods ()
{
Assert.AreEqual (10, IStaticMethodsInterface.Foo ());

Assert.AreEqual (3, IStaticMethodsInterface.Value);
Assert.DoesNotThrow (() => IStaticMethodsInterface.Value = 5);
}

[Test]
public void TestChainedDefaultInterfaceMethods ()
{
var over = new ImplementedChainOverrideClass ();
var iface = over as IDefaultMethodsInterface;

Assert.AreEqual (6, over.Foo ());
Assert.AreEqual (100, over.Bar);
Assert.DoesNotThrow (() => over.Bar = 5);

Assert.AreEqual (6, iface.InvokeFoo ());
}

class ManagedEmptyDefault : Java.Lang.Object, IDefaultMethodsInterface
{
}

class ManagedOverrideDefault : Java.Lang.Object, IDefaultMethodsInterface
{
public int Foo () => 15;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="Xamarin.Android.JcwGen_Tests">
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:label="Xamarin.Android.JcwGen-Tests">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
<AssemblyName>Xamarin.Android.JcwGen-Tests</AssemblyName>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<AndroidSupportedAbis>armeabi-v7a;x86</AndroidSupportedAbis>
<TargetFrameworkVersion>v8.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidUseLatestPlatformSdk>true</AndroidUseLatestPlatformSdk>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<Import Project="..\..\..\Configuration.props" />
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
Expand Down Expand Up @@ -51,6 +52,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BindingTests.cs" />
<Compile Include="DimBindingTests.cs" />
<Compile Include="ExceptionTests.cs" />
<Compile Include="MainActivity.cs" />
<Compile Include="TestInstrumentation.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<AssemblyName>Xamarin.Android.McwGen-Tests</AssemblyName>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<AndroidClassParser>class-parse</AndroidClassParser>
<_JavacSourceVersion>1.8</_JavacSourceVersion>
<_JavacTargetVersion>1.8</_JavacTargetVersion>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<LangVersion>preview</LangVersion>
<_EnableInterfaceMembers>True</_EnableInterfaceMembers>
</PropertyGroup>
<Import Project="..\..\..\Configuration.props" />
<PropertyGroup>
Expand Down Expand Up @@ -113,6 +118,21 @@
<TestJarEntry Include="java\com\xamarin\android\Bxc37706Throwable.java">
<OutputFile>Jars/xamarin-test.jar</OutputFile>
</TestJarEntry>
<TestJarEntry Include="java\com\xamarin\android\DefaultMethodsInterface.java">
<OutputFile>Jars/xamarin-test.jar</OutputFile>
</TestJarEntry>
<TestJarEntry Include="java\com\xamarin\android\EmptyOverrideClass.java">
<OutputFile>Jars/xamarin-test.jar</OutputFile>
</TestJarEntry>
<TestJarEntry Include="java\com\xamarin\android\ImplementedOverrideClass.java">
<OutputFile>Jars/xamarin-test.jar</OutputFile>
</TestJarEntry>
<TestJarEntry Include="java\com\xamarin\android\ImplementedChainOverrideClass.java">
<OutputFile>Jars/xamarin-test.jar</OutputFile>
</TestJarEntry>
<TestJarEntry Include="java\com\xamarin\android\StaticMethodsInterface.java">
<OutputFile>Jars/xamarin-test.jar</OutputFile>
</TestJarEntry>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.Bindings.targets" />
<Import Project="..\..\..\build-tools\scripts\Jar.targets" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.xamarin.android;

public interface DefaultMethodsInterface
{
default int foo () { return 0; }
default int getBar () { return 2; }
default void setBar (int value) { }
default int toImplement () { throw new UnsupportedOperationException (); }
default int invokeFoo () { return foo (); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.xamarin.android;

public class EmptyOverrideClass implements DefaultMethodsInterface
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.xamarin.android;

public class ImplementedChainOverrideClass extends EmptyOverrideClass
{
@Override
public int foo () {
return 6;
}

@Override
public int getBar () {
return 100;
}

@Override
public void setBar (int value) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.xamarin.android;

public class ImplementedOverrideClass implements DefaultMethodsInterface
{
@Override
public int foo () {
return 6;
}

@Override
public int getBar () {
return 100;
}

@Override
public void setBar (int value) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.xamarin.android;

public interface StaticMethodsInterface
{
static int foo () { return 10; }
static int getValue () { return 3; }
static void setValue (int value) { }
}