[introspection] Migrate .NET code to use the new .NET-style availability attributes. (#13363)
* [tools] Extract the logic to parse OSPlatformAttribute platform names to a separate file/class. * [introspection] Migrate .NET code to use the new .NET-style availability attributes. This also means using the 'ApplePlatform' enum instead of the 'PlatformName' enum, because the latter will be removed in .NET. * [FileProvider] Exclude some deprecated API from .NET. * [AVFoundation] Adjust availability attribute for AVCaptureStillImageOutput.HighResolutionStillImageOutputEnabled. * Update tests.
This commit is contained in:
Родитель
b55ee6d521
Коммит
5329b19f62
|
@ -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<AvailabilityBaseAttribute> ();
|
||||
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<AvailabilityBaseAttribute> ()
|
||||
.IsAvailable (targetPlatform);
|
||||
customAttributes = customAttributes.ToArray (); // don't iterate twice
|
||||
if (customAttributes.Any (v => v is ObsoleteAttribute))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
return customAttributes
|
||||
#if NET
|
||||
.OfType<OSPlatformAttribute> ()
|
||||
#else
|
||||
.OfType<AvailabilityBaseAttribute> ()
|
||||
#endif
|
||||
.IsAvailable (targetPlatform);
|
||||
}
|
||||
|
||||
#if NET
|
||||
public static bool IsAvailableOnHostPlatform (this IEnumerable<OSPlatformAttribute> attributes)
|
||||
#else
|
||||
public static bool IsAvailableOnHostPlatform (this IEnumerable<AvailabilityBaseAttribute> attributes)
|
||||
#endif
|
||||
{
|
||||
return attributes.IsAvailable (PlatformInfo.Host);
|
||||
}
|
||||
|
||||
#if NET
|
||||
public static bool IsAvailable (this IEnumerable<OSPlatformAttribute> 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<AvailabilityBaseAttribute> attributes, PlatformInfo targetPlatform)
|
||||
{
|
||||
// always "available" from a binding perspective if
|
||||
|
@ -214,5 +225,6 @@ namespace Xamarin.Tests
|
|||
|
||||
return available;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<OSPlatformAttribute, bool> Filter { get; set; }
|
||||
protected ApplePlatform Platform { get; set; }
|
||||
#else
|
||||
protected Func<AvailabilityBaseAttribute, bool> 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<string> member_level = new HashSet<string> ();
|
||||
|
||||
void CheckDupes (MemberInfo m, Type t, ISet<string> 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 ();
|
||||
|
|
|
@ -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<ObsoleteAttribute> (true).Any ())
|
||||
return true;
|
||||
#if NET
|
||||
if (mi.GetCustomAttributes<UnsupportedOSPlatformAttribute> (true).Any ((v) => v.TryParse (out ApplePlatform? platform, out var _) && platform == PlatformInfo.Host.Name))
|
||||
return true;
|
||||
#else
|
||||
if (mi.GetCustomAttributes<ObsoletedAttribute> (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;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<XamMacArch>x86_64</XamMacArch>
|
||||
<DefineConstants></DefineConstants>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
|
|
@ -133,6 +133,9 @@
|
|||
<Compile Include="$(IntrospectionTestDirectory)\..\..\tools\common\ApplePlatform.cs">
|
||||
<Link>ApplePlatform.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="$(IntrospectionTestDirectory)\..\..\tools\common\OSPlatformAttributeExtensions.cs">
|
||||
<Link>OSPlatformAttributeExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="$(IntrospectionTestDirectory)\..\..\tools\common\SdkVersions.cs">
|
||||
<Link>SdkVersions.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
<Compile Include="..\..\..\..\tools\common\Execution.cs">
|
||||
<Link>Execution.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\..\..\tools\common\OSPlatformAttributeExtensions.cs">
|
||||
<Link>OSPlatformAttributeExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\..\..\..\tools\common\TargetFramework.cs">
|
||||
<Link>TargetFramework.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -98,6 +98,9 @@
|
|||
<Compile Include="..\common\PListExtensions.cs">
|
||||
<Link>external\tools\common\PListExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\common\OSPlatformAttributeExtensions.cs">
|
||||
<Link>external\tools\common\OSPlatformAttributeExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\common\StaticRegistrar.cs">
|
||||
<Link>external\tools\common\StaticRegistrar.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -354,6 +354,9 @@
|
|||
<Compile Include="..\common\Execution.cs">
|
||||
<Link>tools\common\Execution.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\common\OSPlatformAttributeExtensions.cs">
|
||||
<Link>tools\common\OSPlatformAttributeExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\common\StaticRegistrar.cs">
|
||||
<Link>tools\common\StaticRegistrar.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -335,6 +335,9 @@
|
|||
<Compile Include="..\common\TargetFramework.cs">
|
||||
<Link>tools\common\TargetFramework.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\common\OSPlatformAttributeExtensions.cs">
|
||||
<Link>tools\common\OSPlatformAttributeExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\common\StaticRegistrar.cs">
|
||||
<Link>tools\common\StaticRegistrar.cs</Link>
|
||||
</Compile>
|
||||
|
|
Загрузка…
Ссылка в новой задаче