xamarin-macios/tests/common/PlatformInfo.cs

280 строки
9.3 KiB
C#
Исходник Обычный вид История

//
// PlatformInfo.cs: info about the host platform
// and AvailabilityBaseAttribute extensions for tests
//
// Author:
// Aaron Bockover <abock@xamarin.com>
//
// Copyright 2015 Xamarin Inc. All rights reserved.
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
2021-04-10 18:09:14 +03:00
using System.Runtime.Versioning;
using ObjCRuntime;
using Foundation;
#if !(MONOMAC || __MACOS__)
using UIKit;
#endif
using Xamarin.Utils;
namespace Xamarin.Tests
{
public sealed class PlatformInfo
{
static PlatformInfo GetHostPlatformInfo ()
{
string name;
string version;
#if __MACCATALYST__
name = "MacCatalyst";
version = iOSSupportVersion;
#elif __TVOS__ || __IOS__
name = UIDevice.CurrentDevice.SystemName;
version = UIDevice.CurrentDevice.SystemVersion;
#elif __WATCHOS__
name = WatchKit.WKInterfaceDevice.CurrentDevice.SystemName;
version = WatchKit.WKInterfaceDevice.CurrentDevice.SystemVersion;
#elif MONOMAC || __MACOS__
using (var plist = NSDictionary.FromFile ("/System/Library/CoreServices/SystemVersion.plist")) {
name = (NSString)plist ["ProductName"];
version = (NSString)plist ["ProductVersion"];
}
#else
#error Unknown platform
#endif
name = name?.Replace (" ", String.Empty)?.ToLowerInvariant ();
if (name == null)
throw new FormatException ("Product name is `null`");
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))
platformInfo.Name = PlatformName.MacOSX;
else if (name.StartsWith ("ios", StringComparison.Ordinal) || name.StartsWith ("iphoneos", StringComparison.Ordinal))
platformInfo.Name = PlatformName.iOS;
else if (name.StartsWith ("tvos", StringComparison.Ordinal))
platformInfo.Name = PlatformName.TvOS;
else if (name.StartsWith ("watchos", StringComparison.Ordinal))
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;
}
#if __MACCATALYST__
static string? _iOSSupportVersion;
internal static string iOSSupportVersion {
get {
if (_iOSSupportVersion is null) {
// This is how Apple does it: https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm#L100-L105
using var dict = NSMutableDictionary.FromFile ("/System/Library/CoreServices/SystemVersion.plist");
using var str = (NSString) "iOSSupportVersion";
using var obj = dict.ObjectForKey (str);
_iOSSupportVersion = obj.ToString ();
}
return _iOSSupportVersion;
}
}
#endif
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 ()
{
}
}
public static class AvailabilityExtensions
{
public static bool IsAvailableOnHostPlatform (this ICustomAttributeProvider attributeProvider)
{
return attributeProvider.IsAvailable (PlatformInfo.Host);
}
public static bool IsAvailable (this ICustomAttributeProvider attributeProvider, PlatformInfo targetPlatform)
{
var customAttributes = attributeProvider.GetCustomAttributes (true);
2021-04-10 18:09:14 +03:00
#if NET
customAttributes = customAttributes.ToArray (); // don't iterate twice
if (customAttributes.Any (v => v is ObsoleteAttribute))
return false;
#endif
return customAttributes
#if NET
.OfType<OSPlatformAttribute> ()
2021-04-10 18:09:14 +03:00
#else
.OfType<AvailabilityBaseAttribute> ()
2021-04-10 18:09:14 +03:00
#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
static IEnumerable<(OSPlatformAttribute Attribute, ApplePlatform Platform, Version Version)> ParseAttributes (IEnumerable<OSPlatformAttribute> attributes)
{
foreach (var attr in attributes) {
if (!attr.TryParse (out ApplePlatform? platform, out var version))
continue;
yield return new (attr, platform.Value, version);
}
}
public static bool IsAvailable (this IEnumerable<OSPlatformAttribute> attributes, PlatformInfo targetPlatform)
{
var parsedAttributes = ParseAttributes (attributes);
var available = IsAvailable (parsedAttributes, targetPlatform, targetPlatform.Name);
if (available.HasValue)
return available.Value;
if (targetPlatform.Name == ApplePlatform.MacCatalyst) {
available = IsAvailable (parsedAttributes, targetPlatform, ApplePlatform.iOS);
if (available.HasValue)
return available.Value;
}
// 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;
}
public static bool? IsAvailable (IEnumerable<(OSPlatformAttribute Attribute, ApplePlatform Platform, Version Version)> attributes, PlatformInfo targetPlatform, ApplePlatform attributePlatform)
{
// First we check for any unsupported attributes, and only once we know that there aren't any unsupported
// attributes, we check for supported attributes. Otherwise we might determine that an API is available
// if we check the supported attribute first for OS=3.0 here:
// [SupportedOSPlatform ("1.0")
// [UnsupportedOSPlatform ("2.0")
// if we run into the SupportedOSPlatform attribute first.
foreach (var (attr, platform, version) in attributes) {
if (platform != attributePlatform)
continue;
if (attr is UnsupportedOSPlatformAttribute) {
var isUnsupported = version is not null && targetPlatform.Version >= version;
// At this point we can't ascertain that the API is available, only that it's unavailable,
// so only return in that case. We need to check the SupportedOSPlatform attributes
// to see if the API is available.
if (isUnsupported)
return false;
}
}
foreach (var (attr, platform, version) in attributes) {
if (platform != attributePlatform)
continue;
if (attr is SupportedOSPlatformAttribute)
return version is null || targetPlatform.Version >= version;
}
return null;
}
#else
public static bool IsAvailable (this IEnumerable<AvailabilityBaseAttribute> attributes, PlatformInfo targetPlatform)
{
// always "available" from a binding perspective if
// there are no explicit annotations saying otherwise
var available = true;
foreach (var attr in attributes) {
if (attr.Platform != targetPlatform.Name)
continue;
switch (attr.AvailabilityKind) {
case AvailabilityKind.Introduced:
if (attr.Version != null)
available &= targetPlatform.Version >= attr.Version;
if (attr.Architecture != PlatformArchitecture.None &&
attr.Architecture != PlatformArchitecture.All)
available &= attr.Architecture.HasFlag (targetPlatform.Architecture);
break;
case AvailabilityKind.Deprecated:
case AvailabilityKind.Obsoleted:
if (attr.Version != null)
available &= targetPlatform.Version < attr.Version;
// FIXME: handle architecture-level _un_availability?
// we didn't do this with the old AvailabilityAttribute...
break;
case AvailabilityKind.Unavailable:
available = false;
break;
}
if (!available)
return false;
}
return available;
}
#endif
}
}