diff --git a/tests/common/PlatformInfo.cs b/tests/common/PlatformInfo.cs index 5794d9ed68..fe71961bd6 100644 --- a/tests/common/PlatformInfo.cs +++ b/tests/common/PlatformInfo.cs @@ -19,6 +19,8 @@ using Foundation; using UIKit; #endif +using Xamarin.Utils; + namespace Xamarin.Tests { public sealed class PlatformInfo @@ -50,6 +52,20 @@ namespace Xamarin.Tests var platformInfo = new PlatformInfo (); +#if NET + if (name.StartsWith ("maccatalyst", StringComparison.Ordinal)) + platformInfo.Name = ApplePlatform.MacCatalyst; + else if (name.StartsWith ("mac", StringComparison.Ordinal)) + platformInfo.Name = ApplePlatform.MacOSX; + else if (name.StartsWith ("ios", StringComparison.Ordinal) || name.StartsWith ("iphoneos", StringComparison.Ordinal) || name.StartsWith ("ipados", StringComparison.Ordinal)) + platformInfo.Name = ApplePlatform.iOS; + else if (name.StartsWith ("tvos", StringComparison.Ordinal)) + platformInfo.Name = ApplePlatform.TVOS; + else if (name.StartsWith ("watchos", StringComparison.Ordinal)) + platformInfo.Name = ApplePlatform.WatchOS; + else + throw new FormatException ($"Unknown product name: {name}"); +#else if (name.StartsWith ("maccatalyst", StringComparison.Ordinal)) platformInfo.Name = PlatformName.MacCatalyst; else if (name.StartsWith ("mac", StringComparison.Ordinal)) @@ -62,27 +78,39 @@ namespace Xamarin.Tests platformInfo.Name = PlatformName.WatchOS; else throw new FormatException ($"Unknown product name: {name}"); +#endif platformInfo.Version = Version.Parse (version); +#if !NET if (IntPtr.Size == 4) platformInfo.Architecture = PlatformArchitecture.Arch32; else if (IntPtr.Size == 8) platformInfo.Architecture = PlatformArchitecture.Arch64; +#endif return platformInfo; } public static readonly PlatformInfo Host = GetHostPlatformInfo (); +#if NET + public ApplePlatform Name { get; private set; } +#else public PlatformName Name { get; private set; } public PlatformArchitecture Architecture { get; private set; } +#endif public Version Version { get; private set; } +#if NET + public bool IsMac => Name == ApplePlatform.MacOSX; + public bool IsIos => Name == ApplePlatform.iOS; +#else public bool IsMac => Name == PlatformName.MacOSX; public bool IsIos => Name == PlatformName.iOS; public bool IsArch32 => Architecture.HasFlag (PlatformArchitecture.Arch32); public bool IsArch64 => Architecture.HasFlag (PlatformArchitecture.Arch64); +#endif PlatformInfo () { @@ -91,57 +119,6 @@ namespace Xamarin.Tests public static class AvailabilityExtensions { -#if NET - public static AvailabilityBaseAttribute Convert (this OSPlatformAttribute a) - { - if (a == null) - return null; - - PlatformName p; - int n = 0; - switch (a.PlatformName) { - case string dummy when a.PlatformName.StartsWith ("ios"): - p = PlatformName.iOS; - n = "ios".Length; - break; - case string dummy when a.PlatformName.StartsWith ("tvos"): - p = PlatformName.TvOS; - n = "tvos".Length; - break; - case string dummy when a.PlatformName.StartsWith ("watchos"): - p = PlatformName.WatchOS; - n = "watchos".Length; - break; - case string dummy when a.PlatformName.StartsWith ("macos"): - p = PlatformName.MacOSX; - n = "macos".Length; - break; - case string dummy when a.PlatformName.StartsWith ("maccatalyst"): - p = PlatformName.MacCatalyst; - n = "maccatalyst".Length; - break; - default: - return null; - } - bool versioned = Version.TryParse (a.PlatformName [n..], out var v); - - if (a is SupportedOSPlatformAttribute) { - if (!versioned) - throw new FormatException (a.PlatformName); - return new IntroducedAttribute (p, v.Major, v.Minor); - } - if (a is UnsupportedOSPlatformAttribute) { - // if a version is provided then it means it was deprecated - // if no version is provided then it means it's unavailable - if (versioned) - return new DeprecatedAttribute (p, v.Major, v.Minor); - else - return new UnavailableAttribute (p); - } - return null; - } -#endif - public static bool IsAvailableOnHostPlatform (this ICustomAttributeProvider attributeProvider) { return attributeProvider.IsAvailable (PlatformInfo.Host); @@ -149,34 +126,68 @@ namespace Xamarin.Tests public static bool IsAvailable (this ICustomAttributeProvider attributeProvider, PlatformInfo targetPlatform) { + var customAttributes = attributeProvider.GetCustomAttributes (true); + #if NET - var list = new List (); - foreach (var ca in attributeProvider.GetCustomAttributes (true)) { - if (ca is OSPlatformAttribute aa) { - var converted = aa.Convert (); - if (converted is not null) - list.Add (converted); - } - // FIXME - temporary, while older attributes co-exists (in manual bindings) - if (ca is AvailabilityBaseAttribute old) - list.Add (old); - if (ca is ObsoleteAttribute) - return false; - } - return list.IsAvailable (targetPlatform); -#else - return attributeProvider - .GetCustomAttributes (true) - .OfType () - .IsAvailable (targetPlatform); + customAttributes = customAttributes.ToArray (); // don't iterate twice + if (customAttributes.Any (v => v is ObsoleteAttribute)) + return false; #endif + + return customAttributes +#if NET + .OfType () +#else + .OfType () +#endif + .IsAvailable (targetPlatform); } +#if NET + public static bool IsAvailableOnHostPlatform (this IEnumerable attributes) +#else public static bool IsAvailableOnHostPlatform (this IEnumerable attributes) +#endif { return attributes.IsAvailable (PlatformInfo.Host); } +#if NET + public static bool IsAvailable (this IEnumerable attributes, PlatformInfo targetPlatform) + { + // Sort the attributes so that maccatalyst attributes are processed before ios attributes + // This way we can easily fall back to ios for maccatalyst + attributes = attributes.OrderBy (v => v.PlatformName.ToLowerInvariant ()).Reverse (); + + foreach (var attr in attributes) { + if (!attr.TryParse (out ApplePlatform? platform, out var version)) + continue; + + if (targetPlatform.Name == ApplePlatform.MacCatalyst) { + if (platform != ApplePlatform.iOS && platform != ApplePlatform.MacCatalyst) + continue; + } else if (platform != targetPlatform.Name) { + continue; + } + + if (attr is UnsupportedOSPlatformAttribute) + return version is not null && targetPlatform.Version < version; + + if (attr is SupportedOSPlatformAttribute) + return version is null || targetPlatform.Version >= version; + } + + // Our current attribute logic assumes that no attribute means that an API is available on all versions of that platform. + // This is not correct: the correct logic is that an API is available on a platform if there are no availability attributes + // for any other platforms. However, enforcing this here would make a few of our tests fail, so we must keep the incorrect + // logic until we've got all the right attributes implemented. + return true; + + // Correct logic: + // Only available if there aren't any attributes for other platforms + // return attributes.Count () == 0; + } +#else public static bool IsAvailable (this IEnumerable attributes, PlatformInfo targetPlatform) { // always "available" from a binding perspective if @@ -214,5 +225,6 @@ namespace Xamarin.Tests return available; } +#endif } } diff --git a/tests/introspection/ApiAvailabilityTest.cs b/tests/introspection/ApiAvailabilityTest.cs index c47212afaf..f82f719d91 100644 --- a/tests/introspection/ApiAvailabilityTest.cs +++ b/tests/introspection/ApiAvailabilityTest.cs @@ -27,6 +27,7 @@ using System.Text; using NUnit.Framework; using ObjCRuntime; using Xamarin.Tests; +using Xamarin.Utils; namespace Introspection { @@ -34,12 +35,37 @@ namespace Introspection { protected Version Minimum { get; set; } protected Version Maximum { get; set; } +#if NET + protected Func Filter { get; set; } + protected ApplePlatform Platform { get; set; } +#else protected Func Filter { get; set; } protected PlatformName Platform { get; set; } +#endif public ApiAvailabilityTest () { Maximum = Version.Parse (Constants.SdkVersion); +#if NET +#if __MACCATALYST__ + Platform = ApplePlatform.MacCatalyst; + Minimum = Xamarin.SdkVersions.MinMacCatalystVersion; +#elif __IOS__ + Platform = ApplePlatform.iOS; + Minimum = Xamarin.SdkVersions.MiniOSVersion; +#elif __TVOS__ + Platform = ApplePlatform.TVOS; + Minimum = Xamarin.SdkVersions.MinTVOSVersion; +#elif __WATCHOS__ + Platform = ApplePlatform.WatchOS; + Minimum = Xamarin.SdkVersions.MinWatchOSVersion; +#elif MONOMAC + Platform = ApplePlatform.MacOSX; + Minimum = Xamarin.SdkVersions.MinOSXVersion; +#else + #error No Platform Defined +#endif +#else #if __MACCATALYST__ Platform = PlatformName.MacCatalyst; Minimum = Xamarin.SdkVersions.MinMacCatalystVersion; @@ -58,10 +84,21 @@ namespace Introspection { #else #error No Platform Defined #endif +#endif // NET +#if NET + Filter = (OSPlatformAttribute arg) => { + if (!(arg is SupportedOSPlatformAttribute attrib)) + return true; + if (!arg.TryParse (out ApplePlatform? platform, out var version)) + return true; + return platform != Platform; + }; +#else Filter = (AvailabilityBaseAttribute arg) => { return (arg.AvailabilityKind != AvailabilityKind.Introduced) || (arg.Platform != Platform); }; +#endif } bool FoundInProtocols (MemberInfo m, Type t) @@ -120,7 +157,11 @@ namespace Introspection { return false; } +#if NET + void CheckIntroduced (Type t, OSPlatformAttribute ta, MemberInfo m) +#else void CheckIntroduced (Type t, AvailabilityBaseAttribute ta, MemberInfo m) +#endif { var ma = CheckAvailability (m); if (ta == null || ma == null) @@ -130,8 +171,17 @@ namespace Introspection { if (FoundInProtocols (m, t)) return; +#if NET + if (!ta.TryParse (out ApplePlatform? platform, out var taVersion)) + return; + if (!ma.TryParse (out ApplePlatform? _, out var maVersion)) + return; +#else + var taVersion = ta.Version; + var maVersion = ma.Version; +#endif // Duplicate checks, e.g. same attribute on member and type (extranous metadata) - if (ma.Version == ta.Version) { + if (maVersion == taVersion) { switch (t.FullName) { case "AppKit.INSAccessibility": // special case for [I]NSAccessibility type (10.9) / protocol (10.10) mix up @@ -139,13 +189,13 @@ namespace Introspection { // better some dupes than being inaccurate when protocol members are inlined break; default: - AddErrorLine ($"[FAIL] {ma.Version} ({m}) == {ta.Version} ({t})"); + AddErrorLine ($"[FAIL] {maVersion} ({m}) == {taVersion} ({t})"); break; } } // Consistency checks, e.g. member lower than type // note: that's valid in some cases, like a new base type being introduced - if (ma.Version < ta.Version) { + if (maVersion < taVersion) { switch (t.FullName) { case "CoreBluetooth.CBPeer": switch (m.ToString ()) { @@ -162,7 +212,7 @@ namespace Introspection { return; break; } - AddErrorLine ($"[FAIL] {ma.Version} ({m}) < {ta.Version} ({t})"); + AddErrorLine ($"[FAIL] {maVersion} ({m}) < {taVersion} ({t})"); } } @@ -218,77 +268,121 @@ namespace Introspection { return s; } +#if NET + protected OSPlatformAttribute CheckAvailability (ICustomAttributeProvider cap) +#else protected AvailabilityBaseAttribute CheckAvailability (ICustomAttributeProvider cap) +#endif { var attrs = cap.GetCustomAttributes (false); foreach (var ca in attrs) { - var a = ca; #if NET - a = (a as OSPlatformAttribute)?.Convert (); + if (!(ca is OSPlatformAttribute aa)) + continue; +#else + if (!(ca is AvailabilityBaseAttribute aa)) + continue; #endif - if (a is AvailabilityBaseAttribute aa) { - if (Filter (aa)) - continue; - // FIXME should be `<=` but that another large change best done in a different PR - if ((aa.AvailabilityKind == AvailabilityKind.Introduced) && (aa.Version < Minimum)) { - switch (aa.Architecture) { - case PlatformArchitecture.All: - case PlatformArchitecture.None: - AddErrorLine ($"[FAIL] {aa.Version} <= {Minimum} (Min) on '{ToString (cap)}'."); - break; - default: - // An old API still needs the annotations when not available on all architectures - // e.g. NSMenuView is macOS 10.0 but only 32 bits - break; - } + if (Filter (aa)) + continue; + +#if NET + if (!aa.TryParse (out ApplePlatform? platform, out var aaVersion)) + continue; + + // FIXME should be `<=` but that another large change best done in a different PR + var isAvailableBeforeMinimum = aa is SupportedOSPlatformAttribute && aaVersion < Minimum; +#else + // FIXME should be `<=` but that another large change best done in a different PR + bool isAvailableBeforeMinimum = false; + var aaVersion = aa.Version; + if ((aa.AvailabilityKind == AvailabilityKind.Introduced) && (aaVersion < Minimum)) { + switch (aa.Architecture) { + case PlatformArchitecture.All: + case PlatformArchitecture.None: + isAvailableBeforeMinimum = true; + break; + default: + // An old API still needs the annotations when not available on all architectures + // e.g. NSMenuView is macOS 10.0 but only 32 bits + break; } - if (aa.Version > Maximum) - AddErrorLine ($"[FAIL] {aa.Version} > {Maximum} (Max) on '{ToString (cap)}'."); - return aa; } +#endif + if (isAvailableBeforeMinimum) + AddErrorLine ($"[FAIL] {aaVersion} <= {Minimum} (Min) on '{ToString (cap)}'."); + if (aaVersion > Maximum) + AddErrorLine ($"[FAIL] {aaVersion} > {Maximum} (Max) on '{ToString (cap)}'."); + return aa; } return null; } - bool IsUnavailable (ICustomAttributeProvider cap) + bool IsUnavailable (ICustomAttributeProvider cap, out Version? version) { + version = null; foreach (var a in cap.GetCustomAttributes (false)) { var ca = a; #if NET - ca = (a as OSPlatformAttribute)?.Convert (); -#endif + if (a is UnsupportedOSPlatformAttribute aa && aa.TryParse (out ApplePlatform? uaPlatform, out version)) { + if (uaPlatform == Platform) + return true; + } +#else if (ca is UnavailableAttribute ua) { if (ua.Platform == Platform) return true; } +#endif } return false; } - AvailabilityBaseAttribute GetAvailable (ICustomAttributeProvider cap) +#if NET + OSPlatformAttribute GetAvailable (ICustomAttributeProvider cap, out Version? version) +#else + AvailabilityBaseAttribute GetAvailable (ICustomAttributeProvider cap, out Version? version) +#endif { + version = null; foreach (var a in cap.GetCustomAttributes (false)) { var ca = a; #if NET - ca = (a as OSPlatformAttribute)?.Convert (); -#endif + if (ca is SupportedOSPlatformAttribute aa && aa.TryParse (out ApplePlatform? platform, out version)) { + if (platform == Platform) + return aa; + } +#else if (ca is AvailabilityBaseAttribute aa) { if ((aa.AvailabilityKind != AvailabilityKind.Unavailable) && (aa.Platform == Platform)) return aa; } +#endif } return null; } - void CheckUnavailable (Type t, bool typeUnavailable, MemberInfo m) + void CheckUnavailable (Type t, bool typeUnavailable, Version? typeUnavailableVersion, MemberInfo m) { - var ma = GetAvailable (m); - if (typeUnavailable && (ma != null)) - AddErrorLine ($"[FAIL] {m} is marked with {ma} but the type {t.FullName} is [Unavailable ({Platform})]."); + var ma = GetAvailable (m, out var availableVersion); + if (typeUnavailable && (ma != null)) { + if (typeUnavailableVersion is not null && availableVersion is not null) { + if (availableVersion >= typeUnavailableVersion) + AddErrorLine ($"[FAIL] {m} in {m.DeclaringType.FullName} is marked with {ma} in {availableVersion} but the type {t.FullName} is [Unavailable ({Platform})] in {typeUnavailableVersion}"); + } else { + AddErrorLine ($"[FAIL] {m} is marked with {ma} but the type {t.FullName} is [Unavailable ({Platform})]"); + } + } - var mu = IsUnavailable (m); - if (mu && (ma != null)) - AddErrorLine ($"[FAIL] {m} is marked both [Unavailable ({Platform})] and {ma}."); + var mu = IsUnavailable (m, out var unavailableVersion); + if (mu && (ma != null)) { + if (availableVersion is not null && unavailableVersion is not null) { + if (availableVersion >= unavailableVersion) + AddErrorLine ($"[FAIL] {m} is marked both [Unavailable ({Platform})] and {ma}, and it's available in version {availableVersion} which is >= than the unavailable version {unavailableVersion}"); + } else { + AddErrorLine ($"[FAIL] {m} in {m.DeclaringType.FullName} is marked both [Unavailable ({Platform})] and {ma}."); + } + } } [Test] @@ -297,28 +391,132 @@ namespace Introspection { //LogProgress = true; Errors = 0; foreach (Type t in Assembly.GetTypes ()) { + if (SkipUnavailable (t)) + continue; if (LogProgress) Console.WriteLine ($"T: {t}"); - var tu = IsUnavailable (t); - var ta = GetAvailable (t); - if (tu && (ta != null)) - AddErrorLine ($"[FAIL] {t.FullName} is marked both [Unavailable ({Platform})] and {ta}."); + var tu = IsUnavailable (t, out var unavailableVersion); + var ta = GetAvailable (t, out var availableVersion); + if (tu && (ta != null)) { + if (availableVersion is not null && unavailableVersion is not null) { + if (availableVersion >= unavailableVersion) + AddErrorLine ($"[FAIL] {t.FullName} is marked both [Unavailable ({Platform})] and {ta}, and it's available in version {availableVersion} which is >= than the unavailable version {unavailableVersion}"); + } else { + AddErrorLine ($"[FAIL] {t.FullName} is marked both [Unavailable ({Platform})] and {ta}. Available: {availableVersion} Unavailable: {unavailableVersion}"); + } + } foreach (var p in t.GetProperties (BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) { + if (SkipUnavailable (t, p.Name)) + continue; if (LogProgress) - Console.WriteLine ($"P: {p}"); - CheckUnavailable (t, tu, p); + Console.WriteLine ($"P: {p.Name}"); + CheckUnavailable (t, tu, unavailableVersion, p); } foreach (var m in t.GetMembers (BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) { + if (SkipUnavailable (t, m.Name)) + continue; if (LogProgress) - Console.WriteLine ($"M: {m}"); - CheckUnavailable (t, tu, m); + Console.WriteLine ($"M: {m.Name}"); + CheckUnavailable (t, tu, unavailableVersion, m); } } AssertIfErrors ("{0} API with mixed [Unavailable] and availability attributes", Errors); } + protected virtual bool SkipUnavailable (Type type) + { +#if __MACCATALYST__ + switch (type.Namespace) { + case "AddressBook": { + // The entire framework was introduced and deprecated in the same Mac Catalyst version + return true; + } + } +#endif + + switch (type.FullName) { +#if __MACCATALYST__ + case "SafariServices.SFContentBlockerErrorCode": + case "SafariServices.SFContentBlockerErrorCodeExtensions": + // introduced and deprecated in the same Mac Catalyst version + return true; +#endif + } + return false; + } + + protected virtual bool SkipUnavailable (Type type, string memberName) + { + switch (type.FullName) { +#if __MACCATALYST__ + case "AudioUnit.AudioComponent": + switch (memberName) { + case "LastActiveTime": + // introduced and deprecated in the same Mac Catalyst version + return true; + } + break; +#endif + case "CarPlay.CPApplicationDelegate": + switch (memberName) { + case "DidDiscardSceneSessions": + case "GetConfiguration": + case "GetHandlerForIntent": + case "ShouldAutomaticallyLocalizeKeyCommands": + case "ShouldRestoreSecureApplicationState": + case "ShouldSaveSecureApplicationState": + // CPApplicationDelegate is deprecated in macOS 10.15, but these members are pulled in from the UIApplicationDelegate protocol (which is not deprecated) + return true; + } + break; + case "GameKit.GKScore": { + switch (memberName) { + case "ReportLeaderboardScores": + case "ReportLeaderboardScoresAsync": + // Apple introduced and deprecated this method in the same OS version. + return true; + } + break; + } + case "Intents.INNoteContentTypeResolutionResult": { + switch (memberName) { + case "GetConfirmationRequired": + case "GetUnsupported": + // These are static members that have been re-implemented from the base class - the base class isn't deprecated, while INNoteContentTypeResolutionResult is. + return true; + } + break; + } + case "MobileCoreServices.UTType": { + switch (memberName) { + case "UniversalSceneDescriptionMobile": + case "get_UniversalSceneDescriptionMobile": + // Apple added new members to a deprecated enum + return true; + } + break; + } + case "SceneKit.SCNLayer": { + switch (memberName) { + case "CurrentViewport": + case "TemporalAntialiasingEnabled": + case "get_CurrentViewport": + case "get_TemporalAntialiasingEnabled": + case "set_TemporalAntialiasingEnabled": + case "get_UsesReverseZ": + case "set_UsesReverseZ": + case "UsesReverseZ": + // SCNLayer is deprecated in macOS 10.15, but these members are pulled in from the SCNSceneRenderer protocol (which is not deprecated) + return true; + } + break; + } + } + return false; + } + static HashSet member_level = new HashSet (); void CheckDupes (MemberInfo m, Type t, ISet type_level) @@ -381,12 +579,21 @@ namespace Introspection { } #if NET + static bool IsAvailabilityBaseAttributeType (Type type) + { + if (type is null) + return false; + if (type.Name == "AvailabilityBaseAttribute") + return true; + return IsAvailabilityBaseAttributeType (type.BaseType); + } + string CheckLegacyAttributes (ICustomAttributeProvider cap) { var sb = new StringBuilder (); foreach (var a in cap.GetCustomAttributes (false)) { - if (a is AvailabilityBaseAttribute aa) { - sb.AppendLine (aa.ToString ()); + if (IsAvailabilityBaseAttributeType (a.GetType ())) { + sb.AppendLine (a.ToString ()); } } return sb.ToString (); diff --git a/tests/introspection/ApiTypoTest.cs b/tests/introspection/ApiTypoTest.cs index 373128e9c9..8a2840d3c1 100644 --- a/tests/introspection/ApiTypoTest.cs +++ b/tests/introspection/ApiTypoTest.cs @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using NUnit.Framework; @@ -34,6 +35,7 @@ using AppKit; using UIKit; #endif using Foundation; +using Xamarin.Tests; using Xamarin.Utils; namespace Introspection @@ -769,8 +771,13 @@ namespace Introspection return false; if (mi.GetCustomAttributes (true).Any ()) return true; +#if NET + if (mi.GetCustomAttributes (true).Any ((v) => v.TryParse (out ApplePlatform? platform, out var _) && platform == PlatformInfo.Host.Name)) + return true; +#else if (mi.GetCustomAttributes (true).Any ()) return true; +#endif return IsObsolete (mi.DeclaringType); } @@ -888,8 +895,10 @@ namespace Introspection message = ((AdviceAttribute)attribute).Message; if (attribute is ObsoleteAttribute) message = ((ObsoleteAttribute)attribute).Message; +#if !NET if (attribute is AvailabilityBaseAttribute) message = ((AvailabilityBaseAttribute)attribute).Message; +#endif return message; } diff --git a/tests/introspection/Mac/introspection-mac.csproj b/tests/introspection/Mac/introspection-mac.csproj index 28e20bcd5e..36bcb422e9 100644 --- a/tests/introspection/Mac/introspection-mac.csproj +++ b/tests/introspection/Mac/introspection-mac.csproj @@ -15,6 +15,7 @@ x86_64 PackageReference + latest true diff --git a/tests/introspection/dotnet/shared.csproj b/tests/introspection/dotnet/shared.csproj index 2e87c8ffcd..eac7aaa664 100644 --- a/tests/introspection/dotnet/shared.csproj +++ b/tests/introspection/dotnet/shared.csproj @@ -133,6 +133,9 @@ ApplePlatform.cs + + OSPlatformAttributeExtensions.cs + SdkVersions.cs diff --git a/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj b/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj index d19d499ed7..195092205b 100644 --- a/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj +++ b/tests/monotouch-test/dotnet/macOS/monotouch-test.csproj @@ -34,6 +34,9 @@ Execution.cs + + OSPlatformAttributeExtensions.cs + TargetFramework.cs diff --git a/tools/common/OSPlatformAttributeExtensions.cs b/tools/common/OSPlatformAttributeExtensions.cs new file mode 100644 index 0000000000..9f4e54ca69 --- /dev/null +++ b/tools/common/OSPlatformAttributeExtensions.cs @@ -0,0 +1,119 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; + +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.Utils { + internal static class OSPlatformAttributeExtensions { +#if NET + public static bool TryParse (string? platformName, [NotNullWhen (true)] out string? platform, out Version? version) +#else + public static bool TryParse (string? platformName, out string? platform, out Version? version) +#endif + { + platform = null; + version = null; + + if (string.IsNullOrEmpty (platformName)) + return false; + + var versionIndex = -1; +#if NET + for (var i = 0; i < platformName.Length; i++) { +#else + for (var i = 0; i < platformName!.Length; i++) { +#endif + if (platformName [i] >= '0' && platformName [i] <= '9') { + versionIndex = i; + break; + } + } + string supportedPlatform; + string? supportedVersion = null; + + if (versionIndex == -1) { + supportedPlatform = platformName; + } else { + supportedPlatform = platformName.Substring (0, versionIndex); + supportedVersion = platformName.Substring (versionIndex); + } + + platform = supportedPlatform.ToLowerInvariant (); + + if (string.IsNullOrEmpty (supportedVersion)) + return true; + + return Version.TryParse (supportedVersion, out version); + } + +#if NET + public static bool TryParse (string? platformName, [NotNullWhen (true)] out ApplePlatform? platform, out Version? version) +#else + public static bool TryParse (string? platformName, out ApplePlatform? platform, out Version? version) +#endif + { + platform = null; + + if (!TryParse (platformName, out string? supportedPlatform, out version)) + return false; + +#if NET + return TryGetApplePlatform (supportedPlatform, out platform); +#else + return TryGetApplePlatform (supportedPlatform!, out platform); +#endif + } + +#if NET + public static bool TryParse (this OSPlatformAttribute self, [NotNullWhen (true)] out string? platform, out Version? version) + { + if (self is null) + throw new ArgumentNullException (nameof (self)); + + return TryParse (self.PlatformName, out platform, out version); + } + + public static bool TryParse (this OSPlatformAttribute self, [NotNullWhen (true)] out ApplePlatform? platform, out Version? version) + { + platform = null; + + if (!TryParse (self, out string? supportedPlatform, out version)) + return false; + + return TryGetApplePlatform (supportedPlatform, out platform); + } +#endif + +#if NET + static bool TryGetApplePlatform (string supportedPlatform, [NotNullWhen (true)] out ApplePlatform? platform) +#else + static bool TryGetApplePlatform (string supportedPlatform, out ApplePlatform? platform) +#endif + { + switch (supportedPlatform) { + case "ios": + platform = ApplePlatform.iOS; + break; + case "tvos": + platform = ApplePlatform.TVOS; + break; + case "macos": + platform = ApplePlatform.MacOSX; + break; + case "maccatalyst": + platform = ApplePlatform.MacCatalyst; + break; + case "watchos": + platform = ApplePlatform.WatchOS; + break; + default: + platform = null; + return false; + } + return true; + } + } +} diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 1f45595cbf..5c9d3041f1 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -1703,7 +1703,6 @@ namespace Registrar { bool GetDotNetAvailabilityAttribute (ICustomAttribute ca, ApplePlatform currentPlatform, out Version sdkVersion, out string message) { var caType = ca.AttributeType; - var platformName = ApplePlatform.None; sdkVersion = null; message = null; @@ -1717,53 +1716,12 @@ namespace Registrar { throw ErrorHelper.CreateError (4163, Errors.MT4163, caType.Name, ca.ConstructorArguments.Count); } - if (string.IsNullOrEmpty (supportedPlatformAndVersion)) + if (!OSPlatformAttributeExtensions.TryParse (supportedPlatformAndVersion, out ApplePlatform? platformName, out sdkVersion)) return false; - var versionIndex = -1; - for (var i = 0; i < supportedPlatformAndVersion.Length; i++) { - if (supportedPlatformAndVersion [i] >= '0' && supportedPlatformAndVersion [i] <= '9') { - versionIndex = i; - break; - } - } - string supportedPlatform; - string supportedVersion = null; - - if (versionIndex == -1) { - supportedPlatform = supportedPlatformAndVersion; - } else { - supportedPlatform = supportedPlatformAndVersion.Substring (0, versionIndex); - supportedVersion = supportedPlatformAndVersion.Substring (versionIndex); - } - - supportedPlatform = supportedPlatform.ToLowerInvariant (); - switch (supportedPlatform) { - case "ios": - platformName = ApplePlatform.iOS; - break; - case "tvos": - platformName = ApplePlatform.TVOS; - break; - case "macos": - platformName = ApplePlatform.MacOSX; - break; - case "maccatalyst": - platformName = ApplePlatform.MacCatalyst; - break; - case "watchos": - platformName = ApplePlatform.WatchOS; - break; - default: - return false; - } - if (platformName != currentPlatform) return false; - if (!Version.TryParse (supportedVersion, out sdkVersion)) - return false; - return true; } #endif // NET diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 1881bd9532..693f8bf4aa 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -98,6 +98,9 @@ external\tools\common\PListExtensions.cs + + external\tools\common\OSPlatformAttributeExtensions.cs + external\tools\common\StaticRegistrar.cs diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index 9eeae2bc13..c92caaeda4 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -354,6 +354,9 @@ tools\common\Execution.cs + + tools\common\OSPlatformAttributeExtensions.cs + tools\common\StaticRegistrar.cs diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index e1e9bbaded..1a96ff0d5d 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -335,6 +335,9 @@ tools\common\TargetFramework.cs + + tools\common\OSPlatformAttributeExtensions.cs + tools\common\StaticRegistrar.cs