Skip to content

Commit da12df4

Browse files
authored
[generator] Add [SupportedOSPlatform] in assemblies using ApiSince (#773)
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
1 parent d1b872a commit da12df4

15 files changed

+102
-0
lines changed

tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs

+27
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,39 @@ public void WriteDuplicateInterfaceEventArgs ()
215215

216216
Assert.AreEqual (GetExpected (nameof (WriteDuplicateInterfaceEventArgs)), writer.ToString ().NormalizeLineEndings ());
217217
}
218+
219+
[Test]
220+
public void SupportedOSPlatform ()
221+
{
222+
// We do not write [SupportedOSPlatform] for JavaInterop, only XAJavaInterop
223+
var klass = SupportTypeBuilder.CreateClass ("java.code.MyClass", options);
224+
klass.ApiAvailableSince = 30;
225+
226+
generator.Context.ContextTypes.Push (klass);
227+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
228+
generator.Context.ContextTypes.Pop ();
229+
230+
StringAssert.DoesNotContain ("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain SupportedOSPlatform!");
231+
}
218232
}
219233

220234
[TestFixture]
221235
class XAJavaInteropCodeGeneratorTests : CodeGeneratorTests
222236
{
223237
protected override CodeGenerationTarget Target => CodeGenerationTarget.XAJavaInterop1;
238+
239+
[Test]
240+
public void SupportedOSPlatform ()
241+
{
242+
var klass = SupportTypeBuilder.CreateClass ("java.code.MyClass", options);
243+
klass.ApiAvailableSince = 30;
244+
245+
generator.Context.ContextTypes.Push (klass);
246+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
247+
generator.Context.ContextTypes.Pop ();
248+
249+
StringAssert.Contains ("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain SupportedOSPlatform!");
250+
}
224251
}
225252

226253
[TestFixture]

tools/generator/Java.Interop.Tools.Generator.ObjectModel/NamespaceMapping.cs

+15
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ public void Generate (CodeGenerationOptions opt, GenerationInfo gen_info)
3131
// delegate bool _JniMarshal_PPL_Z (IntPtr jnienv, IntPtr klass, IntPtr a);
3232
foreach (var jni in opt.GetJniMarshalDelegates ())
3333
sw.WriteLine ($"delegate {FromJniType (jni[jni.Length - 1])} {jni} (IntPtr jnienv, IntPtr klass{GetDelegateParameters (jni)});");
34+
35+
// [SupportedOSPlatform] only exists in .NET 5.0+, so we need to generate a
36+
// dummy one so earlier frameworks can compile.
37+
if (opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1) {
38+
sw.WriteLine ("#if !NET5_0_OR_GREATER");
39+
sw.WriteLine ("namespace System.Runtime.Versioning {");
40+
sw.WriteLine (" [System.Diagnostics.Conditional(\"NEVER\")]");
41+
sw.WriteLine (" [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Module | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]");
42+
sw.WriteLine (" internal sealed class SupportedOSPlatformAttribute : Attribute {");
43+
sw.WriteLine (" public SupportedOSPlatformAttribute (string platformName) { }");
44+
sw.WriteLine (" }");
45+
sw.WriteLine ("}");
46+
sw.WriteLine ("#endif");
47+
sw.WriteLine ("");
48+
}
3449
}
3550
}
3651

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xamarin.SourceWriter;
7+
8+
namespace generator.SourceWriters
9+
{
10+
public class SupportedOSPlatformAttr : AttributeWriter
11+
{
12+
public int Version { get; }
13+
14+
public SupportedOSPlatformAttr (int version) => Version = version;
15+
16+
public override void WriteAttribute (CodeWriter writer)
17+
{
18+
writer.WriteLine ($"[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android{Version}.0\")]");
19+
}
20+
}
21+
}

tools/generator/SourceWriters/BoundAbstractProperty.cs

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public BoundAbstractProperty (GenBase gen, Property property, CodeGenerationOpti
4141
if (property.Getter.IsReturnEnumified)
4242
GetterAttributes.Add (new GeneratedEnumAttr (true));
4343

44+
SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);
45+
4446
GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.GetConnectorNameFull (opt), additionalProperties: property.Getter.AdditionalAttributeString ()));
4547

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

56+
SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);
57+
5458
SourceWriterExtensions.AddMethodCustomAttributes (SetterAttributes, property.Setter);
5559
SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.GetConnectorNameFull (opt), additionalProperties: property.Setter.AdditionalAttributeString ()));
5660
}

tools/generator/SourceWriters/BoundClass.cs

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public BoundClass (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorConte
4545
if (klass.IsDeprecated)
4646
Attributes.Add (new ObsoleteAttr (klass.DeprecatedComment) { WriteAttributeSuffix = true });
4747

48+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, klass, opt);
49+
4850
Attributes.Add (new RegisterAttr (klass.RawJniName, null, null, true, klass.AdditionalAttributeString ()) { UseGlobal = true, UseShortForm = true });
4951

5052
if (klass.TypeParameters != null && klass.TypeParameters.Any ())

tools/generator/SourceWriters/BoundConstructor.cs

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public BoundConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGen
2727
constructor.JavadocInfo?.AddJavadocs (Comments);
2828
Comments.Add (string.Format ("// Metadata.xml XPath constructor reference: path=\"{0}/constructor[@name='{1}'{2}]\"", klass.MetadataXPathReference, klass.JavaSimpleName, constructor.Parameters.GetMethodXPathPredicate ()));
2929

30+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, constructor, opt);
31+
3032
Attributes.Add (new RegisterAttr (".ctor", constructor.JniSignature, string.Empty, additionalProperties: constructor.AdditionalAttributeString ()));
3133

3234
if (constructor.Deprecated != null)

tools/generator/SourceWriters/BoundFieldAsProperty.cs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op
3131
if (field.IsEnumified)
3232
Attributes.Add (new GeneratedEnumAttr ());
3333

34+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, field, opt);
35+
3436
Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ()));
3537

3638
if (field.IsDeprecated)

tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener
3131
if (method.IsInterfaceDefaultMethod)
3232
Attributes.Add (new CustomAttr ("[global::Java.Interop.JavaInterfaceDefaultMethod]"));
3333

34+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt);
35+
3436
Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName + ":" + method.GetAdapterName (opt, adapter), additionalProperties: method.AdditionalAttributeString ()));
3537

3638
method.JavadocInfo?.AddJavadocs (Comments);

tools/generator/SourceWriters/BoundInterfacePropertyDeclaration.cs

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public BoundInterfacePropertyDeclaration (GenBase gen, Property property, string
2525
if (property.Getter.GenericArguments?.Any () == true)
2626
GetterAttributes.Add (new CustomAttr (property.Getter.GenericArguments.ToGeneratedAttributeString ()));
2727

28+
SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);
29+
2830
GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName + ":" + property.Getter.GetAdapterName (opt, adapter), additionalProperties: property.Getter.AdditionalAttributeString ()));
2931
}
3032

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

41+
SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);
42+
3943
SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName + ":" + property.Setter.GetAdapterName (opt, adapter), additionalProperties: property.Setter.AdditionalAttributeString ()));
4044
}
4145
}

tools/generator/SourceWriters/BoundMethod.cs

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool
7070
if (method.IsReturnEnumified)
7171
Attributes.Add (new GeneratedEnumAttr (true));
7272

73+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt);
74+
7375
Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.IsVirtual ? method.GetConnectorNameFull (opt) : string.Empty, additionalProperties: method.AdditionalAttributeString ()));
7476

7577
SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method);

tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public BoundMethodAbstractDeclaration (GenBase gen, Method method, CodeGeneratio
4646
if (method.DeclaringType.IsGeneratable)
4747
Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\"");
4848

49+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt);
50+
4951
Attributes.Add (new RegisterAttr (method.JavaName, method.JniSignature, method.ConnectorName, additionalProperties: method.AdditionalAttributeString ()));
5052

5153
SourceWriterExtensions.AddMethodCustomAttributes (Attributes, method);

tools/generator/SourceWriters/BoundProperty.cs

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public BoundProperty (GenBase gen, Property property, CodeGenerationOptions opt,
7474
if (gen.IsGeneratable)
7575
GetterComments.Add ($"// Metadata.xml XPath method reference: path=\"{gen.MetadataXPathReference}/method[@name='{property.Getter.JavaName}'{property.Getter.Parameters.GetMethodXPathPredicate ()}]\"");
7676

77+
SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);
78+
7779
GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.IsVirtual ? property.Getter.GetConnectorNameFull (opt) : string.Empty, additionalProperties: property.Getter.AdditionalAttributeString ()));
7880

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

89+
SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);
90+
8791
SourceWriterExtensions.AddMethodCustomAttributes (SetterAttributes, property.Setter);
8892
SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.IsVirtual ? property.Setter.GetConnectorNameFull (opt) : string.Empty, additionalProperties: property.Setter.AdditionalAttributeString ()));
8993

tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs

+9
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,15 @@ public static void AddParameterListCallArgs (List<string> body, ParameterList pa
285285
}
286286
}
287287

288+
public static void AddSupportedOSPlatform (List<AttributeWriter> attributes, ApiVersionsSupport.IApiAvailability member, CodeGenerationOptions opt)
289+
{
290+
// There's no sense in writing say 'android15' because we do not support older APIs,
291+
// so those APIs will be available in all of our versions.
292+
if (member.ApiAvailableSince > 21 && opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1)
293+
attributes.Add (new SupportedOSPlatformAttr (member.ApiAvailableSince));
294+
295+
}
296+
288297
public static void WriteMethodInvokerBody (CodeWriter writer, Method method, CodeGenerationOptions opt, string contextThis)
289298
{
290299
writer.WriteLine ($"if ({method.EscapedIdName} == IntPtr.Zero)");

tools/generator/SourceWriters/GenericExplicitInterfaceImplementationProperty.cs

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public GenericExplicitInterfaceImplementationProperty (Property property, Generi
2727
if (property.Getter.GenericArguments != null && property.Getter.GenericArguments.Any ())
2828
GetterAttributes.Add (new CustomAttr (property.Getter.GenericArguments.ToGeneratedAttributeString ()));
2929

30+
SourceWriterExtensions.AddSupportedOSPlatform (GetterAttributes, property.Getter, opt);
31+
3032
GetterAttributes.Add (new RegisterAttr (property.Getter.JavaName, property.Getter.JniSignature, property.Getter.ConnectorName + ":" + property.Getter.GetAdapterName (opt, adapter), additionalProperties: property.Getter.AdditionalAttributeString ()));
3133

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

45+
SourceWriterExtensions.AddSupportedOSPlatform (SetterAttributes, property.Setter, opt);
46+
4347
SetterAttributes.Add (new RegisterAttr (property.Setter.JavaName, property.Setter.JniSignature, property.Setter.ConnectorName + ":" + property.Setter.GetAdapterName (opt, adapter), additionalProperties: property.Setter.AdditionalAttributeString ()));
4448

4549
// Temporarily rename the parameter to "value"

tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public InterfaceMemberAlternativeClass (InterfaceGen iface, CodeGenerationOption
3636

3737
UsePriorityOrder = true;
3838

39+
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, iface, opt);
40+
3941
Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { AcwLast = true });
4042

4143
if (should_obsolete)

0 commit comments

Comments
 (0)