Skip to content

Commit d3ea180

Browse files
authored
[generator] Add support for [ObsoletedOSPlatform] (#1026)
Context: dotnet/android#7234 Add support for a new option `generator --lang-features=obsoleted-platform-attributes`. When used, for an API that was obsoleted in API levels greater than our .NET 7 minimum (API-21), we will generate .NET 7's new `[ObsoletedOSPlatform]` attribute *instead of* `[Obsolete]`: // New [global::System.Runtime.Versioning.ObsoletedOSPlatform ("android22.0", @"This class is obsoleted in this android platform")] public partial class CookieSpecParamBean : Org.Apache.Http.Params.HttpAbstractParamBean { } // Previous [global::System.Obsolete (@"This class is obsoleted in this android platform")] public partial class CookieSpecParamBean : Org.Apache.Http.Params.HttpAbstractParamBean { } This is useful in a .NET 7+ context because we always *compile* against the "latest" `Mono.Android`, even if you are *targeting* an earlier version. For example, the use of `[Obsolete]` means that using `CookieSpecParamBean` would always result in a CS0618 obsolete usage warning, even when building with `$(SupportedOSPlatformVersion)`=21. (`CookieSpecParamBean` was obsoleted in API-22.)
1 parent 6d1ae4e commit d3ea180

26 files changed

+180
-27
lines changed

src/utils/XmlExtensions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,18 @@ public static string XGetAttribute (this XPathNavigator nav, string name, string
2020
var attr = nav.GetAttribute (name, ns);
2121
return attr != null ? attr.Trim () : null;
2222
}
23+
24+
public static int? XGetAttributeAsIntOrNull (this XElement element, string name)
25+
{
26+
var attr = element.Attribute (name);
27+
28+
if (attr?.Value is null)
29+
return null;
30+
31+
if (int.TryParse (attr.Value, out var val))
32+
return val;
33+
34+
return null;
35+
}
2336
}
2437
}

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,86 @@ public void ObsoleteBoundMethodAbstractDeclaration ()
531531
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is so old!\")]"), writer.ToString ());
532532
}
533533

534+
[Test]
535+
public void ObsoletedOSPlatformAttributeSupport ()
536+
{
537+
var xml = @"<api>
538+
<package name='java.lang' jni-name='java/lang'>
539+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
540+
</package>
541+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
542+
<class abstract='false' deprecated='This is a class deprecated since 25!' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' deprecated-since='25'>
543+
<field deprecated='This is a field deprecated since 25!' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false' deprecated-since='25'></field>
544+
<constructor deprecated='This is a constructor deprecated since 25!' final='false' name='MyClass' jni-signature='()V' bridge='false' static='false' type='com.xamarin.android.MyClass' synthetic='false' visibility='public' deprecated-since='25'></constructor>
545+
<method abstract='true' deprecated='This is a method deprecated since 25!' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
546+
<method abstract='false' deprecated='This is a property getter deprecated since 25!' final='false' name='getCount' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
547+
<method abstract='false' deprecated='This is a property setter deprecated since 25!' final='false' name='setCount' jni-signature='(I)V' bridge='false' native='false' return='void' jni-return='V' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'>
548+
<parameter name='count' type='int' jni-type='I'></parameter>
549+
</method>
550+
</class>
551+
</package>
552+
</api>";
553+
554+
options.UseObsoletedOSPlatformAttributes = true;
555+
556+
var gens = ParseApiDefinition (xml);
557+
var iface = gens.Single (g => g.Name == "MyClass");
558+
559+
generator.Context.ContextTypes.Push (iface);
560+
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
561+
generator.Context.ContextTypes.Pop ();
562+
563+
// Ensure [ObsoletedOSPlatform] was written
564+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a class deprecated since 25!\")]"), writer.ToString ());
565+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a field deprecated since 25!\")]"), writer.ToString ());
566+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a constructor deprecated since 25!\")]"), writer.ToString ());
567+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a method deprecated since 25!\")]"), writer.ToString ());
568+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a property getter deprecated since 25! This is a property setter deprecated since 25!\")]"), writer.ToString ());
569+
}
570+
571+
[Test]
572+
public void ObsoletedOSPlatformAttributeUnneededSupport ()
573+
{
574+
var xml = @"<api>
575+
<package name='java.lang' jni-name='java/lang'>
576+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
577+
</package>
578+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
579+
<class abstract='false' deprecated='This is a class deprecated since 19!' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;' deprecated-since='19'>
580+
<field deprecated='This is a field deprecated since 0!' final='true' name='ACCEPT_HANDOVER' jni-signature='Ljava/lang/String;' static='true' transient='false' type='java.lang.String' type-generic-aware='java.lang.String' value='&quot;android.permission.ACCEPT_HANDOVER&quot;' visibility='public' volatile='false' deprecated-since='0'></field>
581+
<constructor deprecated='This is a constructor deprecated since empty string!' final='false' name='MyClass' jni-signature='()V' bridge='false' static='false' type='com.xamarin.android.MyClass' synthetic='false' visibility='public' deprecated-since=''></constructor>
582+
<method abstract='true' deprecated='deprecated' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='25'></method>
583+
<method abstract='true' deprecated='This method has an invalid deprecated-since!' final='false' name='countAffectedRows2' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='foo'></method>
584+
<method abstract='false' deprecated='deprecated' final='false' name='getCount' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='22'></method>
585+
<method abstract='false' deprecated='deprecated' final='false' name='setCount' jni-signature='(I)V' bridge='false' native='false' return='void' jni-return='V' static='false' synchronized='false' synthetic='false' visibility='public' deprecated-since='22'>
586+
<parameter name='count' type='int' jni-type='I'></parameter>
587+
</method>
588+
</class>
589+
</package>
590+
</api>";
591+
592+
options.UseObsoletedOSPlatformAttributes = true;
593+
594+
var gens = ParseApiDefinition (xml);
595+
var iface = gens.Single (g => g.Name == "MyClass");
596+
597+
generator.Context.ContextTypes.Push (iface);
598+
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
599+
generator.Context.ContextTypes.Pop ();
600+
601+
// These should use [Obsolete] because they have always been obsolete in all currently supported versions (21+)
602+
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a class deprecated since 19!\")]"), writer.ToString ());
603+
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a field deprecated since 0!\")]"), writer.ToString ());
604+
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a constructor deprecated since empty string!\")]"), writer.ToString ());
605+
606+
// This should not have a message because the default "deprecated" message isn't useful
607+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\")]"), writer.ToString ());
608+
Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android22.0\")]"), writer.ToString ());
609+
610+
// This should use [Obsolete] because the 'deprecated-since' attribute could not be parsed
611+
Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This method has an invalid deprecated-since!\")]"), writer.ToString ());
612+
}
613+
534614
[Test]
535615
[NonParallelizable] // We are setting a static property on Report
536616
public void WarnIfTypeNameMatchesNamespace ()

tools/generator/CodeGenerationOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public SymbolTable SymbolTable {
6666
public bool SupportNestedInterfaceTypes { get; set; }
6767
public bool SupportNullableReferenceTypes { get; set; }
6868
public bool UseShallowReferencedTypes { get; set; }
69+
public bool UseObsoletedOSPlatformAttributes { get; set; }
6970
public bool RemoveConstSugar => BuildingCoreAssembly;
7071

7172
bool? buildingCoreAssembly;

tools/generator/CodeGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve
8383
SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods,
8484
SupportNestedInterfaceTypes = options.SupportNestedInterfaceTypes,
8585
SupportNullableReferenceTypes = options.SupportNullableReferenceTypes,
86+
UseObsoletedOSPlatformAttributes = options.UseObsoletedOSPlatformAttributes,
8687
};
8788
var resolverCache = new TypeDefinitionCache ();
8889

tools/generator/CodeGeneratorOptions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public CodeGeneratorOptions ()
5353
public bool SupportNestedInterfaceTypes { get; set; }
5454
public bool SupportNullableReferenceTypes { get; set; }
5555
public bool UseLegacyJavaResolver { get; set; }
56+
public bool UseObsoletedOSPlatformAttributes { get; set; }
5657

5758
public XmldocStyle XmldocStyle { get; set; } = XmldocStyle.IntelliSense;
5859

@@ -102,12 +103,13 @@ public static CodeGeneratorOptions Parse (string[] args)
102103
"SDK Platform {VERSION}/API level.",
103104
v => opts.ApiLevel = v },
104105
{ "lang-features=",
105-
"For internal use. (Flags: interface-constants,default-interface-methods,nullable-reference-types)",
106+
"For internal use. (Flags: interface-constants,default-interface-methods,nested-interface-types,nullable-reference-types,obsoleted-platform-attributes)",
106107
v => {
107108
opts.SupportInterfaceConstants = v?.Contains ("interface-constants") == true;
108109
opts.SupportDefaultInterfaceMethods = v?.Contains ("default-interface-methods") == true;
109110
opts.SupportNestedInterfaceTypes = v?.Contains ("nested-interface-types") == true;
110111
opts.SupportNullableReferenceTypes = v?.Contains ("nullable-reference-types") == true;
112+
opts.UseObsoletedOSPlatformAttributes = v?.Contains ("obsoleted-platform-attributes") == true;
111113
}},
112114
{ "preserve-enums",
113115
"For internal use.",

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem, CodeGenerat
155155
ApiAvailableSince = declaringType.ApiAvailableSince,
156156
CustomAttributes = elem.XGetAttribute ("customAttributes"),
157157
Deprecated = elem.Deprecated (),
158+
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
158159
GenericArguments = elem.GenericArguments (),
159160
Name = elem.XGetAttribute ("name"),
160161
Visibility = elem.Visibility ()
@@ -200,6 +201,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener
200201
var field = new Field {
201202
ApiAvailableSince = declaringType.ApiAvailableSince,
202203
DeprecatedComment = elem.XGetAttribute ("deprecated"),
204+
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
203205
IsAcw = true,
204206
IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated",
205207
IsDeprecatedError = elem.XGetAttribute ("deprecated-error") == "true",
@@ -237,6 +239,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener
237239
public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, CodeGenerationOptions opt, bool isInterface)
238240
{
239241
var support = new GenBaseSupport {
242+
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
240243
IsAcw = true,
241244
IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated",
242245
IsGeneratable = true,
@@ -348,6 +351,7 @@ public static Method CreateMethod (GenBase declaringType, XElement elem, CodeGen
348351
ArgsType = elem.Attribute ("argsType")?.Value,
349352
CustomAttributes = elem.XGetAttribute ("customAttributes"),
350353
Deprecated = elem.Deprecated (),
354+
DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"),
351355
ExplicitInterface = elem.XGetAttribute ("explicitInterface"),
352356
EventName = elem.Attribute ("eventName")?.Value,
353357
GenerateAsyncWrapper = elem.Attribute ("generateAsyncWrapper") != null,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class Field : ApiVersionsSupport.IApiAvailability, ISourceLineInfo
99
public string Annotation { get; set; }
1010
public int ApiAvailableSince { get; set; }
1111
public string DeprecatedComment { get; set; }
12+
public int? DeprecatedSince { get; set; }
1213
public bool IsAcw { get; set; }
1314
public bool IsDeprecated { get; set; }
1415
public bool IsDeprecatedError { get; set; }

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ public bool ContainsProperty (string name, bool check_ifaces, bool check_base_if
251251

252252
public string DeprecatedComment => support.DeprecatedComment;
253253

254+
public int? DeprecatedSince => support.DeprecatedSince;
255+
254256
IEnumerable<GenBase> Descendants (IList<GenBase> gens)
255257
{
256258
foreach (var directDescendants in gens.Where (x => x.BaseGen == this)) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public class GenBaseSupport
77
public bool IsAcw { get; set; }
88
public bool IsDeprecated { get; set; }
99
public string DeprecatedComment { get; set; }
10+
public int? DeprecatedSince { get; set; }
1011
public bool IsGeneratable { get; set; }
1112
public bool IsGeneric { get; set; }
1213
public bool IsObfuscated { get; set; }

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ protected MethodBase (GenBase declaringType)
1818
public string AssemblyName { get; set; }
1919
public GenBase DeclaringType { get; }
2020
public string Deprecated { get; set; }
21+
public int? DeprecatedSince { get; set; }
2122
public GenericParameterDefinitionList GenericArguments { get; set; }
2223
public bool IsAcw { get; set; }
2324
public bool IsValid { get; private set; }

0 commit comments

Comments
 (0)