Skip to content

Commit 6a87755

Browse files
committed
C#: Use dotnet --list-runtimes to find runtime locations.
1 parent bb521d7 commit 6a87755

File tree

3 files changed

+132
-37
lines changed

3 files changed

+132
-37
lines changed

csharp/extractor/Semmle.Extraction.CSharp.Standalone/BuildAnalysis.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public BuildAnalysis(Options options, ProgressMonitor progressMonitor)
6666
// Find DLLs in the .Net Framework
6767
if (options.ScanNetFrameworkDlls)
6868
{
69-
var runtimeLocation = Runtime.GetRuntime(options.UseSelfContainedDotnet);
69+
var runtimeLocation = new Runtime(dotnet).GetRuntime(options.UseSelfContainedDotnet);
7070
progressMonitor.Log(Util.Logging.Severity.Debug, $"Runtime location selected: {runtimeLocation}");
7171
dllDirNames.Add(runtimeLocation);
7272
}

csharp/extractor/Semmle.Extraction.CSharp.Standalone/DotNet.cs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using System;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
4+
using Semmle.Util;
35

46
namespace Semmle.BuildAnalyser
57
{
@@ -8,6 +10,7 @@ namespace Semmle.BuildAnalyser
810
/// </summary>
911
internal class DotNet
1012
{
13+
private const string dotnet = "dotnet";
1114
private readonly ProgressMonitor progressMonitor;
1215

1316
public DotNet(ProgressMonitor progressMonitor)
@@ -19,26 +22,26 @@ public DotNet(ProgressMonitor progressMonitor)
1922
private void Info()
2023
{
2124
// TODO: make sure the below `dotnet` version is matching the one specified in global.json
22-
progressMonitor.RunningProcess("dotnet --info");
23-
using var proc = Process.Start("dotnet", "--info");
25+
progressMonitor.RunningProcess($"{dotnet} --info");
26+
using var proc = Process.Start(dotnet, "--info");
2427
proc.WaitForExit();
2528
var ret = proc.ExitCode;
2629

2730
if (ret != 0)
2831
{
29-
progressMonitor.CommandFailed("dotnet", "--info", ret);
30-
throw new Exception($"dotnet --info failed with exit code {ret}.");
32+
progressMonitor.CommandFailed(dotnet, "--info", ret);
33+
throw new Exception($"{dotnet} --info failed with exit code {ret}.");
3134
}
3235
}
3336

3437
private bool RunCommand(string args)
3538
{
36-
progressMonitor.RunningProcess($"dotnet {args}");
37-
using var proc = Process.Start("dotnet", args);
39+
progressMonitor.RunningProcess($"{dotnet} {args}");
40+
using var proc = Process.Start(dotnet, args);
3841
proc.WaitForExit();
3942
if (proc.ExitCode != 0)
4043
{
41-
progressMonitor.CommandFailed("dotnet", args, proc.ExitCode);
44+
progressMonitor.CommandFailed(dotnet, args, proc.ExitCode);
4245
return false;
4346
}
4447

@@ -62,5 +65,22 @@ public bool AddPackage(string folder, string package)
6265
var args = $"add \"{folder}\" package \"{package}\" --no-restore";
6366
return RunCommand(args);
6467
}
68+
69+
public IList<string> GetListedRuntimes()
70+
{
71+
var args = "--list-runtimes";
72+
var pi = new ProcessStartInfo(dotnet, args)
73+
{
74+
RedirectStandardOutput = true,
75+
UseShellExecute = false
76+
};
77+
var exitCode = pi.ReadOutput(out var runtimes);
78+
if (exitCode != 0)
79+
{
80+
progressMonitor.CommandFailed(dotnet, args, exitCode);
81+
return new List<string>();
82+
}
83+
return runtimes;
84+
}
6585
}
6686
}

csharp/extractor/Semmle.Extraction.CSharp.Standalone/Runtime.cs

Lines changed: 103 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,106 @@
33
using System.Runtime.InteropServices;
44
using System.IO;
55
using System.Linq;
6+
using System.Text.RegularExpressions;
7+
using Semmle.BuildAnalyser;
68
using Semmle.Util;
79

810
namespace Semmle.Extraction.CSharp.Standalone
911
{
1012
/// <summary>
1113
/// Locates .NET Runtimes.
1214
/// </summary>
13-
internal static class Runtime
15+
internal partial class Runtime
1416
{
17+
private readonly DotNet dotNet;
18+
public Runtime(DotNet dotNet) => this.dotNet = dotNet;
19+
20+
private sealed class Version : IComparable<Version>
21+
{
22+
private readonly string Dir;
23+
public int Major { get; }
24+
public int Minor { get; }
25+
public int Patch { get; }
26+
27+
28+
public string FullPath => Path.Combine(Dir, this.ToString());
29+
30+
31+
public Version(string version, string dir)
32+
{
33+
var parts = version.Split('.');
34+
Major = int.Parse(parts[0]);
35+
Minor = int.Parse(parts[1]);
36+
Patch = int.Parse(parts[2]);
37+
Dir = dir;
38+
}
39+
40+
public int CompareTo(Version? other) =>
41+
other is null ? 1 : GetHashCode().CompareTo(other.GetHashCode());
42+
43+
public override bool Equals(object? obj) =>
44+
obj is not null && obj is Version other && other.FullPath == FullPath;
45+
46+
public override int GetHashCode() =>
47+
(Major * 1000 + Minor) * 1000 + Patch;
48+
49+
public override string ToString() =>
50+
$"{Major}.{Minor}.{Patch}";
51+
}
52+
1553
private static string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory();
1654

55+
private static readonly string NetCoreApp = "Microsoft.NETCore.App";
56+
private static readonly string AspNetCoreApp = "Microsoft.AspNetCore.App";
57+
58+
private static void AddOrUpdate(Dictionary<string, Version> dict, string framework, Version version)
59+
{
60+
if (!dict.TryGetValue(framework, out var existing) || existing.CompareTo(version) < 0)
61+
{
62+
dict[framework] = version;
63+
}
64+
}
65+
66+
[GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+)\s\[(\S+)\]$")]
67+
private static partial Regex RuntimeRegex();
68+
1769
/// <summary>
18-
/// Locates .NET Core Runtimes.
70+
/// Parses the output of `dotnet --list-runtimes` to get a map from a runtime to the location of
71+
/// the newest version of the runtime.
72+
/// It is assume that the format of a listed runtime is something like:
73+
/// Microsoft.NETCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
1974
/// </summary>
20-
private static IEnumerable<string> CoreRuntimes
75+
private static Dictionary<string, Version> ParseRuntimes(IList<string> listed)
2176
{
22-
get
77+
// Parse listed runtimes.
78+
var runtimes = new Dictionary<string, Version>();
79+
listed.ForEach(r =>
2380
{
24-
var dotnetPath = FileUtils.FindProgramOnPath(Win32.IsWindows() ? "dotnet.exe" : "dotnet");
25-
var dotnetDirs = dotnetPath is not null
26-
? new[] { dotnetPath }
27-
: new[] { "/usr/share/dotnet", @"C:\Program Files\dotnet" };
28-
var coreDirs = dotnetDirs.Select(d => Path.Combine(d, "shared", "Microsoft.NETCore.App"));
29-
30-
var dir = coreDirs.FirstOrDefault(Directory.Exists);
31-
if (dir is not null)
81+
var match = RuntimeRegex().Match(r);
82+
if (match.Success)
3283
{
33-
return Directory.EnumerateDirectories(dir).OrderByDescending(Path.GetFileName);
84+
AddOrUpdate(runtimes, match.Groups[1].Value, new Version(match.Groups[2].Value, match.Groups[3].Value));
3485
}
86+
});
3587

36-
return Enumerable.Empty<string>();
88+
return runtimes;
89+
}
90+
91+
private Dictionary<string, Version> GetNewestRuntimes()
92+
{
93+
try
94+
{
95+
var listed = dotNet.GetListedRuntimes();
96+
return ParseRuntimes(listed);
97+
}
98+
catch (Exception ex)
99+
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
100+
{
101+
return new Dictionary<string, Version>();
37102
}
38103
}
39104

105+
40106
/// <summary>
41107
/// Locates .NET Desktop Runtimes.
42108
/// This includes Mono and Microsoft.NET.
@@ -69,24 +135,33 @@ private static IEnumerable<string> DesktopRuntimes
69135
}
70136
}
71137

72-
/// <summary>
73-
/// Gets the .NET runtime location to use for extraction
74-
/// </summary>
75-
public static string GetRuntime(bool useSelfContained) => useSelfContained ? ExecutingRuntime : Runtimes.First();
76-
77-
private static IEnumerable<string> Runtimes
138+
private IEnumerable<string> GetRuntimes()
78139
{
79-
get
80-
{
81-
foreach (var r in CoreRuntimes)
82-
yield return r;
140+
// Gets the newest version of the installed runtimes.
141+
var newestRuntimes = GetNewestRuntimes();
83142

84-
foreach (var r in DesktopRuntimes)
85-
yield return r;
143+
// Location of the newest .NET Core Runtime.
144+
if (newestRuntimes.TryGetValue(NetCoreApp, out var netCoreApp))
145+
{
146+
yield return netCoreApp.FullPath;
147+
}
86148

87-
// A bad choice if it's the self-contained runtime distributed in codeql dist.
88-
yield return ExecutingRuntime;
149+
// Location of the newest ASP.NET Core Runtime.
150+
if (newestRuntimes.TryGetValue(AspNetCoreApp, out var aspNetCoreApp))
151+
{
152+
yield return aspNetCoreApp.FullPath;
89153
}
154+
155+
foreach (var r in DesktopRuntimes)
156+
yield return r;
157+
158+
// A bad choice if it's the self-contained runtime distributed in codeql dist.
159+
yield return ExecutingRuntime;
90160
}
161+
162+
/// <summary>
163+
/// Gets the .NET runtime location to use for extraction
164+
/// </summary>
165+
public string GetRuntime(bool useSelfContained) => useSelfContained ? ExecutingRuntime : GetRuntimes().First();
91166
}
92167
}

0 commit comments

Comments
 (0)