You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
7.5 KiB
235 lines
7.5 KiB
using System.Diagnostics; |
|
using System.Diagnostics.CodeAnalysis; |
|
using System.Reflection; |
|
using System.Runtime.InteropServices; |
|
using System.Runtime.Versioning; |
|
using Semver; |
|
using StabilityMatrix.Core.Extensions; |
|
using StabilityMatrix.Core.Models.FileInterfaces; |
|
|
|
namespace StabilityMatrix.Core.Helper; |
|
|
|
/// <summary> |
|
/// Compatibility layer for checks and file paths on different platforms. |
|
/// </summary> |
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
|
public static class Compat |
|
{ |
|
private const string AppName = "StabilityMatrix"; |
|
|
|
public static SemVersion AppVersion { get; set; } |
|
|
|
// OS Platform |
|
public static PlatformKind Platform { get; } |
|
|
|
[SupportedOSPlatformGuard("windows")] |
|
public static bool IsWindows => Platform.HasFlag(PlatformKind.Windows); |
|
|
|
[SupportedOSPlatformGuard("linux")] |
|
public static bool IsLinux => Platform.HasFlag(PlatformKind.Linux); |
|
|
|
[SupportedOSPlatformGuard("macos")] |
|
public static bool IsMacOS => Platform.HasFlag(PlatformKind.MacOS); |
|
|
|
[UnsupportedOSPlatformGuard("windows")] |
|
public static bool IsUnix => Platform.HasFlag(PlatformKind.Unix); |
|
|
|
public static bool IsArm => Platform.HasFlag(PlatformKind.Arm); |
|
public static bool IsX64 => Platform.HasFlag(PlatformKind.X64); |
|
|
|
// Paths |
|
|
|
/// <summary> |
|
/// AppData directory path. On Windows this is %AppData%, on Linux and MacOS this is ~/.config |
|
/// </summary> |
|
public static DirectoryPath AppData { get; } |
|
|
|
/// <summary> |
|
/// AppData + AppName (e.g. %AppData%\StabilityMatrix) |
|
/// </summary> |
|
public static DirectoryPath AppDataHome { get; private set; } |
|
|
|
/// <summary> |
|
/// Set AppDataHome to a custom path. Used for testing. |
|
/// </summary> |
|
public static void SetAppDataHome(string path) |
|
{ |
|
AppDataHome = path; |
|
} |
|
|
|
/// <summary> |
|
/// Current directory the app is in. |
|
/// </summary> |
|
public static DirectoryPath AppCurrentDir { get; } |
|
|
|
/// <summary> |
|
/// Current path to the app binary. |
|
/// </summary> |
|
public static FilePath AppCurrentPath => AppCurrentDir.JoinFile(GetExecutableName()); |
|
|
|
/// <summary> |
|
/// Path to the .app bundle on macOS. |
|
/// </summary> |
|
[SupportedOSPlatform("macos")] |
|
public static DirectoryPath? AppBundleCurrentPath { get; } |
|
|
|
/// <summary> |
|
/// Either the <see cref="AppCurrentPath"/> File or <see cref="AppBundleCurrentPath"/> directory on macOS. |
|
/// </summary> |
|
public static FileSystemPath AppOrBundleCurrentPath => IsMacOS ? AppBundleCurrentPath! : AppCurrentPath; |
|
|
|
// File extensions |
|
/// <summary> |
|
/// Platform-specific executable extension. |
|
/// ".exe" on Windows, Empty string on Linux and MacOS. |
|
/// </summary> |
|
public static string ExeExtension { get; } |
|
|
|
/// <summary> |
|
/// Platform-specific dynamic library extension. |
|
/// ".dll" on Windows, ".dylib" on MacOS, ".so" on Linux. |
|
/// </summary> |
|
public static string DllExtension { get; } |
|
|
|
/// <summary> |
|
/// Delimiter for $PATH environment variable. |
|
/// </summary> |
|
public static char PathDelimiter => IsWindows ? ';' : ':'; |
|
|
|
static Compat() |
|
{ |
|
var infoVersion = Assembly |
|
.GetCallingAssembly() |
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>() |
|
?.InformationalVersion; |
|
|
|
AppVersion = SemVersion.Parse(infoVersion ?? "0.0.0", SemVersionStyles.Strict); |
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|
{ |
|
Platform = PlatformKind.Windows; |
|
AppCurrentDir = AppContext.BaseDirectory; |
|
ExeExtension = ".exe"; |
|
DllExtension = ".dll"; |
|
} |
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|
{ |
|
Platform = PlatformKind.MacOS | PlatformKind.Unix; |
|
|
|
// This is ./<AppName>.app/Contents/MacOS |
|
var macDir = new DirectoryPath(AppContext.BaseDirectory); |
|
// We need to go up two directories to get the .app directory |
|
AppBundleCurrentPath = macDir.Parent?.Parent; |
|
// Then CurrentDir is the next parent |
|
AppCurrentDir = AppBundleCurrentPath!.Parent!; |
|
|
|
ExeExtension = ""; |
|
DllExtension = ".dylib"; |
|
} |
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
|
{ |
|
Platform = PlatformKind.Linux | PlatformKind.Unix; |
|
|
|
// For AppImage builds, the path is in `$APPIMAGE` |
|
var appPath = Environment.GetEnvironmentVariable("APPIMAGE") ?? AppContext.BaseDirectory; |
|
AppCurrentDir = |
|
Path.GetDirectoryName(appPath) ?? throw new Exception("Could not find application directory"); |
|
ExeExtension = ""; |
|
DllExtension = ".so"; |
|
} |
|
else |
|
{ |
|
throw new PlatformNotSupportedException(); |
|
} |
|
|
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) |
|
{ |
|
Platform |= PlatformKind.Arm; |
|
} |
|
else |
|
{ |
|
Platform |= PlatformKind.X64; |
|
} |
|
|
|
AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); |
|
|
|
AppDataHome = AppData + AppName; |
|
} |
|
|
|
/// <summary> |
|
/// Generic function to return different objects based on platform flags. |
|
/// Parameters are checked in sequence with Compat.Platform.HasFlag, |
|
/// the first match is returned. |
|
/// </summary> |
|
/// <exception cref="PlatformNotSupportedException">Thrown when no targets match</exception> |
|
public static T Switch<T>(params (PlatformKind platform, T target)[] targets) |
|
{ |
|
foreach (var (platform, target) in targets) |
|
{ |
|
if (Platform.HasFlag(platform)) |
|
{ |
|
return target; |
|
} |
|
} |
|
|
|
throw new PlatformNotSupportedException( |
|
$"Platform {Platform.ToString()} is not in supported targets: " |
|
+ $"{string.Join(", ", targets.Select(t => t.platform.ToString()))}" |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Get the current application executable name. |
|
/// </summary> |
|
public static string GetExecutableName() |
|
{ |
|
if (IsLinux) |
|
{ |
|
// Use name component of APPIMAGE |
|
var appImage = Environment.GetEnvironmentVariable("APPIMAGE"); |
|
if (string.IsNullOrEmpty(appImage)) |
|
{ |
|
throw new Exception("Could not find APPIMAGE environment variable"); |
|
} |
|
return Path.GetFileName(appImage); |
|
} |
|
using var process = Process.GetCurrentProcess(); |
|
var fullPath = process.MainModule?.ModuleName; |
|
|
|
if (string.IsNullOrEmpty(fullPath)) |
|
{ |
|
throw new Exception("Could not find executable name"); |
|
} |
|
return Path.GetFileName(fullPath); |
|
} |
|
|
|
/// <summary> |
|
/// Get the current application executable or bundle name. |
|
/// </summary> |
|
public static string GetAppName() |
|
{ |
|
// For other platforms, this is the same as the executable name |
|
if (!IsMacOS) |
|
{ |
|
return GetExecutableName(); |
|
} |
|
|
|
// On macOS, get name of current bundle |
|
return Path.GetFileName(AppBundleCurrentPath.Unwrap()); |
|
} |
|
|
|
public static string GetEnvPathWithExtensions(params string[] paths) |
|
{ |
|
var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process); |
|
var newPath = string.Join(PathDelimiter, paths); |
|
|
|
if (string.IsNullOrEmpty(currentPath)) |
|
{ |
|
return string.Join(PathDelimiter, paths); |
|
} |
|
else |
|
{ |
|
return newPath + PathDelimiter + currentPath; |
|
} |
|
} |
|
}
|
|
|