From d3ea180cd4f557e1c40ea908beb2d2398f7e5b48 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 23 Aug 2022 14:12:29 -0500 Subject: [PATCH] [generator] Add support for `[ObsoletedOSPlatform]` (#1026) Context: https://github.com/xamarin/xamarin-android/issues/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.) --- src/utils/XmlExtensions.cs | 13 +++ .../Unit-Tests/CodeGeneratorTests.cs | 80 +++++++++++++++++++ tools/generator/CodeGenerationOptions.cs | 1 + tools/generator/CodeGenerator.cs | 1 + tools/generator/CodeGeneratorOptions.cs | 4 +- .../XmlApiImporter.cs | 4 + .../Field.cs | 1 + .../GenBase.cs | 2 + .../GenBaseSupport.cs | 1 + .../MethodBase.cs | 1 + .../SourceWriters/Attributes/ObsoleteAttr.cs | 12 ++- .../Attributes/ObsoletedOSPlatformAttr.cs | 25 ++++++ tools/generator/SourceWriters/BoundClass.cs | 2 +- .../SourceWriters/BoundConstructor.cs | 2 +- tools/generator/SourceWriters/BoundField.cs | 2 +- .../SourceWriters/BoundFieldAsProperty.cs | 2 +- .../generator/SourceWriters/BoundInterface.cs | 2 +- .../BoundInterfaceMethodDeclaration.cs | 2 +- tools/generator/SourceWriters/BoundMethod.cs | 2 +- .../BoundMethodAbstractDeclaration.cs | 2 +- .../BoundMethodExtensionStringOverload.cs | 2 +- .../BoundMethodStringOverload.cs | 2 +- .../generator/SourceWriters/BoundProperty.cs | 3 +- .../Extensions/SourceWriterExtensions.cs | 27 ++++++- .../InterfaceMemberAlternativeClass.cs | 8 +- .../generator/SourceWriters/MethodCallback.cs | 4 +- 26 files changed, 180 insertions(+), 27 deletions(-) create mode 100644 tools/generator/SourceWriters/Attributes/ObsoletedOSPlatformAttr.cs diff --git a/src/utils/XmlExtensions.cs b/src/utils/XmlExtensions.cs index 6a8d4b2ef..a72eb8f92 100644 --- a/src/utils/XmlExtensions.cs +++ b/src/utils/XmlExtensions.cs @@ -20,5 +20,18 @@ public static string XGetAttribute (this XPathNavigator nav, string name, string var attr = nav.GetAttribute (name, ns); return attr != null ? attr.Trim () : null; } + + public static int? XGetAttributeAsIntOrNull (this XElement element, string name) + { + var attr = element.Attribute (name); + + if (attr?.Value is null) + return null; + + if (int.TryParse (attr.Value, out var val)) + return val; + + return null; + } } } diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index 7f937b6a6..bbb9724c2 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -531,6 +531,86 @@ public void ObsoleteBoundMethodAbstractDeclaration () Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is so old!\")]"), writer.ToString ()); } + [Test] + public void ObsoletedOSPlatformAttributeSupport () + { + var xml = @" + + + + + + + + + + + + + + + "; + + options.UseObsoletedOSPlatformAttributes = true; + + var gens = ParseApiDefinition (xml); + var iface = gens.Single (g => g.Name == "MyClass"); + + generator.Context.ContextTypes.Push (iface); + generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + // Ensure [ObsoletedOSPlatform] was written + Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a class deprecated since 25!\")]"), writer.ToString ()); + Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a field deprecated since 25!\")]"), writer.ToString ()); + Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a constructor deprecated since 25!\")]"), writer.ToString ()); + Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\", @\"This is a method deprecated since 25!\")]"), writer.ToString ()); + 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 ()); + } + + [Test] + public void ObsoletedOSPlatformAttributeUnneededSupport () + { + var xml = @" + + + + + + + + + + + + + + + + "; + + options.UseObsoletedOSPlatformAttributes = true; + + var gens = ParseApiDefinition (xml); + var iface = gens.Single (g => g.Name == "MyClass"); + + generator.Context.ContextTypes.Push (iface); + generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + // These should use [Obsolete] because they have always been obsolete in all currently supported versions (21+) + Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a class deprecated since 19!\")]"), writer.ToString ()); + Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a field deprecated since 0!\")]"), writer.ToString ()); + Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This is a constructor deprecated since empty string!\")]"), writer.ToString ()); + + // This should not have a message because the default "deprecated" message isn't useful + Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android25.0\")]"), writer.ToString ()); + Assert.True (writer.ToString ().Contains ("[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android22.0\")]"), writer.ToString ()); + + // This should use [Obsolete] because the 'deprecated-since' attribute could not be parsed + Assert.True (writer.ToString ().Contains ("[global::System.Obsolete (@\"This method has an invalid deprecated-since!\")]"), writer.ToString ()); + } + [Test] [NonParallelizable] // We are setting a static property on Report public void WarnIfTypeNameMatchesNamespace () diff --git a/tools/generator/CodeGenerationOptions.cs b/tools/generator/CodeGenerationOptions.cs index eb722f12d..68eb6d0b8 100644 --- a/tools/generator/CodeGenerationOptions.cs +++ b/tools/generator/CodeGenerationOptions.cs @@ -66,6 +66,7 @@ public SymbolTable SymbolTable { public bool SupportNestedInterfaceTypes { get; set; } public bool SupportNullableReferenceTypes { get; set; } public bool UseShallowReferencedTypes { get; set; } + public bool UseObsoletedOSPlatformAttributes { get; set; } public bool RemoveConstSugar => BuildingCoreAssembly; bool? buildingCoreAssembly; diff --git a/tools/generator/CodeGenerator.cs b/tools/generator/CodeGenerator.cs index 880d91daf..753f05138 100644 --- a/tools/generator/CodeGenerator.cs +++ b/tools/generator/CodeGenerator.cs @@ -83,6 +83,7 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve SupportDefaultInterfaceMethods = options.SupportDefaultInterfaceMethods, SupportNestedInterfaceTypes = options.SupportNestedInterfaceTypes, SupportNullableReferenceTypes = options.SupportNullableReferenceTypes, + UseObsoletedOSPlatformAttributes = options.UseObsoletedOSPlatformAttributes, }; var resolverCache = new TypeDefinitionCache (); diff --git a/tools/generator/CodeGeneratorOptions.cs b/tools/generator/CodeGeneratorOptions.cs index 1d6ec1324..08395d154 100644 --- a/tools/generator/CodeGeneratorOptions.cs +++ b/tools/generator/CodeGeneratorOptions.cs @@ -53,6 +53,7 @@ public CodeGeneratorOptions () public bool SupportNestedInterfaceTypes { get; set; } public bool SupportNullableReferenceTypes { get; set; } public bool UseLegacyJavaResolver { get; set; } + public bool UseObsoletedOSPlatformAttributes { get; set; } public XmldocStyle XmldocStyle { get; set; } = XmldocStyle.IntelliSense; @@ -102,12 +103,13 @@ public static CodeGeneratorOptions Parse (string[] args) "SDK Platform {VERSION}/API level.", v => opts.ApiLevel = v }, { "lang-features=", - "For internal use. (Flags: interface-constants,default-interface-methods,nullable-reference-types)", + "For internal use. (Flags: interface-constants,default-interface-methods,nested-interface-types,nullable-reference-types,obsoleted-platform-attributes)", v => { opts.SupportInterfaceConstants = v?.Contains ("interface-constants") == true; opts.SupportDefaultInterfaceMethods = v?.Contains ("default-interface-methods") == true; opts.SupportNestedInterfaceTypes = v?.Contains ("nested-interface-types") == true; opts.SupportNullableReferenceTypes = v?.Contains ("nullable-reference-types") == true; + opts.UseObsoletedOSPlatformAttributes = v?.Contains ("obsoleted-platform-attributes") == true; }}, { "preserve-enums", "For internal use.", diff --git a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs index fbca66cee..2e773df60 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs @@ -155,6 +155,7 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem, CodeGenerat ApiAvailableSince = declaringType.ApiAvailableSince, CustomAttributes = elem.XGetAttribute ("customAttributes"), Deprecated = elem.Deprecated (), + DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"), GenericArguments = elem.GenericArguments (), Name = elem.XGetAttribute ("name"), Visibility = elem.Visibility () @@ -200,6 +201,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener var field = new Field { ApiAvailableSince = declaringType.ApiAvailableSince, DeprecatedComment = elem.XGetAttribute ("deprecated"), + DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"), IsAcw = true, IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated", IsDeprecatedError = elem.XGetAttribute ("deprecated-error") == "true", @@ -237,6 +239,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener public static GenBaseSupport CreateGenBaseSupport (XElement pkg, XElement elem, CodeGenerationOptions opt, bool isInterface) { var support = new GenBaseSupport { + DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"), IsAcw = true, IsDeprecated = elem.XGetAttribute ("deprecated") != "not deprecated", IsGeneratable = true, @@ -348,6 +351,7 @@ public static Method CreateMethod (GenBase declaringType, XElement elem, CodeGen ArgsType = elem.Attribute ("argsType")?.Value, CustomAttributes = elem.XGetAttribute ("customAttributes"), Deprecated = elem.Deprecated (), + DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"), ExplicitInterface = elem.XGetAttribute ("explicitInterface"), EventName = elem.Attribute ("eventName")?.Value, GenerateAsyncWrapper = elem.Attribute ("generateAsyncWrapper") != null, diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs index ac0206cba..6f38240c7 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs @@ -9,6 +9,7 @@ public class Field : ApiVersionsSupport.IApiAvailability, ISourceLineInfo public string Annotation { get; set; } public int ApiAvailableSince { get; set; } public string DeprecatedComment { get; set; } + public int? DeprecatedSince { get; set; } public bool IsAcw { get; set; } public bool IsDeprecated { get; set; } public bool IsDeprecatedError { get; set; } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 6d55000fc..5331bbf2b 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -251,6 +251,8 @@ public bool ContainsProperty (string name, bool check_ifaces, bool check_base_if public string DeprecatedComment => support.DeprecatedComment; + public int? DeprecatedSince => support.DeprecatedSince; + IEnumerable Descendants (IList gens) { foreach (var directDescendants in gens.Where (x => x.BaseGen == this)) { diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBaseSupport.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBaseSupport.cs index 57526908a..75258cb6d 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBaseSupport.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBaseSupport.cs @@ -7,6 +7,7 @@ public class GenBaseSupport public bool IsAcw { get; set; } public bool IsDeprecated { get; set; } public string DeprecatedComment { get; set; } + public int? DeprecatedSince { get; set; } public bool IsGeneratable { get; set; } public bool IsGeneric { get; set; } public bool IsObfuscated { get; set; } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs index ee10dc45f..09fb19072 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs @@ -18,6 +18,7 @@ protected MethodBase (GenBase declaringType) public string AssemblyName { get; set; } public GenBase DeclaringType { get; } public string Deprecated { get; set; } + public int? DeprecatedSince { get; set; } public GenericParameterDefinitionList GenericArguments { get; set; } public bool IsAcw { get; set; } public bool IsValid { get; private set; } diff --git a/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs b/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs index 152bce266..b00ef5008 100644 --- a/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs +++ b/tools/generator/SourceWriters/Attributes/ObsoleteAttr.cs @@ -14,21 +14,19 @@ public class ObsoleteAttr : AttributeWriter public ObsoleteAttr (string message = null, bool isError = false) { - Message = message; + Message = message?.Replace ("\"", "\"\"").Trim (); IsError = isError; } public override void WriteAttribute (CodeWriter writer) { - var parts = new List (); + var content = string.Empty; - if (Message != null) - parts.Add ($"@\"{Message}\""); + if (Message != null || IsError) + content += $"@\"{Message}\""; if (IsError) - parts.Add ("error: true"); - - var content = string.Join (", ", parts.ToArray ()); + content += ", error: true"; if (content.HasValue ()) writer.WriteLine ($"[global::System.Obsolete ({content})]"); diff --git a/tools/generator/SourceWriters/Attributes/ObsoletedOSPlatformAttr.cs b/tools/generator/SourceWriters/Attributes/ObsoletedOSPlatformAttr.cs new file mode 100644 index 000000000..eecb0a87d --- /dev/null +++ b/tools/generator/SourceWriters/Attributes/ObsoletedOSPlatformAttr.cs @@ -0,0 +1,25 @@ +using System; +using Xamarin.SourceWriter; + +namespace generator.SourceWriters +{ + public class ObsoletedOSPlatformAttr : AttributeWriter + { + public string Message { get; set; } + public int Version { get; } + + public ObsoletedOSPlatformAttr (string message, int version) + { + Message = message; + Version = version; + } + + public override void WriteAttribute (CodeWriter writer) + { + if (Message.HasValue ()) + writer.WriteLine ($"[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android{Version}.0\", @\"{Message.Replace ("\"", "\"\"")}\")]"); + else + writer.WriteLine ($"[global::System.Runtime.Versioning.ObsoletedOSPlatform (\"android{Version}.0\")]"); + } + } +} diff --git a/tools/generator/SourceWriters/BoundClass.cs b/tools/generator/SourceWriters/BoundClass.cs index 38337dbe3..76ca86db8 100644 --- a/tools/generator/SourceWriters/BoundClass.cs +++ b/tools/generator/SourceWriters/BoundClass.cs @@ -44,7 +44,7 @@ public BoundClass (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorConte klass.JavadocInfo?.AddJavadocs (Comments); Comments.Add ($"// Metadata.xml XPath class reference: path=\"{klass.MetadataXPathReference}\""); - SourceWriterExtensions.AddObsolete (Attributes, klass.DeprecatedComment, klass.IsDeprecated); + SourceWriterExtensions.AddObsolete (Attributes, klass.DeprecatedComment, opt, forceDeprecate: klass.IsDeprecated, deprecatedSince: klass.DeprecatedSince); SourceWriterExtensions.AddSupportedOSPlatform (Attributes, klass, opt); diff --git a/tools/generator/SourceWriters/BoundConstructor.cs b/tools/generator/SourceWriters/BoundConstructor.cs index ed7ad3ca4..9440abf0a 100644 --- a/tools/generator/SourceWriters/BoundConstructor.cs +++ b/tools/generator/SourceWriters/BoundConstructor.cs @@ -33,7 +33,7 @@ public BoundConstructor (ClassGen klass, Ctor constructor, bool useBase, CodeGen Attributes.Add (new RegisterAttr (".ctor", constructor.JniSignature, string.Empty, additionalProperties: constructor.AdditionalAttributeString ())); } - SourceWriterExtensions.AddObsolete (Attributes, constructor.Deprecated); + SourceWriterExtensions.AddObsolete (Attributes, constructor.Deprecated, opt, deprecatedSince: constructor.DeprecatedSince); if (constructor.CustomAttributes != null) Attributes.Add (new CustomAttr (constructor.CustomAttributes)); diff --git a/tools/generator/SourceWriters/BoundField.cs b/tools/generator/SourceWriters/BoundField.cs index 6c4a66347..620c3134a 100644 --- a/tools/generator/SourceWriters/BoundField.cs +++ b/tools/generator/SourceWriters/BoundField.cs @@ -31,7 +31,7 @@ public BoundField (GenBase type, Field field, CodeGenerationOptions opt) if (field.IsEnumified) Attributes.Add (new GeneratedEnumAttr ()); - SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, field.IsDeprecated, isError: field.IsDeprecatedError); + SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, opt, field.IsDeprecated, isError: field.IsDeprecatedError, deprecatedSince: field.DeprecatedSince); if (field.Annotation.HasValue ()) Attributes.Add (new CustomAttr (field.Annotation)); diff --git a/tools/generator/SourceWriters/BoundFieldAsProperty.cs b/tools/generator/SourceWriters/BoundFieldAsProperty.cs index aecf5d1a6..9277a1828 100644 --- a/tools/generator/SourceWriters/BoundFieldAsProperty.cs +++ b/tools/generator/SourceWriters/BoundFieldAsProperty.cs @@ -47,7 +47,7 @@ public BoundFieldAsProperty (GenBase type, Field field, CodeGenerationOptions op Attributes.Add (new RegisterAttr (field.JavaName, additionalProperties: field.AdditionalAttributeString ())); } - SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, field.IsDeprecated, isError: field.IsDeprecatedError); + SourceWriterExtensions.AddObsolete (Attributes, field.DeprecatedComment, opt, field.IsDeprecated, isError: field.IsDeprecatedError, deprecatedSince: field.DeprecatedSince); SetVisibility (field.Visibility); UseExplicitPrivateKeyword = true; diff --git a/tools/generator/SourceWriters/BoundInterface.cs b/tools/generator/SourceWriters/BoundInterface.cs index 9dc62a407..5ca02cf8f 100644 --- a/tools/generator/SourceWriters/BoundInterface.cs +++ b/tools/generator/SourceWriters/BoundInterface.cs @@ -43,7 +43,7 @@ public BoundInterface (InterfaceGen iface, CodeGenerationOptions opt, CodeGenera iface.JavadocInfo?.AddJavadocs (Comments); Comments.Add ($"// Metadata.xml XPath interface reference: path=\"{iface.MetadataXPathReference}\""); - SourceWriterExtensions.AddObsolete (Attributes, iface.DeprecatedComment, iface.IsDeprecated); + SourceWriterExtensions.AddObsolete (Attributes, iface.DeprecatedComment, opt, iface.IsDeprecated, deprecatedSince: iface.DeprecatedSince); if (!iface.IsConstSugar (opt)) { var signature = string.IsNullOrWhiteSpace (iface.Namespace) diff --git a/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs b/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs index 8d452ac34..502efac0e 100644 --- a/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs +++ b/tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs @@ -34,7 +34,7 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener if (method.DeclaringType.IsGeneratable) Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); - SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated); + SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince); if (method.IsReturnEnumified) Attributes.Add (new GeneratedEnumAttr (true)); diff --git a/tools/generator/SourceWriters/BoundMethod.cs b/tools/generator/SourceWriters/BoundMethod.cs index 806efe655..2146dd436 100644 --- a/tools/generator/SourceWriters/BoundMethod.cs +++ b/tools/generator/SourceWriters/BoundMethod.cs @@ -74,7 +74,7 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool if (method.DeclaringType.IsGeneratable) Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); - SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated); + SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince); if (method.IsReturnEnumified) Attributes.Add (new GeneratedEnumAttr (true)); diff --git a/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs b/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs index bbae48799..d76b4e90d 100644 --- a/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs +++ b/tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs @@ -53,7 +53,7 @@ public BoundMethodAbstractDeclaration (GenBase gen, Method method, CodeGeneratio if (method.DeclaringType.IsGeneratable) Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\""); - SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated); + SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince); SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt); diff --git a/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs b/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs index dfae18351..4ad132b19 100644 --- a/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs +++ b/tools/generator/SourceWriters/BoundMethodExtensionStringOverload.cs @@ -26,7 +26,7 @@ public BoundMethodExtensionStringOverload (Method method, CodeGenerationOptions SetVisibility (method.Visibility); ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); - SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated); + SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince); SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt); diff --git a/tools/generator/SourceWriters/BoundMethodStringOverload.cs b/tools/generator/SourceWriters/BoundMethodStringOverload.cs index 80e47b452..e60f9acb6 100644 --- a/tools/generator/SourceWriters/BoundMethodStringOverload.cs +++ b/tools/generator/SourceWriters/BoundMethodStringOverload.cs @@ -24,7 +24,7 @@ public BoundMethodStringOverload (Method method, CodeGenerationOptions opt) SetVisibility (method.Visibility); ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal).Replace ("Java.Lang.ICharSequence", "string").Replace ("global::string", "string")); - SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated); + SourceWriterExtensions.AddObsolete (Attributes, method.Deprecated, opt, deprecatedSince: method.DeprecatedSince); SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt); diff --git a/tools/generator/SourceWriters/BoundProperty.cs b/tools/generator/SourceWriters/BoundProperty.cs index 41209eb2b..240b86a95 100644 --- a/tools/generator/SourceWriters/BoundProperty.cs +++ b/tools/generator/SourceWriters/BoundProperty.cs @@ -75,7 +75,8 @@ public BoundProperty (GenBase gen, Property property, CodeGenerationOptions opt, // Unlike [Register], [Obsolete] cannot be put on property accessors, so we can apply them only under limited condition... if (property.Getter.Deprecated != null && (property.Setter == null || property.Setter.Deprecated != null)) { var message = property.Getter.Deprecated.Trim () + (property.Setter != null && property.Setter.Deprecated != property.Getter.Deprecated ? " " + property.Setter.Deprecated.Trim () : null); - SourceWriterExtensions.AddObsolete (Attributes, message); + var since = property.Getter?.DeprecatedSince ?? property.Setter?.DeprecatedSince; + SourceWriterExtensions.AddObsolete (Attributes, message, opt, deprecatedSince: since); } SourceWriterExtensions.AddSupportedOSPlatform (Attributes, property.Getter, opt); diff --git a/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs b/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs index 3488bd3e8..7535f9488 100644 --- a/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs +++ b/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs @@ -301,13 +301,36 @@ public static void AddSupportedOSPlatform (List attributes, Api } - public static void AddObsolete (List attributes, string message, bool forceDeprecate = false, bool isError = false) + public static void AddObsolete (List attributes, string message, CodeGenerationOptions opt, bool forceDeprecate = false, bool isError = false, int? deprecatedSince = null) { // Bail if we're not obsolete if ((!forceDeprecate && !message.HasValue ()) || message == "not deprecated") return; - attributes.Add (new ObsoleteAttr (message: message?.Replace ("\"", "\"\"").Trim (), isError: isError)); + // Check if we should use [ObsoletedOSPlatform] instead of [Obsolete] + if (AddObsoletedOSPlatformAttribute (attributes, message, deprecatedSince, opt)) + return; + + attributes.Add (new ObsoleteAttr (message, isError)); + } + + // Returns true if attribute was applied + static bool AddObsoletedOSPlatformAttribute (List attributes, string message, int? deprecatedSince, CodeGenerationOptions opt) + { + if (!opt.UseObsoletedOSPlatformAttributes) + return false; + + // If it was obsoleted in a version earlier than we support (like 15), use a regular [Obsolete] instead + if (!deprecatedSince.HasValue || deprecatedSince <= 21) + return false; + + // This is the default Android message, but it isn't useful so remove it + if (message == "deprecated") + message = string.Empty; + + attributes.Add (new ObsoletedOSPlatformAttr (message, deprecatedSince.Value)); + + return true; } public static void WriteMethodInvokerBody (CodeWriter writer, Method method, CodeGenerationOptions opt, string contextThis) diff --git a/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs b/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs index 4f1c92c38..8d99cb8e9 100644 --- a/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs +++ b/tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs @@ -42,7 +42,7 @@ public InterfaceMemberAlternativeClass (InterfaceGen iface, CodeGenerationOption Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { AcwLast = true }); if (should_obsolete) - SourceWriterExtensions.AddObsolete (Attributes, $"Use the '{iface.FullName}' type. This class will be removed in a future release."); + SourceWriterExtensions.AddObsolete (Attributes, $"Use the '{iface.FullName}' type. This class will be removed in a future release.", opt); Constructors.Add (new ConstructorWriter { Name = Name, IsInternal = true }); @@ -53,7 +53,7 @@ public InterfaceMemberAlternativeClass (InterfaceGen iface, CodeGenerationOption Fields.Add (new PeerMembersField (opt, iface.RawJniName, Name, false)); if (!iface.HasManagedName && !opt.SupportInterfaceConstants) - sibling_classes.Add (new InterfaceConstsForwardClass (iface)); + sibling_classes.Add (new InterfaceConstsForwardClass (iface, opt)); } void AddMethods (InterfaceGen iface, bool shouldObsolete, CodeGenerationOptions opt) @@ -171,7 +171,7 @@ public void WriteSiblingClasses (CodeWriter writer) public class InterfaceConstsForwardClass : ClassWriter { - public InterfaceConstsForwardClass (InterfaceGen iface) + public InterfaceConstsForwardClass (InterfaceGen iface, CodeGenerationOptions opt) { Name = iface.Name.Substring (1) + "Consts"; Inherits = iface.Name.Substring (1); @@ -180,7 +180,7 @@ public InterfaceConstsForwardClass (InterfaceGen iface) IsAbstract = true; Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ())); - SourceWriterExtensions.AddObsolete (Attributes, $"Use the '{iface.Name.Substring (1)}' type. This type will be removed in a future release.", isError: true); + SourceWriterExtensions.AddObsolete (Attributes, $"Use the '{iface.Name.Substring (1)}' type. This type will be removed in a future release.", opt, isError: true); Constructors.Add (new ConstructorWriter { Name = Name, diff --git a/tools/generator/SourceWriters/MethodCallback.cs b/tools/generator/SourceWriters/MethodCallback.cs index c1107d3e5..f292d89f8 100644 --- a/tools/generator/SourceWriters/MethodCallback.cs +++ b/tools/generator/SourceWriters/MethodCallback.cs @@ -43,7 +43,7 @@ public MethodCallback (GenBase type, Method method, CodeGenerationOptions option IsStatic = true; IsPrivate = method.IsInterfaceDefaultMethod; - SourceWriterExtensions.AddObsolete (Attributes, null, forceDeprecate: !string.IsNullOrWhiteSpace (method.Deprecated)); + SourceWriterExtensions.AddObsolete (Attributes, null, opt, forceDeprecate: !string.IsNullOrWhiteSpace (method.Deprecated), deprecatedSince: method.DeprecatedSince); SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt); @@ -135,7 +135,7 @@ public GetDelegateHandlerMethod (Method method, CodeGenerationOptions opt) IsStatic = true; IsPrivate = method.IsInterfaceDefaultMethod; - SourceWriterExtensions.AddObsolete (Attributes, null, forceDeprecate: !string.IsNullOrWhiteSpace (method.Deprecated)); + SourceWriterExtensions.AddObsolete (Attributes, null, opt, forceDeprecate: !string.IsNullOrWhiteSpace (method.Deprecated), deprecatedSince: method.DeprecatedSince); SourceWriterExtensions.AddSupportedOSPlatform (Attributes, method, opt); }