|
1 | 1 | using System;
|
| 2 | +using System.Collections.Generic; |
2 | 3 | using System.Diagnostics;
|
3 | 4 | using System.IO;
|
| 5 | +using System.Linq; |
4 | 6 | using System.Runtime.InteropServices;
|
5 | 7 | using GitCredentialManager.Interop.Posix.Native;
|
6 | 8 |
|
@@ -349,52 +351,106 @@ private static string GetOSType()
|
349 | 351 | return "Unknown";
|
350 | 352 | }
|
351 | 353 |
|
| 354 | + private static string _linuxDistroVersion; |
| 355 | + |
352 | 356 | private static string GetOSVersion(ITrace2 trace2)
|
353 | 357 | {
|
| 358 | + // |
| 359 | + // Since .NET 5 we can use Environment.OSVersion because it was updated to |
| 360 | + // return the correct version on Windows & macOS, rather than the manifested |
| 361 | + // version for Windows or the kernel version for macOS. |
| 362 | + // https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/5.0/environment-osversion-returns-correct-version |
| 363 | + // |
| 364 | + // However, we still need to use the old method for Windows on .NET Framework |
| 365 | + // and call into the Win32 API to get the correct version (regardless of app |
| 366 | + // compatibility settings). |
| 367 | +#if NETFRAMEWORK |
354 | 368 | if (IsWindows() && RtlGetVersionEx(out RTL_OSVERSIONINFOEX osvi) == 0)
|
355 | 369 | {
|
356 | 370 | return $"{osvi.dwMajorVersion}.{osvi.dwMinorVersion} (build {osvi.dwBuildNumber})";
|
357 | 371 | }
|
358 |
| - |
359 |
| - if (IsMacOS()) |
| 372 | +#endif |
| 373 | + if (IsWindows() || IsMacOS()) |
360 | 374 | {
|
361 |
| - var psi = new ProcessStartInfo |
362 |
| - { |
363 |
| - FileName = "/usr/bin/sw_vers", |
364 |
| - Arguments = "-productVersion", |
365 |
| - RedirectStandardOutput = true |
366 |
| - }; |
367 |
| - |
368 |
| - using (var swvers = new ChildProcess(trace2, psi)) |
369 |
| - { |
370 |
| - swvers.Start(Trace2ProcessClass.Other); |
371 |
| - swvers.WaitForExit(); |
372 |
| - |
373 |
| - if (swvers.ExitCode == 0) |
374 |
| - { |
375 |
| - return swvers.StandardOutput.ReadToEnd().Trim(); |
376 |
| - } |
377 |
| - } |
| 375 | + return Environment.OSVersion.VersionString; |
378 | 376 | }
|
379 | 377 |
|
380 | 378 | if (IsLinux())
|
381 | 379 | {
|
382 |
| - var psi = new ProcessStartInfo |
383 |
| - { |
384 |
| - FileName = "uname", |
385 |
| - Arguments = "-a", |
386 |
| - RedirectStandardOutput = true |
387 |
| - }; |
| 380 | + return _linuxDistroVersion ??= GetLinuxDistroVersion(); |
388 | 381 |
|
389 |
| - using (var uname = new ChildProcess(trace2, psi)) |
| 382 | + string GetLinuxDistroVersion() |
390 | 383 | {
|
391 |
| - uname.Start(Trace2ProcessClass.Other); |
392 |
| - uname.Process.WaitForExit(); |
| 384 | + // Let's first try to get the distribution information from /etc/os-release |
| 385 | + // (or /usr/lib/os-release) which is required in systemd distributions. |
| 386 | + // https://www.freedesktop.org/software/systemd/man/os-release.html |
| 387 | + foreach (string osReleasePath in new[] { "/etc/os-release", "/usr/lib/os-release" }) |
| 388 | + { |
| 389 | + if (!File.Exists(osReleasePath)) |
| 390 | + { |
| 391 | + continue; |
| 392 | + } |
| 393 | + |
| 394 | + var props = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); |
| 395 | + char[] split = { '=' }; |
| 396 | + string[] lines = File.ReadAllLines(osReleasePath); |
| 397 | + foreach (string line in lines) |
| 398 | + { |
| 399 | + // Each line is a key="value" pair |
| 400 | + string[] kvp = line.Split(split, count: 2); |
| 401 | + if (kvp.Length != 2) |
| 402 | + { |
| 403 | + continue; |
| 404 | + } |
| 405 | + |
| 406 | + props[kvp[0]] = kvp[1].Trim('"'); |
| 407 | + } |
| 408 | + |
| 409 | + // Try to get the PRETTY_NAME first which is a user-friendly description |
| 410 | + // including the distro name and version. |
| 411 | + if (props.TryGetValue("PRETTY_NAME", out string prettyName)) |
| 412 | + { |
| 413 | + return prettyName; |
| 414 | + } |
| 415 | + |
| 416 | + // Fall-back to (NAME || ID) + (VERSION || VERSION_ID || VERSION_CODENAME)? |
| 417 | + if (props.TryGetValue("NAME", out string distro) || |
| 418 | + props.TryGetValue("ID", out distro)) |
| 419 | + { |
| 420 | + if (props.TryGetValue("VERSION", out string version) || |
| 421 | + props.TryGetValue("VERSION_ID", out version) || |
| 422 | + props.TryGetValue("VERSION_CODENAME", out version)) |
| 423 | + { |
| 424 | + return $"{distro} {version}"; |
| 425 | + } |
| 426 | + |
| 427 | + // Return just the distro name if we don't have a version |
| 428 | + return distro; |
| 429 | + } |
| 430 | + } |
| 431 | + |
| 432 | + // If we couldn't get the distribution information from /etc/os-release |
| 433 | + // (for example if we're running on a non-systemd distribution), then let's |
| 434 | + // use `uname -a` to get at least some information. |
| 435 | + var psi = new ProcessStartInfo |
| 436 | + { |
| 437 | + FileName = "uname", |
| 438 | + Arguments = "-a", |
| 439 | + RedirectStandardOutput = true |
| 440 | + }; |
393 | 441 |
|
394 |
| - if (uname.ExitCode == 0) |
| 442 | + using (var uname = new ChildProcess(trace2, psi)) |
395 | 443 | {
|
396 |
| - return uname.StandardOutput.ReadToEnd().Trim(); |
| 444 | + uname.Start(Trace2ProcessClass.Other); |
| 445 | + uname.Process.WaitForExit(); |
| 446 | + |
| 447 | + if (uname.ExitCode == 0) |
| 448 | + { |
| 449 | + return uname.StandardOutput.ReadToEnd().Trim(); |
| 450 | + } |
397 | 451 | }
|
| 452 | + |
| 453 | + return "Unknown-Linux"; |
398 | 454 | }
|
399 | 455 | }
|
400 | 456 |
|
|
0 commit comments