-
Notifications
You must be signed in to change notification settings - Fork 416
/
ProjectLoader.cs
233 lines (193 loc) · 11.2 KB
/
ProjectLoader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using Microsoft.Extensions.Logging;
using OmniSharp.MSBuild.Logging;
using OmniSharp.MSBuild.ProjectFile;
using OmniSharp.Options;
using MSB = Microsoft.Build;
namespace OmniSharp.MSBuild
{
internal class ProjectLoader
{
private readonly ILogger _logger;
private readonly Dictionary<string, string> _globalProperties;
private readonly MSBuildOptions _options;
private readonly SdksPathResolver _sdksPathResolver;
public ProjectLoader(MSBuildOptions options, string solutionDirectory, ImmutableDictionary<string, string> propertyOverrides, ILoggerFactory loggerFactory, SdksPathResolver sdksPathResolver)
{
_logger = loggerFactory.CreateLogger<ProjectLoader>();
_options = options ?? new MSBuildOptions();
_sdksPathResolver = sdksPathResolver ?? throw new ArgumentNullException(nameof(sdksPathResolver));
_globalProperties = CreateGlobalProperties(_options, solutionDirectory, propertyOverrides, _logger);
}
private static Dictionary<string, string> CreateGlobalProperties(
MSBuildOptions options, string solutionDirectory, ImmutableDictionary<string, string> propertyOverrides, ILogger logger)
{
var globalProperties = new Dictionary<string, string>
{
{ PropertyNames.DesignTimeBuild, "true" },
{ PropertyNames.BuildingInsideVisualStudio, "true" },
{ PropertyNames.BuildProjectReferences, "false" },
{ PropertyNames._ResolveReferenceDependencies, "true" },
{ PropertyNames.SolutionDir, solutionDirectory + Path.DirectorySeparatorChar },
// Setting this property will cause any XAML markup compiler tasks to run in the
// current AppDomain, rather than creating a new one. This is important because
// our AppDomain.AssemblyResolve handler for MSBuild will not be connected to
// the XAML markup compiler's AppDomain, causing the task not to be able to find
// MSBuild.
{ PropertyNames.AlwaysCompileMarkupFilesInSeparateDomain, "false" },
// This properties allow the design-time build to handle the Compile target without actually invoking the compiler.
// See https://github.com/dotnet/roslyn/pull/4604 for details.
{ PropertyNames.ProvideCommandLineArgs, "true" },
{ PropertyNames.SkipCompilerExecution, "true" },
// Ensures the SDK doesn't try to generate app hosts for the loading projects
{ PropertyNames.UseAppHost, "false" },
};
globalProperties.AddPropertyOverride(PropertyNames.MSBuildExtensionsPath, options.MSBuildExtensionsPath, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.TargetFrameworkRootPath, options.TargetFrameworkRootPath, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.RoslynTargetsPath, options.RoslynTargetsPath, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.CscToolPath, options.CscToolPath, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.CscToolExe, options.CscToolExe, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.VisualStudioVersion, options.VisualStudioVersion, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.Configuration, options.Configuration, propertyOverrides, logger);
globalProperties.AddPropertyOverride(PropertyNames.Platform, options.Platform, propertyOverrides, logger);
if (propertyOverrides.TryGetValue(PropertyNames.BypassFrameworkInstallChecks, out var value))
{
globalProperties.Add(PropertyNames.BypassFrameworkInstallChecks, value);
}
return globalProperties;
}
public (MSB.Execution.ProjectInstance projectInstance, MSB.Evaluation.Project project, ImmutableArray<MSBuildDiagnostic> diagnostics) BuildProject(
string filePath, IReadOnlyDictionary<string, string> configurationsInSolution)
{
using (_sdksPathResolver.SetSdksPathEnvironmentVariable(filePath))
{
var msbuildLogger = new MSBuildLogger(_logger);
var loggers = new List<MSB.Framework.ILogger>()
{
msbuildLogger
};
var evaluatedProject = EvaluateProjectFileCore(filePath, configurationsInSolution, loggers);
SetTargetFrameworkIfNeeded(evaluatedProject);
var projectInstance = evaluatedProject.CreateProjectInstance();
if (_options.GenerateBinaryLogs)
{
var binlogPath = Path.ChangeExtension(projectInstance.FullPath, ".binlog");
var binaryLogger = new MSB.Logging.BinaryLogger()
{
CollectProjectImports = MSB.Logging.BinaryLogger.ProjectImportsCollectionMode.Embed,
Parameters = binlogPath
};
loggers.Add(binaryLogger);
}
var buildResult = projectInstance.Build(
targets: new string[] { TargetNames.Compile, TargetNames.CoreCompile },
loggers);
var diagnostics = msbuildLogger.GetDiagnostics();
return buildResult
? (projectInstance, evaluatedProject, diagnostics)
: (null, null, diagnostics);
}
}
public MSB.Evaluation.Project EvaluateProjectFile(string filePath)
{
using (_sdksPathResolver.SetSdksPathEnvironmentVariable(filePath))
{
return EvaluateProjectFileCore(filePath);
}
}
private MSB.Evaluation.Project EvaluateProjectFileCore(string filePath, IReadOnlyDictionary<string, string> projectConfigurationsInSolution = null, IList<MSB.Framework.ILogger> loggers = null)
{
var localProperties = new Dictionary<string, string>(_globalProperties);
if (projectConfigurationsInSolution != null
&& localProperties.TryGetValue(PropertyNames.Configuration, out string solutionConfiguration))
{
if (!localProperties.TryGetValue(PropertyNames.Platform, out string solutionPlatform))
{
solutionPlatform = "Any CPU";
}
var solutionSelector = $"{solutionConfiguration}|{solutionPlatform}.ActiveCfg";
_logger.LogDebug($"Found configuration `{solutionSelector}` in solution for '{filePath}'.");
if (projectConfigurationsInSolution.TryGetValue(solutionSelector, out string projectSelector))
{
var splitted = projectSelector.Split('|');
if (splitted.Length == 2)
{
var projectConfiguration = splitted[0];
localProperties[PropertyNames.Configuration] = projectConfiguration;
// NOTE: Solution often defines configuration as `Any CPU` whereas project relies on `AnyCPU`
var projectPlatform = splitted[1].Replace("Any CPU", "AnyCPU");
localProperties[PropertyNames.Platform] = projectPlatform;
_logger.LogDebug($"Using configuration from solution: `{projectConfiguration}|{projectPlatform}`");
}
}
}
// Evaluate the MSBuild project
var projectCollection = new MSB.Evaluation.ProjectCollection(localProperties, loggers, Microsoft.Build.Evaluation.ToolsetDefinitionLocations.Default);
var toolsVersion = _options.ToolsVersion;
if (string.IsNullOrEmpty(toolsVersion) || Version.TryParse(toolsVersion, out _))
{
toolsVersion = projectCollection.DefaultToolsVersion;
}
toolsVersion = GetLegalToolsetVersion(toolsVersion, projectCollection.Toolsets);
var project = projectCollection.LoadProject(filePath, toolsVersion);
SetTargetFrameworkIfNeeded(project);
return project;
}
private static void SetTargetFrameworkIfNeeded(MSB.Evaluation.Project evaluatedProject)
{
var targetFramework = evaluatedProject.GetPropertyValue(PropertyNames.TargetFramework);
var targetFrameworks = PropertyConverter.SplitList(evaluatedProject.GetPropertyValue(PropertyNames.TargetFrameworks), ';');
// If the project supports multiple target frameworks and specific framework isn't
// selected, we must pick one before execution. Otherwise, the ResolveReferences
// target might not be available to us.
if (string.IsNullOrWhiteSpace(targetFramework) && targetFrameworks.Length > 0)
{
// For now, we'll just pick the first target framework. Eventually, we'll need to
// do better and potentially allow OmniSharp hosts to select a target framework.
targetFramework = targetFrameworks[0];
evaluatedProject.SetGlobalProperty(PropertyNames.TargetFramework, targetFramework);
evaluatedProject.ReevaluateIfNecessary();
}
}
private string GetLegalToolsetVersion(string toolsVersion, ICollection<MSB.Evaluation.Toolset> toolsets)
{
// Does the expected tools version exist? If so, use it.
foreach (var toolset in toolsets)
{
if (toolset.ToolsVersion == toolsVersion)
{
return toolsVersion;
}
}
// If not, try to find the highest version available and use that instead.
Version highestVersion = null;
var legalToolsets = new SortedList<Version, MSB.Evaluation.Toolset>(toolsets.Count);
foreach (var toolset in toolsets)
{
// Only consider this toolset if it has a legal version, we haven't seen it, and its path exists.
if (Version.TryParse(toolset.ToolsVersion, out var toolsetVersion) &&
!legalToolsets.ContainsKey(toolsetVersion) &&
Directory.Exists(toolset.ToolsPath))
{
legalToolsets.Add(toolsetVersion, toolset);
if (highestVersion == null ||
toolsetVersion > highestVersion)
{
highestVersion = toolsetVersion;
}
}
}
if (legalToolsets.Count == 0 || highestVersion == null)
{
_logger.LogError($"No legal MSBuild tools available, defaulting to {toolsVersion}.");
return toolsVersion;
}
var result = legalToolsets[highestVersion].ToolsVersion;
_logger.LogInformation($"Using MSBuild tools version: {result}");
return result;
}
}
}