From ee1b955cc0ccc0df2fdb4c2cf106276ce36207b0 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Wed, 12 Feb 2025 13:23:11 -1000 Subject: [PATCH] [generator] Add support for emitting `[UnsupportedOSPlatform]`. --- .../Unit-Tests/CodeGeneratorTests.cs | 30 +++++++++++++++++++ .../Unit-Tests/XmlApiImporterTests.cs | 9 ++++++ tools/generator/ApiVersionsSupport.cs | 1 + .../XmlApiImporter.cs | 8 ++++- .../Field.cs | 1 + .../GenBase.cs | 2 ++ .../MethodBase.cs | 1 + .../Attributes/UnsupportedOSPlatformAttr.cs | 21 +++++++++++++ .../Extensions/SourceWriterExtensions.cs | 18 +++++++++-- 9 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 tools/generator/SourceWriters/Attributes/UnsupportedOSPlatformAttr.cs diff --git a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs index 29e4205e1..961668207 100644 --- a/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs @@ -1406,6 +1406,36 @@ public void SupportedOSPlatformConstFields () StringAssert.Contains ("[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain SupportedOSPlatform!"); } + [Test] + public void UnsupportedOSPlatform () + { + var klass = SupportTypeBuilder.CreateClass ("java.code.MyClass", options); + klass.ApiRemovedSince = 30; + + generator.Context.ContextTypes.Push (klass); + generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + StringAssert.Contains ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain UnsupportedOSPlatform!"); + } + + [Test] + public void UnsupportedOSPlatformConstFields () + { + var klass = new TestClass ("java.lang.Object", "com.mypackage.foo"); + var field = new TestField ("java.lang.String", "bar").SetConstant ("MY_VALUE"); + + field.ApiRemovedSince = 30; + + klass.Fields.Add (field); + + generator.Context.ContextTypes.Push (klass); + generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + StringAssert.Contains ("[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android30.0\")]", builder.ToString (), "Should contain UnsupportedOSPlatform!"); + } + [Test] public void StringPropertyOverride ([Values ("true", "false")] string final) { diff --git a/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs b/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs index 9999d17b4..b19aa96f5 100644 --- a/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs +++ b/tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs @@ -51,6 +51,15 @@ public void CreateClass_CorrectApiSinceOverridePackage () Assert.AreEqual (9, klass.ApiAvailableSince); } + [Test] + public void CreateClass_CorrectApiRemoved () + { + var xml = XDocument.Parse (""); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"), opt); + + Assert.AreEqual (7, klass.ApiRemovedSince); + } + [Test] public void CreateCtor_EnsureValidName () { diff --git a/tools/generator/ApiVersionsSupport.cs b/tools/generator/ApiVersionsSupport.cs index 43530de89..524c7437d 100644 --- a/tools/generator/ApiVersionsSupport.cs +++ b/tools/generator/ApiVersionsSupport.cs @@ -18,6 +18,7 @@ public static class ApiVersionsSupport public interface IApiAvailability { int ApiAvailableSince { get; set; } + int ApiRemovedSince { get; set; } } static IEnumerable FlattenGens (IEnumerable gens) diff --git a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs index 901f235e8..6c3a67767 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs @@ -159,6 +159,7 @@ public static Ctor CreateCtor (GenBase declaringType, XElement elem, CodeGenerat var ctor = new Ctor (declaringType) { AnnotatedVisibility = elem.XGetAttribute ("annotated-visibility"), ApiAvailableSince = declaringType.ApiAvailableSince, + ApiRemovedSince = declaringType.ApiRemovedSince, CustomAttributes = elem.XGetAttribute ("customAttributes"), Deprecated = elem.Deprecated (), DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"), @@ -211,6 +212,7 @@ public static Field CreateField (GenBase declaringType, XElement elem, CodeGener var field = new Field { AnnotatedVisibility = elem.XGetAttribute ("annotated-visibility"), ApiAvailableSince = declaringType.ApiAvailableSince, + ApiRemovedSince = declaringType.ApiRemovedSince, DeprecatedComment = elem.XGetAttribute ("deprecated"), DeprecatedSince = elem.XGetAttributeAsIntOrNull ("deprecated-since"), IsAcw = true, @@ -369,6 +371,7 @@ public static Method CreateMethod (GenBase declaringType, XElement elem, CodeGen var method = new Method (declaringType) { AnnotatedVisibility = elem.XGetAttribute ("annotated-visibility"), ApiAvailableSince = declaringType.ApiAvailableSince, + ApiRemovedSince = declaringType.ApiRemovedSince, ArgsType = elem.Attribute ("argsType")?.Value, CustomAttributes = elem.XGetAttribute ("customAttributes"), Deprecated = elem.Deprecated (), @@ -515,9 +518,12 @@ static XElement GetPreviousClass (XNode n, string nameValue) // Elements need to be passed in the above order. (package, class, member) static void FillApiSince (ApiVersionsSupport.IApiAvailability model, params XElement[] elems) { - foreach (var elem in elems) + foreach (var elem in elems) { if (int.TryParse (elem.XGetAttribute ("api-since"), out var result)) model.ApiAvailableSince = result; + if (int.TryParse (elem.XGetAttribute ("removed-since"), out var removed)) + model.ApiRemovedSince = removed; + } } static bool IsObfuscatedName (int threshold, string name) diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs index 8477fab1d..4141993ee 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 AnnotatedVisibility { get; set; } public string Annotation { get; set; } public int ApiAvailableSince { get; set; } + public int ApiRemovedSince { get; set; } public string DeprecatedComment { get; set; } public int? DeprecatedSince { get; set; } public bool IsAcw { 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 94cf2a022..8568a8de8 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -188,6 +188,8 @@ IEnumerable Ancestors () public int ApiAvailableSince { get; set; } + public int ApiRemovedSince { get; set; } + public virtual ClassGen BaseGen => null; public GenBase BaseSymbol => diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs index 8eb5c71da..4f548c729 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs @@ -16,6 +16,7 @@ protected MethodBase (GenBase declaringType) public string AnnotatedVisibility { get; set; } public string Annotation { get; internal set; } public int ApiAvailableSince { get; set; } + public int ApiRemovedSince { get; set; } public string AssemblyName { get; set; } public GenBase DeclaringType { get; } public string Deprecated { get; set; } diff --git a/tools/generator/SourceWriters/Attributes/UnsupportedOSPlatformAttr.cs b/tools/generator/SourceWriters/Attributes/UnsupportedOSPlatformAttr.cs new file mode 100644 index 000000000..27c789c4f --- /dev/null +++ b/tools/generator/SourceWriters/Attributes/UnsupportedOSPlatformAttr.cs @@ -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 UnsupportedOSPlatformAttr : AttributeWriter + { + public int Version { get; } + + public UnsupportedOSPlatformAttr (int version) => Version = version; + + public override void WriteAttribute (CodeWriter writer) + { + writer.WriteLine ($"[global::System.Runtime.Versioning.UnsupportedOSPlatformAttribute (\"android{Version}.0\")]"); + } + } +} diff --git a/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs b/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs index 7170f74d0..bbc1ca4f5 100644 --- a/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs +++ b/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs @@ -12,6 +12,8 @@ namespace generator.SourceWriters { public static class SourceWriterExtensions { + const int MINIMUM_API_LEVEL = 21; + public static void AddField (TypeWriter tw, GenBase type, Field field, CodeGenerationOptions opt) { if (field.NeedsProperty) @@ -306,16 +308,26 @@ public static void AddParameterListCallArgs (List body, ParameterList pa } public static void AddSupportedOSPlatform (List attributes, ApiVersionsSupport.IApiAvailability member, CodeGenerationOptions opt) - => AddSupportedOSPlatform (attributes, member.ApiAvailableSince, opt); + { + AddSupportedOSPlatform (attributes, member.ApiAvailableSince, opt); + AddUnsupportedOSPlatform (attributes, member.ApiRemovedSince, opt); + } public static void AddSupportedOSPlatform (List attributes, int since, 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 (since > 21 && opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1) + if (since > MINIMUM_API_LEVEL && opt.CodeGenerationTarget == Xamarin.Android.Binder.CodeGenerationTarget.XAJavaInterop1) attributes.Add (new SupportedOSPlatformAttr (since)); } + public static void AddUnsupportedOSPlatform (List attributes, int since, CodeGenerationOptions opt) + { + // Here it makes sense to still write 'android15' because it will be missing in later versions like `android35`. + if (since > 0 && opt.CodeGenerationTarget == CodeGenerationTarget.XAJavaInterop1) + attributes.Add (new UnsupportedOSPlatformAttr (since)); + } + 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 @@ -336,7 +348,7 @@ static bool AddObsoletedOSPlatformAttribute (List attributes, s 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) + if (!deprecatedSince.HasValue || deprecatedSince <= MINIMUM_API_LEVEL) return false; // This is the default Android message, but it isn't useful so remove it