diff --git a/Documentation/release-notes/master.md b/Documentation/release-notes/master.md index 5a78f7c719d..14e9bff6d28 100644 --- a/Documentation/release-notes/master.md +++ b/Documentation/release-notes/master.md @@ -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`: + +``` +preview +<_EnableInterfaceMembers>True +``` + +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 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs index f0a189ecf7b..f1ecbe93522 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Generator.cs @@ -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; } @@ -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 (); } @@ -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; + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs index 1333a761455..8c65c8373cb 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs @@ -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."); + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets index 8afb3eac59e..0b5dd75d422 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets @@ -494,6 +494,8 @@ Copyright (C) 2012 Xamarin Inc. All rights reserved. ToolPath="$(MonoAndroidToolsDirectory)" ToolExe="$(BindingsGeneratorToolExe)" UseShortFileNames="$(UseShortGeneratorFileNames)" + LangVersion="$(LangVersion)" + EnableInterfaceMembersPreview="$(_EnableInterfaceMembers)" /> diff --git a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/DimBindingTests.cs b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/DimBindingTests.cs new file mode 100644 index 00000000000..accd2db949a --- /dev/null +++ b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/DimBindingTests.cs @@ -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 (() => 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; + } + } +} diff --git a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Properties/AndroidManifest.xml b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Properties/AndroidManifest.xml index 92da57bf571..78bc48f3b95 100644 --- a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Properties/AndroidManifest.xml +++ b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Properties/AndroidManifest.xml @@ -1,6 +1,6 @@ - + diff --git a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj index f09792aa5da..634a17f7402 100644 --- a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj +++ b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj @@ -17,9 +17,10 @@ Xamarin.Android.JcwGen-Tests Properties\AndroidManifest.xml armeabi-v7a;x86 - v8.1 + v9.0 false true + preview @@ -51,6 +52,7 @@ + diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj index e606aa04730..cbcae2e0410 100644 --- a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj +++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj @@ -14,6 +14,11 @@ Xamarin.Android.McwGen-Tests False class-parse + <_JavacSourceVersion>1.8 + <_JavacTargetVersion>1.8 + v9.0 + preview + <_EnableInterfaceMembers>True @@ -113,6 +118,21 @@ Jars/xamarin-test.jar + + Jars/xamarin-test.jar + + + Jars/xamarin-test.jar + + + Jars/xamarin-test.jar + + + Jars/xamarin-test.jar + + + Jars/xamarin-test.jar + diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/DefaultMethodsInterface.java b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/DefaultMethodsInterface.java new file mode 100644 index 00000000000..2ac218c652b --- /dev/null +++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/DefaultMethodsInterface.java @@ -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 (); } +} diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/EmptyOverrideClass.java b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/EmptyOverrideClass.java new file mode 100644 index 00000000000..d647bfa1dc6 --- /dev/null +++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/EmptyOverrideClass.java @@ -0,0 +1,5 @@ +package com.xamarin.android; + +public class EmptyOverrideClass implements DefaultMethodsInterface +{ +} diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/ImplementedChainOverrideClass.java b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/ImplementedChainOverrideClass.java new file mode 100644 index 00000000000..ad1c1d6385c --- /dev/null +++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/ImplementedChainOverrideClass.java @@ -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) { + } +} diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/ImplementedOverrideClass.java b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/ImplementedOverrideClass.java new file mode 100644 index 00000000000..d5079f198e5 --- /dev/null +++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/ImplementedOverrideClass.java @@ -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) { + } +} diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/StaticMethodsInterface.java b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/StaticMethodsInterface.java new file mode 100644 index 00000000000..9755d39deab --- /dev/null +++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/StaticMethodsInterface.java @@ -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) { } +}