Skip to content

Commit

Permalink
[generator] Add [SupportedOSPlatform] in assemblies using ApiSince (#773
Browse files Browse the repository at this point in the history
)

Context: dotnet/android#5338

.NET 5 provides a new
[`System.Runtime.Versioning.SupportedOSPlatformAttribute`][0] custom
attribute which is used to specify on which platforms and platform
versions an API is available.  This is used to build analyzers to give
users warnings if they are trying to use an API when it will not be
available on their target platform.

`SupportedOSPlatformAttribute` is fundamentally the same as our
existing `RegisterAttribute.ApiSince` property, except tooling has
actually been built to consume the information.

As such, we need `generator` support to put this information into
`Mono.Android.dll`:

	partial class Activity {
	    // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='dismissKeyboardShortcutsHelper' and count(parameter)=0]"
	    [global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android24.0")]
	    [Register ("dismissKeyboardShortcutsHelper", "()V", "", ApiSince = 24)]
	    public unsafe void DismissKeyboardShortcutsHelper () { … }
	}


Some interesting notes:

  - `SupportedOSPlatformAttribute` is only available in .NET 5+, so
    we include a local version for earlier frameworks so we can
    compile without needing to `#ifdef` every attribute use.

  - The local version is marked as `[Conditional ("NEVER")]` so the
    attributes will not actually get compiled into the resulting
    pre-NET5.0 assembly.

  - The attribute cannot be placed on interfaces or fields, so we
    aren't able to annotate them.

  - Our minimum supported API for .NET 6 is 21, so we only write
    attributes for API added in versions newer than 21, as API added
    earlier are always available.

[0]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.versioning.supportedosplatformattribute?view=net-5.0
  • Loading branch information
jpobst committed Jan 11, 2021
1 parent d1b872a commit da12df4
Show file tree
Hide file tree
Showing 15 changed files with 102 additions and 0 deletions.
27 changes: 27 additions & 0 deletions tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,39 @@ public void WriteDuplicateInterfaceEventArgs ()

Assert.AreEqual (GetExpected (nameof (WriteDuplicateInterfaceEventArgs)), writer.ToString ().NormalizeLineEndings ());
}

[Test]
public void SupportedOSPlatform ()
{
// We do not write [SupportedOSPlatform] for JavaInterop, only XAJavaInterop
var klass = SupportTypeBuilder.CreateClass ("java.code.MyClass", options);
klass.ApiAvailableSince = 30;

generator.Context.ContextTypes.Push (klass);
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain SupportedOSPlatform!");
}
}

[TestFixture]
class XAJavaInteropCodeGeneratorTests : CodeGeneratorTests
{
protected override CodeGenerationTarget Target => CodeGenerationTarget.XAJavaInterop1;

[Test]
public void SupportedOSPlatform ()
{
var klass = SupportTypeBuilder.CreateClass ("java.code.MyClass", options);
klass.ApiAvailableSince = 30;

generator.Context.ContextTypes.Push (klass);
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

StringAssert.Contains ("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain SupportedOSPlatform!");
}
}

[TestFixture]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ public void Generate (CodeGenerationOptions opt, GenerationInfo gen_info)
// delegate bool _JniMarshal_PPL_Z (IntPtr jnienv, IntPtr klass, IntPtr a);
foreach (var jni in opt.GetJniMarshalDelegates ())
sw.WriteLine ($"delegate {FromJniType (jni[jni.Length - 1])} {jni} (IntPtr jnienv, IntPtr klass{GetDelegateParameters (jni)});");

// [SupportedOSPlatform] only exists in .NET 5.0+, so we need to generate a
// dummy one so earlier frameworks can compile.
if (opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1) {
sw.WriteLine ("#if !NET5_0_OR_GREATER");
sw.WriteLine ("namespace System.Runtime.Versioning {");
sw.WriteLine (" [System.Diagnostics.Conditional(\"NEVER\")]");
sw.WriteLine (" [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Module | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]");
sw.WriteLine (" internal sealed class SupportedOSPlatformAttribute : Attribute {");
sw.WriteLine (" public SupportedOSPlatformAttribute (string platformName) { }");
sw.WriteLine (" }");
sw.WriteLine ("}");
sw.WriteLine ("#endif");
sw.WriteLine ("");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.SourceWriter;

namespace generator.SourceWriters
{
public class SupportedOSPlatformAttr : AttributeWriter
{
public int Version { get; }

public SupportedOSPlatformAttr (int version) => Version = version;

public override void WriteAttribute (CodeWriter writer)
{
writer.WriteLine ($"[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android{Version}.0\")]");
}
}
}
4 changes: 4 additions & 0 deletions tools/generator/SourceWriters/BoundAbstractProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public BoundAbstractProperty (GenBase gen, Property property, CodeGenerationOpti
if (property.Getter.IsReturnEnumified)
GetterAttributes.Add (new GeneratedEnumAttr (true));

SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);

GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.GetConnectorNameFull (opt), additionalProperties: property.Getter.AdditionalAttributeString ()));

SourceWriterExtensions.AddMethodCustomAttributes (GetterAttributes, property.Getter);
Expand All @@ -51,6 +53,8 @@ public BoundAbstractProperty (GenBase gen, Property property, CodeGenerationOpti
if (gen.IsGeneratable)
SetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Setter.JavaName}'{property.Setter.Parameters.GetMethodXPathPredicate ()}]\"");

SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);

SourceWriterExtensions.AddMethodCustomAttributes (SetterAttributes, property.Setter);
SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.GetConnectorNameFull (opt), additionalProperties: property.Setter.AdditionalAttributeString ()));
}
Expand Down
2 changes: 2 additions & 0 deletions tools/generator/SourceWriters/BoundClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public BoundClass (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorConte
if (klass.IsDeprecated)
Attributes.Add (new ObsoleteAttr (klass.DeprecatedComment) { WriteAttributeSuffix = true });

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, klass, opt);

Attributes.Add (new RegisterAttr (klass.RawJniName, null, null, true, klass.AdditionalAttributeString ()) { UseGlobal = true, UseShortForm = true });

if (klass.TypeParameters != null && klass.TypeParameters.Any ())
Expand Down
2 changes: 2 additions & 0 deletions tools/generator/SourceWriters/BoundConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public BoundConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGen
constructor.JavadocInfo?.AddJavadocs (Comments);
Comments.Add (string.Format ("// Metadata.xml XPath constructor reference: path=\"{0}/constructor[@name='{1}'{2}]\"", klass.MetadataXPathReference, klass.JavaSimpleName, constructor.Parameters.GetMethodXPathPredicate ()));

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, constructor, opt);

Attributes.Add (new RegisterAttr (".ctor", constructor.JniSignature, string.Empty, additionalProperties: constructor.AdditionalAttributeString ()));

if (constructor.Deprecated != null)
Expand Down
2 changes: 2 additions & 0 deletions tools/generator/SourceWriters/BoundFieldAsProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op
if (field.IsEnumified)
Attributes.Add (new GeneratedEnumAttr ());

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, field, opt);

Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ()));

if (field.IsDeprecated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener
if (method.IsInterfaceDefaultMethod)
Attributes.Add (new CustomAttr ("[global::Java.Interop.JavaInterfaceDefaultMethod]"));

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt);

Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName + ":" + method.GetAdapterName (opt, adapter), additionalProperties: method.AdditionalAttributeString ()));

method.JavadocInfo?.AddJavadocs (Comments);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public BoundInterfacePropertyDeclaration (GenBase gen, Property property, string
if (property.Getter.GenericArguments?.Any () == true)
GetterAttributes.Add (new CustomAttr (property.Getter.GenericArguments.ToGeneratedAttributeString ()));

SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);

GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName + ":" + property.Getter.GetAdapterName (opt, adapter), additionalProperties: property.Getter.AdditionalAttributeString ()));
}

Expand All @@ -36,6 +38,8 @@ public BoundInterfacePropertyDeclaration (GenBase gen, Property property, string
if (property.Setter.GenericArguments?.Any () == true)
SetterAttributes.Add (new CustomAttr (property.Setter.GenericArguments.ToGeneratedAttributeString ()));

SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);

SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName + ":" + property.Setter.GetAdapterName (opt, adapter), additionalProperties: property.Setter.AdditionalAttributeString ()));
}
}
Expand Down
2 changes: 2 additions & 0 deletions tools/generator/SourceWriters/BoundMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool
if (method.IsReturnEnumified)
Attributes.Add (new GeneratedEnumAttr (true));

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt);

Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.IsVirtual ? method.GetConnectorNameFull (opt) : string.Empty, additionalProperties: method.AdditionalAttributeString ()));

SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public BoundMethodAbstractDeclaration (GenBase gen, Method method, CodeGeneratio
if (method.DeclaringType.IsGeneratable)
Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\"");

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt);

Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName, additionalProperties: method.AdditionalAttributeString ()));

SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method);
Expand Down
4 changes: 4 additions & 0 deletions tools/generator/SourceWriters/BoundProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public BoundProperty (GenBase gen, Property property, CodeGenerationOptions opt,
if (gen.IsGeneratable)
GetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Getter.JavaName}'{property.Getter.Parameters.GetMethodXPathPredicate ()}]\"");

SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);

GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.IsVirtual ? property.Getter.GetConnectorNameFull (opt) : string.Empty, additionalProperties: property.Getter.AdditionalAttributeString ()));

SourceWriterExtensions.AddMethodBody (GetBody, property.Getter, opt);
Expand All @@ -84,6 +86,8 @@ public BoundProperty (GenBase gen, Property property, CodeGenerationOptions opt,
if (gen.IsGeneratable)
SetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Setter.JavaName}'{property.Setter.Parameters.GetMethodXPathPredicate ()}]\"");

SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);

SourceWriterExtensions.AddMethodCustomAttributes (SetterAttributes, property.Setter);
SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.IsVirtual ? property.Setter.GetConnectorNameFull (opt) : string.Empty, additionalProperties: property.Setter.AdditionalAttributeString ()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ public static void AddParameterListCallArgs (List<string> body, ParameterList pa
}
}

public static void AddSupportedOSPlatform (List<AttributeWriter> attributes, ApiVersionsSupport.IApiAvailability member, CodeGenerationOptions opt)
{
// There's no sense in writing say 'android15' because we do not support older APIs,
// so those APIs will be available in all of our versions.
if (member.ApiAvailableSince > 21 && opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1)
attributes.Add (new SupportedOSPlatformAttr (member.ApiAvailableSince));

}

public static void WriteMethodInvokerBody (CodeWriter writer, Method method, CodeGenerationOptions opt, string contextThis)
{
writer.WriteLine ($"if ({method.EscapedIdName} == IntPtr.Zero)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public GenericExplicitInterfaceImplementationProperty (Property property, Generi
if (property.Getter.GenericArguments != null && property.Getter.GenericArguments.Any ())
GetterAttributes.Add (new CustomAttr (property.Getter.GenericArguments.ToGeneratedAttributeString ()));

SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);

GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName + ":" + property.Getter.GetAdapterName (opt, adapter), additionalProperties: property.Getter.AdditionalAttributeString ()));

GetBody.Add ($"return {property.Name};");
Expand All @@ -40,6 +42,8 @@ public GenericExplicitInterfaceImplementationProperty (Property property, Generi
if (property.Setter.GenericArguments != null && property.Setter.GenericArguments.Any ())
SetterAttributes.Add (new CustomAttr (property.Setter.GenericArguments.ToGeneratedAttributeString ()));

SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);

SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName + ":" + property.Setter.GetAdapterName (opt, adapter), additionalProperties: property.Setter.AdditionalAttributeString ()));

// Temporarily rename the parameter to "value"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public InterfaceMemberAlternativeClass (InterfaceGen iface, CodeGenerationOption

UsePriorityOrder = true;

SourceWriterExtensions.AddSupportedOSPlatform (Attributes, iface, opt);

Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { AcwLast = true });

if (should_obsolete)
Expand Down

0 comments on commit da12df4

Please sign in to comment.