Skip to content

Commit 79a885a

Browse files
authored
Implement caching in .NET info resolver (Buildalyzer#267)
1 parent 140a2e0 commit 79a885a

File tree

4 files changed

+83
-84
lines changed

4 files changed

+83
-84
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Concurrent;
2+
using Buildalyzer.IO;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Logging.Abstractions;
5+
6+
namespace Buildalyzer.Environment;
7+
8+
internal sealed class DotNetInfoResolver(ILoggerFactory? factory)
9+
{
10+
private static readonly TimeSpan FallbackWaitTime = TimeSpan.FromSeconds(10);
11+
private readonly ILoggerFactory Factory = factory ?? NullLoggerFactory.Instance;
12+
private readonly ILogger Logger = (factory ?? NullLoggerFactory.Instance).CreateLogger<DotNetInfoResolver>();
13+
14+
[Pure]
15+
public DotNetInfo Resolve(IOPath projectPath, IOPath dotNetExePath)
16+
=> Cache.TryGetValue(projectPath, out var info)
17+
? info
18+
: Execute(projectPath, dotNetExePath);
19+
20+
[Pure]
21+
private DotNetInfo Execute(IOPath projectPath, IOPath dotNetExePath)
22+
{
23+
// Ensure that we set the DOTNET_CLI_UI_LANGUAGE environment variable to "en-US" before
24+
// running 'dotnet --info'. Otherwise, we may get localized results
25+
// Also unset some MSBuild variables, see https://github.com/OmniSharp/omnisharp-roslyn/blob/df160f86ce906bc566fe3e04e38a4077bd6023b4/src/OmniSharp.Abstractions/Services/DotNetCliService.cs#L36
26+
var environmentVariables = new Dictionary<string, string?>
27+
{
28+
[EnvironmentVariables.DOTNET_CLI_UI_LANGUAGE] /*.*/ = "en-US",
29+
[EnvironmentVariables.MSBUILD_EXE_PATH] /*.......*/ = null,
30+
[EnvironmentVariables.COREHOST_TRACE] /*.........*/ = "0",
31+
[MsBuildProperties.MSBuildExtensionsPath] /*.....*/ = null,
32+
};
33+
34+
// global.json may change the version, so need to set working directory
35+
using var processRunner = new ProcessRunner(
36+
dotNetExePath.ToString(),
37+
"--info",
38+
projectPath.File().Directory!.FullName,
39+
environmentVariables,
40+
Factory);
41+
42+
processRunner.Start();
43+
processRunner.WaitForExit(GetWaitTime());
44+
45+
var info = DotNetInfo.Parse(processRunner.Output);
46+
Cache[projectPath] = info;
47+
return info;
48+
}
49+
50+
[Pure]
51+
private int GetWaitTime()
52+
{
53+
if (int.TryParse(System.Environment.GetEnvironmentVariable(EnvironmentVariables.DOTNET_INFO_WAIT_TIME), out int waitTime))
54+
{
55+
Logger?.LogInformation("dotnet --info wait time is {WaitTime}ms", waitTime);
56+
return waitTime;
57+
}
58+
else
59+
{
60+
return (int)FallbackWaitTime.TotalMilliseconds;
61+
}
62+
}
63+
64+
private readonly ConcurrentDictionary<IOPath, DotNetInfo> Cache = new();
65+
}

src/Buildalyzer/Environment/DotnetPathResolver.cs

Lines changed: 0 additions & 62 deletions
This file was deleted.

src/Buildalyzer/Environment/EnvironmentFactory.cs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.IO;
4-
using System.Linq;
1+
using System.IO;
52
using System.Runtime.InteropServices;
63
using Buildalyzer.Construction;
74
using Microsoft.Build.Utilities;
@@ -23,19 +20,19 @@ internal EnvironmentFactory(IAnalyzerManager manager, IProjectFile projectFile)
2320
_logger = _manager.LoggerFactory?.CreateLogger<EnvironmentFactory>();
2421
}
2522

26-
public BuildEnvironment GetBuildEnvironment() =>
23+
public BuildEnvironment? GetBuildEnvironment() =>
2724
GetBuildEnvironment(null, null);
2825

29-
public BuildEnvironment GetBuildEnvironment(string targetFramework) =>
26+
public BuildEnvironment? GetBuildEnvironment(string? targetFramework) =>
3027
GetBuildEnvironment(targetFramework, null);
3128

32-
public BuildEnvironment GetBuildEnvironment(EnvironmentOptions options) =>
29+
public BuildEnvironment? GetBuildEnvironment(EnvironmentOptions? options) =>
3330
GetBuildEnvironment(null, options);
3431

35-
public BuildEnvironment GetBuildEnvironment(string targetFramework, EnvironmentOptions options)
32+
public BuildEnvironment? GetBuildEnvironment(string? targetFramework, EnvironmentOptions? options)
3633
{
3734
options ??= new EnvironmentOptions();
38-
BuildEnvironment buildEnvironment;
35+
BuildEnvironment? buildEnvironment;
3936

4037
// Use the .NET Framework if that's the preference
4138
// ...or if this project file uses a target known to require .NET Framework
@@ -57,18 +54,20 @@ public BuildEnvironment GetBuildEnvironment(string targetFramework, EnvironmentO
5754

5855
// Based on code from OmniSharp
5956
// https://github.com/OmniSharp/omnisharp-roslyn/blob/78ccc8b4376c73da282a600ac6fb10fce8620b52/src/OmniSharp.Abstractions/Services/DotNetCliService.cs
60-
private BuildEnvironment CreateCoreEnvironment(EnvironmentOptions options)
57+
private BuildEnvironment? CreateCoreEnvironment(EnvironmentOptions options)
6158
{
6259
// Get paths
63-
DotnetPathResolver pathResolver = new DotnetPathResolver(_manager.LoggerFactory);
64-
string dotnetPath = pathResolver.ResolvePath(_projectFile.Path, options.DotnetExePath);
65-
if (dotnetPath == null)
60+
var resolver = new DotNetInfoResolver(_manager.LoggerFactory);
61+
var info = resolver.Resolve(IO.IOPath.Parse(_projectFile.Path), IO.IOPath.Parse(options.DotnetExePath));
62+
63+
if ((info.BasePath ?? info.Runtimes.Values.FirstOrDefault()) is not { } dotnetPath)
6664
{
65+
_logger?.LogWarning("Could not locate SDK path in `{DotnetPath} --info` results", options.DotnetExePath);
6766
return null;
6867
}
6968

7069
string msBuildExePath = Path.Combine(dotnetPath, "MSBuild.dll");
71-
if (options != null && options.EnvironmentVariables.ContainsKey(EnvironmentVariables.MSBUILD_EXE_PATH))
70+
if (options.EnvironmentVariables.ContainsKey(EnvironmentVariables.MSBUILD_EXE_PATH))
7271
{
7372
msBuildExePath = options.EnvironmentVariables[EnvironmentVariables.MSBUILD_EXE_PATH];
7473
}
@@ -121,7 +120,7 @@ private BuildEnvironment CreateCoreEnvironment(EnvironmentOptions options)
121120
options.WorkingDirectory);
122121
}
123122

124-
private BuildEnvironment CreateFrameworkEnvironment(EnvironmentOptions options)
123+
private BuildEnvironment? CreateFrameworkEnvironment(EnvironmentOptions options)
125124
{
126125
// Clone the options global properties dictionary so we can add to it
127126
Dictionary<string, string> additionalGlobalProperties = new Dictionary<string, string>(options.GlobalProperties);
@@ -135,7 +134,7 @@ private BuildEnvironment CreateFrameworkEnvironment(EnvironmentOptions options)
135134
}
136135

137136
string msBuildExePath;
138-
if (options != null && options.EnvironmentVariables.ContainsKey(EnvironmentVariables.MSBUILD_EXE_PATH))
137+
if (options.EnvironmentVariables.ContainsKey(EnvironmentVariables.MSBUILD_EXE_PATH))
139138
{
140139
msBuildExePath = options.EnvironmentVariables[EnvironmentVariables.MSBUILD_EXE_PATH];
141140
}
@@ -194,7 +193,7 @@ private bool GetFrameworkMsBuildExePath(out string msBuildExePath)
194193
return !string.IsNullOrEmpty(msBuildExePath);
195194
}
196195

197-
private bool OnlyTargetsFramework(string targetFramework)
196+
private bool OnlyTargetsFramework(string? targetFramework)
198197
=> targetFramework == null
199198
? _projectFile.TargetFrameworks.TrueForAll(IsFrameworkTargetFramework)
200199
: IsFrameworkTargetFramework(targetFramework);

src/Buildalyzer/Environment/ProcessRunner.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Diagnostics;
41
using Microsoft.Extensions.Logging;
52

63
namespace Buildalyzer.Environment;
@@ -21,8 +18,8 @@ public ProcessRunner(
2118
string fileName,
2219
string arguments,
2320
string workingDirectory,
24-
Dictionary<string, string> environmentVariables,
25-
ILoggerFactory loggerFactory)
21+
Dictionary<string, string?> environmentVariables,
22+
ILoggerFactory? loggerFactory)
2623
{
2724
_logger = loggerFactory?.CreateLogger<ProcessRunner>();
2825
Process = new Process

0 commit comments

Comments
 (0)