using System; using System.Runtime.InteropServices; namespace Deploymentcenter.Client { /// /// Laufzeitkennung (.NET-RID) eines Releases. /// /// Der UpdateService kannte lange nur Projekt, Kanal und Version. Sobald /// fuer mehrere Plattformen gebaut wurde, landeten alle Pakete unter /// derselben Version im selben Kanal und ueberschrieben sich - ein /// Linux-System zog sich das Windows-Paket. Diese Klasse liefert die /// gemeinsame Sprache dafuer: Packager, Agent und Server benutzen /// dieselbe Schreibweise und dieselben Ablagepfade. /// public static class PlatformId { /// Kennung fuer plattformunabhaengige Releases. public const string Any = "any"; /// /// Kennung des laufenden Systems, z. B. "win-x64" oder "linux-arm64". /// public static string Current { get { #if NET8_0_OR_GREATER string rid = RuntimeInformation.RuntimeIdentifier; if (!string.IsNullOrWhiteSpace(rid)) { return Normalize(rid); } #endif return Normalize(ComposeFallback()); } } /// /// Bringt eine Kennung auf die Form, die auch der Server verwendet. /// Unbrauchbare Angaben werden zu "any" - ein verunglueckter Parameter /// soll nicht dazu fuehren, dass gar kein Release mehr gefunden wird. /// public static string Normalize(string? platform) { string value = (platform ?? string.Empty).Trim().ToLowerInvariant(); if (value.Length == 0) return Any; // Portable RIDs tragen manchmal eine Betriebssystemversion // ("win10-x64", "ubuntu.22.04-x64"). Fuer die Auswahl eines Pakets // ist nur die Familie samt Architektur interessant. value = CollapseVersioned(value); foreach (char c in value) { bool allowed = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_'; if (!allowed) return Any; } return value.Length > 32 ? Any : value; } /// Ist das die plattformunabhaengige Kennung? public static bool IsAny(string? platform) { return Normalize(platform) == Any; } /// /// Pfadsegment fuer die Release-Ablage. /// /// Plattformunabhaengige Releases behalten bewusst den alten Pfad /// ohne Zwischenebene. Andernfalls waeren alle bereits ausgelieferten /// Anwendungen von einem Tag auf den anderen von ihren Updates /// abgeschnitten. /// public static string PathSegment(string? platform) { string normalized = Normalize(platform); return normalized == Any ? string.Empty : "/" + normalized; } /// /// Versucht, die Kennung aus einem Publish-Pfad zu lesen, etwa /// "bin/Release/net8.0/linux-x64/publish". Liefert null, wenn der Pfad /// keine erkennbare Kennung enthaelt. /// public static string? InferFromPath(string? path) { if (string.IsNullOrWhiteSpace(path)) return null; string[] segments = path!.Replace('\\', '/').Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // Von hinten, weil die Kennung in der Konvention unmittelbar vor // "publish" steht und weiter vorne zufaellig gleichnamige Ordner // liegen koennen. for (int i = segments.Length - 1; i >= 0; i--) { string candidate = segments[i].ToLowerInvariant(); if (LooksLikeRid(candidate)) { return Normalize(candidate); } } return null; } private static bool LooksLikeRid(string value) { int dash = value.IndexOf('-'); if (dash <= 0 || dash == value.Length - 1) return false; string os = value.Substring(0, dash); string rest = value.Substring(dash + 1); bool knownOs = os == "win" || os == "linux" || os == "osx" || os.StartsWith("win", StringComparison.Ordinal) || os.StartsWith("linux", StringComparison.Ordinal) || os.StartsWith("osx", StringComparison.Ordinal); if (!knownOs) return false; return rest == "x64" || rest == "x86" || rest == "arm64" || rest == "arm" || rest.EndsWith("-x64", StringComparison.Ordinal) || rest.EndsWith("-arm64", StringComparison.Ordinal); } /// /// "win10-x64" und "ubuntu.22.04-x64" auf "win-x64" bzw. "linux-x64" /// zurueckfuehren. Ohne das entstuenden fuer dasselbe Paket mehrere /// Kennungen, je nachdem, auf welchem System gebaut wurde. /// private static string CollapseVersioned(string value) { int dash = value.LastIndexOf('-'); if (dash <= 0) return value; string os = value.Substring(0, dash); string arch = value.Substring(dash + 1); if (os.StartsWith("win", StringComparison.Ordinal)) return "win-" + arch; if (os.StartsWith("osx", StringComparison.Ordinal) || os.StartsWith("macos", StringComparison.Ordinal)) return "osx-" + arch; // musl ist eine eigene Zielplattform - ein glibc-Paket laeuft dort // nicht, die Unterscheidung muss also erhalten bleiben. if (os.Contains("musl")) return "linux-musl-" + arch; if (os.StartsWith("linux", StringComparison.Ordinal) || os.StartsWith("ubuntu", StringComparison.Ordinal) || os.StartsWith("debian", StringComparison.Ordinal) || os.StartsWith("alpine", StringComparison.Ordinal) || os.StartsWith("rhel", StringComparison.Ordinal) || os.StartsWith("centos", StringComparison.Ordinal) || os.StartsWith("fedora", StringComparison.Ordinal)) { return os.StartsWith("alpine", StringComparison.Ordinal) ? "linux-musl-" + arch : "linux-" + arch; } return value; } private static string ComposeFallback() { string os; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) os = "win"; else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) os = "osx"; else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) os = "linux"; else return Any; string arch; switch (RuntimeInformation.ProcessArchitecture) { case Architecture.X64: arch = "x64"; break; case Architecture.X86: arch = "x86"; break; case Architecture.Arm64: arch = "arm64"; break; case Architecture.Arm: arch = "arm"; break; default: return Any; } return os + "-" + arch; } } }