using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Semver;
using Sentry.Protocol;
using StabilityMatrix.Core.Models.FileInterfaces;
namespace StabilityMatrix.Core.Helper;
///
/// Compatibility layer for checks and file paths on different platforms.
///
[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
///
/// AppData directory path. On Windows this is %AppData%, on Linux and MacOS this is ~/.config
///
public static DirectoryPath AppData { get; }
///
/// AppData + AppName (e.g. %AppData%\StabilityMatrix)
///
public static DirectoryPath AppDataHome { get; }
///
/// Current directory the app is in.
///
public static DirectoryPath AppCurrentDir { get; }
///
/// Current path to the app.
///
public static FilePath AppCurrentPath => AppCurrentDir.JoinFile(GetExecutableName());
// File extensions
///
/// Platform-specific executable extension.
/// ".exe" on Windows, Empty string on Linux and MacOS.
///
public static string ExeExtension { get; }
///
/// Platform-specific dynamic library extension.
/// ".dll" on Windows, ".dylib" on MacOS, ".so" on Linux.
///
public static string DllExtension { get; }
///
/// Delimiter for $PATH environment variable.
///
public static char PathDelimiter => IsWindows ? ';' : ':';
static Compat()
{
var infoVersion = Assembly.GetCallingAssembly()
.GetCustomAttribute()?.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;
AppCurrentDir = AppContext.BaseDirectory; // TODO: check this
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.Arm)
{
Platform |= PlatformKind.Arm;
}
else
{
Platform |= PlatformKind.X64;
}
AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
AppDataHome = AppData + AppName;
}
///
/// Generic function to return different objects based on platform flags.
/// Parameters are checked in sequence with Compat.Platform.HasFlag,
/// the first match is returned.
///
/// Thrown when no targets match
public static T Switch(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()))}");
}
///
/// Get the current application executable name.
///
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);
}
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;
}
}
}