using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using NuGet.Versioning; using Semmle.Util; using Semmle.Util.Logging; namespace Semmle.Extraction.CSharp.DependencyFetching { /// /// Locates .NET Runtimes. /// internal partial class Runtime { private const string netCoreApp = "Microsoft.NETCore.App"; private const string aspNetCoreApp = "Microsoft.AspNetCore.App"; private readonly IDotNet dotNet; private readonly Lazy> newestRuntimes; private Dictionary NewestRuntimes => newestRuntimes.Value; public Runtime(IDotNet dotNet) { this.dotNet = dotNet; this.newestRuntimes = new(GetNewestRuntimes); } [GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+(-[a-z]+\.\d+\.\d+\.\d+)?)\s\[(.+)\]$")] private static partial Regex RuntimeRegex(); /// /// Parses the output of `dotnet --list-runtimes` to get a map from a runtime to the location of /// the newest version of the runtime. /// It is assume that the format of a listed runtime is something like: /// Microsoft.NETCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App] /// private static Dictionary ParseRuntimes(IList listed) { // Parse listed runtimes. var runtimes = new Dictionary(); var regex = RuntimeRegex(); listed.ForEach(r => { var match = regex.Match(r); if (match.Success && NuGetVersion.TryParse(match.Groups[2].Value, out var version)) { runtimes.AddOrUpdateToLatest(match.Groups[1].Value, new DotNetVersion(match.Groups[4].Value, version)); } }); return runtimes; } /// /// Returns a dictionary mapping runtimes to their newest version. /// internal Dictionary GetNewestRuntimes() { var listed = dotNet.GetListedRuntimes(); return ParseRuntimes(listed); } /// /// Locates .NET Desktop Runtimes. /// This includes Mono and Microsoft.NET. /// private IEnumerable DesktopRuntimes { get { if (Directory.Exists(@"C:\Windows\Microsoft.NET\Framework64")) { return Directory.EnumerateDirectories(@"C:\Windows\Microsoft.NET\Framework64", "v*") .OrderByDescending(Path.GetFileName); } var monoPath = FileUtils.FindProgramOnPath(Win32.IsWindows() ? "mono.exe" : "mono"); string[] monoDirs = monoPath is not null ? [Path.GetFullPath(Path.Combine(monoPath, "..", "lib", "mono")), monoPath] : ["/usr/lib/mono", "/usr/local/mono", "/usr/local/bin/mono", @"C:\Program Files\Mono\lib\mono"]; var monoDir = monoDirs.FirstOrDefault(Directory.Exists); if (monoDir is not null) { return Directory.EnumerateDirectories(monoDir) .Where(d => char.IsDigit(Path.GetFileName(d)[0])) .OrderByDescending(Path.GetFileName); } return []; } } private string? GetVersion(string framework) { if (NewestRuntimes.TryGetValue(framework, out var version)) { var refAssemblies = version.FullPathReferenceAssemblies; return Directory.Exists(refAssemblies) ? refAssemblies : version.FullPath; } return null; } /// /// Gets the Dotnet Core location. /// public string? NetCoreRuntime => GetVersion(netCoreApp); /// /// Gets the .NET Framework location. Either the installation folder on Windows or Mono /// public string? DesktopRuntime => DesktopRuntimes?.FirstOrDefault(); /// /// Gets the executing runtime location, this is the self contained runtime shipped in the CodeQL CLI bundle. /// public string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory(); /// /// Gets the ASP.NET Core location. /// public string? AspNetCoreRuntime => GetVersion(aspNetCoreApp); } }