convert spaces to tabs
This commit is contained in:
@@ -13,408 +13,408 @@ namespace SharpIDE.Application.Features.Analysis.ProjectLoader;
|
|||||||
|
|
||||||
public partial class CustomMsBuildProjectLoader
|
public partial class CustomMsBuildProjectLoader
|
||||||
{
|
{
|
||||||
private sealed partial class CustomWorker
|
private sealed partial class CustomWorker
|
||||||
{
|
{
|
||||||
private readonly SolutionServices _solutionServices;
|
private readonly SolutionServices _solutionServices;
|
||||||
private readonly DiagnosticReporter _diagnosticReporter;
|
private readonly DiagnosticReporter _diagnosticReporter;
|
||||||
private readonly PathResolver _pathResolver;
|
private readonly PathResolver _pathResolver;
|
||||||
private readonly ProjectFileExtensionRegistry _projectFileExtensionRegistry;
|
private readonly ProjectFileExtensionRegistry _projectFileExtensionRegistry;
|
||||||
private readonly BuildHostProcessManager _buildHostProcessManager;
|
private readonly BuildHostProcessManager _buildHostProcessManager;
|
||||||
private readonly string _baseDirectory;
|
private readonly string _baseDirectory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An ordered list of paths to project files that should be loaded. In the case of a solution,
|
/// An ordered list of paths to project files that should be loaded. In the case of a solution,
|
||||||
/// this is the list of project file paths in the solution.
|
/// this is the list of project file paths in the solution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ImmutableArray<string> _requestedProjectPaths;
|
private readonly ImmutableArray<string> _requestedProjectPaths;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Map of <see cref="ProjectId"/>s, project paths, and output file paths.
|
/// Map of <see cref="ProjectId"/>s, project paths, and output file paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ProjectMap _projectMap;
|
private readonly ProjectMap _projectMap;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Progress reporter.
|
/// Progress reporter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IProgress<ProjectLoadProgress>? _progress;
|
private readonly IProgress<ProjectLoadProgress>? _progress;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides options for how failures should be reported when loading requested project files.
|
/// Provides options for how failures should be reported when loading requested project files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly DiagnosticReportingOptions _requestedProjectOptions;
|
private readonly DiagnosticReportingOptions _requestedProjectOptions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides options for how failures should be reported when loading any discovered project files.
|
/// Provides options for how failures should be reported when loading any discovered project files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly DiagnosticReportingOptions _discoveredProjectOptions;
|
private readonly DiagnosticReportingOptions _discoveredProjectOptions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When true, metadata is preferred for any project reference unless the referenced project is already loaded
|
/// When true, metadata is preferred for any project reference unless the referenced project is already loaded
|
||||||
/// because it was requested.
|
/// because it was requested.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly bool _preferMetadataForReferencesOfDiscoveredProjects;
|
private readonly bool _preferMetadataForReferencesOfDiscoveredProjects;
|
||||||
private readonly Dictionary<ProjectId, ProjectFileInfo> _projectIdToFileInfoMap;
|
private readonly Dictionary<ProjectId, ProjectFileInfo> _projectIdToFileInfoMap;
|
||||||
private readonly Dictionary<ProjectId, List<ProjectReference>> _projectIdToProjectReferencesMap;
|
private readonly Dictionary<ProjectId, List<ProjectReference>> _projectIdToProjectReferencesMap;
|
||||||
private readonly Dictionary<string, ImmutableArray<ProjectInfo>> _pathToDiscoveredProjectInfosMap;
|
private readonly Dictionary<string, ImmutableArray<ProjectInfo>> _pathToDiscoveredProjectInfosMap;
|
||||||
|
|
||||||
public CustomWorker(
|
public CustomWorker(
|
||||||
SolutionServices services,
|
SolutionServices services,
|
||||||
DiagnosticReporter diagnosticReporter,
|
DiagnosticReporter diagnosticReporter,
|
||||||
PathResolver pathResolver,
|
PathResolver pathResolver,
|
||||||
ProjectFileExtensionRegistry projectFileExtensionRegistry,
|
ProjectFileExtensionRegistry projectFileExtensionRegistry,
|
||||||
BuildHostProcessManager buildHostProcessManager,
|
BuildHostProcessManager buildHostProcessManager,
|
||||||
ImmutableArray<string> requestedProjectPaths,
|
ImmutableArray<string> requestedProjectPaths,
|
||||||
string baseDirectory,
|
string baseDirectory,
|
||||||
ProjectMap? projectMap,
|
ProjectMap? projectMap,
|
||||||
IProgress<ProjectLoadProgress>? progress,
|
IProgress<ProjectLoadProgress>? progress,
|
||||||
DiagnosticReportingOptions requestedProjectOptions,
|
DiagnosticReportingOptions requestedProjectOptions,
|
||||||
DiagnosticReportingOptions discoveredProjectOptions,
|
DiagnosticReportingOptions discoveredProjectOptions,
|
||||||
bool preferMetadataForReferencesOfDiscoveredProjects)
|
bool preferMetadataForReferencesOfDiscoveredProjects)
|
||||||
{
|
{
|
||||||
_solutionServices = services;
|
_solutionServices = services;
|
||||||
_diagnosticReporter = diagnosticReporter;
|
_diagnosticReporter = diagnosticReporter;
|
||||||
_pathResolver = pathResolver;
|
_pathResolver = pathResolver;
|
||||||
_projectFileExtensionRegistry = projectFileExtensionRegistry;
|
_projectFileExtensionRegistry = projectFileExtensionRegistry;
|
||||||
_buildHostProcessManager = buildHostProcessManager;
|
_buildHostProcessManager = buildHostProcessManager;
|
||||||
_baseDirectory = baseDirectory;
|
_baseDirectory = baseDirectory;
|
||||||
_requestedProjectPaths = requestedProjectPaths;
|
_requestedProjectPaths = requestedProjectPaths;
|
||||||
_projectMap = projectMap ?? ProjectMap.Create();
|
_projectMap = projectMap ?? ProjectMap.Create();
|
||||||
_progress = progress;
|
_progress = progress;
|
||||||
_requestedProjectOptions = requestedProjectOptions;
|
_requestedProjectOptions = requestedProjectOptions;
|
||||||
_discoveredProjectOptions = discoveredProjectOptions;
|
_discoveredProjectOptions = discoveredProjectOptions;
|
||||||
_preferMetadataForReferencesOfDiscoveredProjects = preferMetadataForReferencesOfDiscoveredProjects;
|
_preferMetadataForReferencesOfDiscoveredProjects = preferMetadataForReferencesOfDiscoveredProjects;
|
||||||
_projectIdToFileInfoMap = [];
|
_projectIdToFileInfoMap = [];
|
||||||
_pathToDiscoveredProjectInfosMap = new Dictionary<string, ImmutableArray<ProjectInfo>>(PathUtilities.Comparer);
|
_pathToDiscoveredProjectInfosMap = new Dictionary<string, ImmutableArray<ProjectInfo>>(PathUtilities.Comparer);
|
||||||
_projectIdToProjectReferencesMap = [];
|
_projectIdToProjectReferencesMap = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TResult> DoOperationAndReportProgressAsync<TResult>(ProjectLoadOperation operation, string? projectPath, string? targetFramework, Func<Task<TResult>> doFunc)
|
private async Task<TResult> DoOperationAndReportProgressAsync<TResult>(ProjectLoadOperation operation, string? projectPath, string? targetFramework, Func<Task<TResult>> doFunc)
|
||||||
{
|
{
|
||||||
var watch = _progress != null
|
var watch = _progress != null
|
||||||
? Stopwatch.StartNew()
|
? Stopwatch.StartNew()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
TResult result;
|
TResult result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = await doFunc().ConfigureAwait(false);
|
result = await doFunc().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (_progress != null && watch != null)
|
if (_progress != null && watch != null)
|
||||||
{
|
{
|
||||||
watch.Stop();
|
watch.Stop();
|
||||||
_progress.Report(new ProjectLoadProgress(projectPath ?? string.Empty, operation, targetFramework, watch.Elapsed));
|
_progress.Report(new ProjectLoadProgress(projectPath ?? string.Empty, operation, targetFramework, watch.Elapsed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(ImmutableArray<ProjectInfo>, Dictionary<ProjectId, ProjectFileInfo>)> LoadAsync(CancellationToken cancellationToken)
|
public async Task<(ImmutableArray<ProjectInfo>, Dictionary<ProjectId, ProjectFileInfo>)> LoadAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var results = ImmutableArray.CreateBuilder<ProjectInfo>();
|
var results = ImmutableArray.CreateBuilder<ProjectInfo>();
|
||||||
var processedPaths = new HashSet<string>(PathUtilities.Comparer);
|
var processedPaths = new HashSet<string>(PathUtilities.Comparer);
|
||||||
|
|
||||||
foreach (var projectPath in _requestedProjectPaths)
|
foreach (var projectPath in _requestedProjectPaths)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (!_pathResolver.TryGetAbsoluteProjectPath(projectPath, _baseDirectory, _requestedProjectOptions.OnPathFailure, out var absoluteProjectPath))
|
if (!_pathResolver.TryGetAbsoluteProjectPath(projectPath, _baseDirectory, _requestedProjectOptions.OnPathFailure, out var absoluteProjectPath))
|
||||||
{
|
{
|
||||||
continue; // Failure should already be reported.
|
continue; // Failure should already be reported.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!processedPaths.Add(absoluteProjectPath))
|
if (!processedPaths.Add(absoluteProjectPath))
|
||||||
{
|
{
|
||||||
_diagnosticReporter.Report(
|
_diagnosticReporter.Report(
|
||||||
new WorkspaceDiagnostic(
|
new WorkspaceDiagnostic(
|
||||||
WorkspaceDiagnosticKind.Warning,
|
WorkspaceDiagnosticKind.Warning,
|
||||||
string.Format(WorkspaceMSBuildResources.Duplicate_project_discovered_and_skipped_0, absoluteProjectPath)));
|
string.Format(WorkspaceMSBuildResources.Duplicate_project_discovered_and_skipped_0, absoluteProjectPath)));
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectFileInfos = await LoadProjectInfosFromPathAsync(absoluteProjectPath, _requestedProjectOptions, cancellationToken).ConfigureAwait(false);
|
var projectFileInfos = await LoadProjectInfosFromPathAsync(absoluteProjectPath, _requestedProjectOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
results.AddRange(projectFileInfos);
|
results.AddRange(projectFileInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (projectPath, projectInfos) in _pathToDiscoveredProjectInfosMap)
|
foreach (var (projectPath, projectInfos) in _pathToDiscoveredProjectInfosMap)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (!processedPaths.Contains(projectPath))
|
if (!processedPaths.Contains(projectPath))
|
||||||
{
|
{
|
||||||
results.AddRange(projectInfos);
|
results.AddRange(projectInfos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (results.ToImmutableAndClear(), _projectIdToFileInfoMap);
|
return (results.ToImmutableAndClear(), _projectIdToFileInfoMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ImmutableArray<ProjectFileInfo>> LoadProjectFileInfosAsync(string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken)
|
private async Task<ImmutableArray<ProjectFileInfo>> LoadProjectFileInfosAsync(string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!_projectFileExtensionRegistry.TryGetLanguageNameFromProjectPath(projectPath, reportingOptions.OnLoaderFailure, out var languageName))
|
if (!_projectFileExtensionRegistry.TryGetLanguageNameFromProjectPath(projectPath, reportingOptions.OnLoaderFailure, out var languageName))
|
||||||
{
|
{
|
||||||
return []; // Failure should already be reported.
|
return []; // Failure should already be reported.
|
||||||
}
|
}
|
||||||
|
|
||||||
var preferredBuildHostKind = BuildHostProcessManager.GetKindForProject(projectPath);
|
var preferredBuildHostKind = BuildHostProcessManager.GetKindForProject(projectPath);
|
||||||
var (buildHost, actualBuildHostKind) = await _buildHostProcessManager.GetBuildHostWithFallbackAsync(preferredBuildHostKind, projectPath, cancellationToken).ConfigureAwait(false);
|
var (buildHost, actualBuildHostKind) = await _buildHostProcessManager.GetBuildHostWithFallbackAsync(preferredBuildHostKind, projectPath, cancellationToken).ConfigureAwait(false);
|
||||||
var projectFile = await DoOperationAndReportProgressAsync(
|
var projectFile = await DoOperationAndReportProgressAsync(
|
||||||
ProjectLoadOperation.Evaluate,
|
ProjectLoadOperation.Evaluate,
|
||||||
projectPath,
|
projectPath,
|
||||||
targetFramework: null,
|
targetFramework: null,
|
||||||
() => buildHost.LoadProjectFileAsync(projectPath, languageName, cancellationToken)
|
() => buildHost.LoadProjectFileAsync(projectPath, languageName, cancellationToken)
|
||||||
).ConfigureAwait(false);
|
).ConfigureAwait(false);
|
||||||
|
|
||||||
// If there were any failures during load, we won't be able to build the project. So, bail early with an empty project.
|
// If there were any failures during load, we won't be able to build the project. So, bail early with an empty project.
|
||||||
var diagnosticItems = await projectFile.GetDiagnosticLogItemsAsync(cancellationToken).ConfigureAwait(false);
|
var diagnosticItems = await projectFile.GetDiagnosticLogItemsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (diagnosticItems.Any(d => d.Kind == DiagnosticLogItemKind.Error))
|
if (diagnosticItems.Any(d => d.Kind == DiagnosticLogItemKind.Error))
|
||||||
{
|
{
|
||||||
_diagnosticReporter.Report(diagnosticItems);
|
_diagnosticReporter.Report(diagnosticItems);
|
||||||
|
|
||||||
return [ProjectFileInfo.CreateEmpty(languageName, projectPath)];
|
return [ProjectFileInfo.CreateEmpty(languageName, projectPath)];
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectFileInfos = await DoOperationAndReportProgressAsync(
|
var projectFileInfos = await DoOperationAndReportProgressAsync(
|
||||||
ProjectLoadOperation.Build,
|
ProjectLoadOperation.Build,
|
||||||
projectPath,
|
projectPath,
|
||||||
targetFramework: null,
|
targetFramework: null,
|
||||||
() => projectFile.GetProjectFileInfosAsync(cancellationToken)
|
() => projectFile.GetProjectFileInfosAsync(cancellationToken)
|
||||||
).ConfigureAwait(false);
|
).ConfigureAwait(false);
|
||||||
|
|
||||||
var results = ImmutableArray.CreateBuilder<ProjectFileInfo>(projectFileInfos.Length);
|
var results = ImmutableArray.CreateBuilder<ProjectFileInfo>(projectFileInfos.Length);
|
||||||
|
|
||||||
foreach (var projectFileInfo in projectFileInfos)
|
foreach (var projectFileInfo in projectFileInfos)
|
||||||
{
|
{
|
||||||
// Note: any diagnostics would have been logged to the original project file's log.
|
// Note: any diagnostics would have been logged to the original project file's log.
|
||||||
|
|
||||||
results.Add(projectFileInfo);
|
results.Add(projectFileInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll go check for any further diagnostics and report them
|
// We'll go check for any further diagnostics and report them
|
||||||
diagnosticItems = await projectFile.GetDiagnosticLogItemsAsync(cancellationToken).ConfigureAwait(false);
|
diagnosticItems = await projectFile.GetDiagnosticLogItemsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
_diagnosticReporter.Report(diagnosticItems);
|
_diagnosticReporter.Report(diagnosticItems);
|
||||||
|
|
||||||
return results.MoveToImmutable();
|
return results.MoveToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ImmutableArray<ProjectInfo>> LoadProjectInfosFromPathAsync(
|
private async Task<ImmutableArray<ProjectInfo>> LoadProjectInfosFromPathAsync(
|
||||||
string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken)
|
string projectPath, DiagnosticReportingOptions reportingOptions, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_projectMap.TryGetProjectInfosByProjectPath(projectPath, out var results) ||
|
if (_projectMap.TryGetProjectInfosByProjectPath(projectPath, out var results) ||
|
||||||
_pathToDiscoveredProjectInfosMap.TryGetValue(projectPath, out results))
|
_pathToDiscoveredProjectInfosMap.TryGetValue(projectPath, out results))
|
||||||
{
|
{
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = ImmutableArray.CreateBuilder<ProjectInfo>();
|
var builder = ImmutableArray.CreateBuilder<ProjectInfo>();
|
||||||
|
|
||||||
var projectFileInfos = await LoadProjectFileInfosAsync(projectPath, reportingOptions, cancellationToken).ConfigureAwait(false);
|
var projectFileInfos = await LoadProjectFileInfosAsync(projectPath, reportingOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var idsAndFileInfos = new List<(ProjectId id, ProjectFileInfo fileInfo)>();
|
var idsAndFileInfos = new List<(ProjectId id, ProjectFileInfo fileInfo)>();
|
||||||
|
|
||||||
foreach (var projectFileInfo in projectFileInfos)
|
foreach (var projectFileInfo in projectFileInfos)
|
||||||
{
|
{
|
||||||
var projectId = _projectMap.GetOrCreateProjectId(projectFileInfo);
|
var projectId = _projectMap.GetOrCreateProjectId(projectFileInfo);
|
||||||
|
|
||||||
if (_projectIdToFileInfoMap.ContainsKey(projectId))
|
if (_projectIdToFileInfoMap.ContainsKey(projectId))
|
||||||
{
|
{
|
||||||
// There are multiple projects with the same project path and output path. This can happen
|
// There are multiple projects with the same project path and output path. This can happen
|
||||||
// if a multi-TFM project does not have unique output file paths for each TFM. In that case,
|
// if a multi-TFM project does not have unique output file paths for each TFM. In that case,
|
||||||
// we'll create a new ProjectId to ensure that the project is added to the workspace.
|
// we'll create a new ProjectId to ensure that the project is added to the workspace.
|
||||||
|
|
||||||
_diagnosticReporter.Report(
|
_diagnosticReporter.Report(
|
||||||
DiagnosticReportingMode.Log,
|
DiagnosticReportingMode.Log,
|
||||||
string.Format(WorkspaceMSBuildResources.Found_project_with_the_same_file_path_and_output_path_as_another_project_0, projectFileInfo.FilePath));
|
string.Format(WorkspaceMSBuildResources.Found_project_with_the_same_file_path_and_output_path_as_another_project_0, projectFileInfo.FilePath));
|
||||||
|
|
||||||
projectId = ProjectId.CreateNewId(debugName: projectFileInfo.FilePath);
|
projectId = ProjectId.CreateNewId(debugName: projectFileInfo.FilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
idsAndFileInfos.Add((projectId, projectFileInfo));
|
idsAndFileInfos.Add((projectId, projectFileInfo));
|
||||||
_projectIdToFileInfoMap.Add(projectId, projectFileInfo);
|
_projectIdToFileInfoMap.Add(projectId, projectFileInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this project resulted in more than a single project, a discriminator (e.g. TFM) should be
|
// If this project resulted in more than a single project, a discriminator (e.g. TFM) should be
|
||||||
// added to the project name.
|
// added to the project name.
|
||||||
var addDiscriminator = idsAndFileInfos.Count > 1;
|
var addDiscriminator = idsAndFileInfos.Count > 1;
|
||||||
|
|
||||||
foreach (var (id, fileInfo) in idsAndFileInfos)
|
foreach (var (id, fileInfo) in idsAndFileInfos)
|
||||||
{
|
{
|
||||||
var projectInfo = await CreateProjectInfoAsync(fileInfo, id, addDiscriminator, cancellationToken).ConfigureAwait(false);
|
var projectInfo = await CreateProjectInfoAsync(fileInfo, id, addDiscriminator, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
builder.Add(projectInfo);
|
builder.Add(projectInfo);
|
||||||
_projectMap.AddProjectInfo(projectInfo);
|
_projectMap.AddProjectInfo(projectInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
results = builder.ToImmutable();
|
results = builder.ToImmutable();
|
||||||
|
|
||||||
_pathToDiscoveredProjectInfosMap.Add(projectPath, results);
|
_pathToDiscoveredProjectInfosMap.Add(projectPath, results);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<ProjectInfo> CreateProjectInfoAsync(ProjectFileInfo projectFileInfo, ProjectId projectId, bool addDiscriminator, CancellationToken cancellationToken)
|
private Task<ProjectInfo> CreateProjectInfoAsync(ProjectFileInfo projectFileInfo, ProjectId projectId, bool addDiscriminator, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var language = projectFileInfo.Language;
|
var language = projectFileInfo.Language;
|
||||||
var projectPath = projectFileInfo.FilePath;
|
var projectPath = projectFileInfo.FilePath;
|
||||||
var projectName = Path.GetFileNameWithoutExtension(projectPath) ?? string.Empty;
|
var projectName = Path.GetFileNameWithoutExtension(projectPath) ?? string.Empty;
|
||||||
if (addDiscriminator && !RoslynString.IsNullOrWhiteSpace(projectFileInfo.TargetFramework))
|
if (addDiscriminator && !RoslynString.IsNullOrWhiteSpace(projectFileInfo.TargetFramework))
|
||||||
{
|
{
|
||||||
projectName += "(" + projectFileInfo.TargetFramework + ")";
|
projectName += "(" + projectFileInfo.TargetFramework + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = projectPath is null
|
var version = projectPath is null
|
||||||
? VersionStamp.Default
|
? VersionStamp.Default
|
||||||
: VersionStamp.Create(FileUtilities.GetFileTimeStamp(projectPath));
|
: VersionStamp.Create(FileUtilities.GetFileTimeStamp(projectPath));
|
||||||
|
|
||||||
if (projectFileInfo.IsEmpty)
|
if (projectFileInfo.IsEmpty)
|
||||||
{
|
{
|
||||||
var assemblyName = GetAssemblyNameFromProjectPath(projectPath);
|
var assemblyName = GetAssemblyNameFromProjectPath(projectPath);
|
||||||
|
|
||||||
var parseOptions = GetLanguageService<ISyntaxTreeFactoryService>(language)
|
var parseOptions = GetLanguageService<ISyntaxTreeFactoryService>(language)
|
||||||
?.GetDefaultParseOptions();
|
?.GetDefaultParseOptions();
|
||||||
var compilationOptions = GetLanguageService<ICompilationFactoryService>(language)
|
var compilationOptions = GetLanguageService<ICompilationFactoryService>(language)
|
||||||
?.GetDefaultCompilationOptions();
|
?.GetDefaultCompilationOptions();
|
||||||
|
|
||||||
return Task.FromResult(
|
return Task.FromResult(
|
||||||
ProjectInfo.Create(
|
ProjectInfo.Create(
|
||||||
new ProjectInfo.ProjectAttributes(
|
new ProjectInfo.ProjectAttributes(
|
||||||
projectId,
|
projectId,
|
||||||
version,
|
version,
|
||||||
name: projectName,
|
name: projectName,
|
||||||
assemblyName: assemblyName,
|
assemblyName: assemblyName,
|
||||||
language: language,
|
language: language,
|
||||||
compilationOutputInfo: new CompilationOutputInfo(projectFileInfo.IntermediateOutputFilePath, projectFileInfo.GeneratedFilesOutputDirectory),
|
compilationOutputInfo: new CompilationOutputInfo(projectFileInfo.IntermediateOutputFilePath, projectFileInfo.GeneratedFilesOutputDirectory),
|
||||||
checksumAlgorithm: SourceHashAlgorithms.Default,
|
checksumAlgorithm: SourceHashAlgorithms.Default,
|
||||||
outputFilePath: projectFileInfo.OutputFilePath,
|
outputFilePath: projectFileInfo.OutputFilePath,
|
||||||
outputRefFilePath: projectFileInfo.OutputRefFilePath,
|
outputRefFilePath: projectFileInfo.OutputRefFilePath,
|
||||||
filePath: projectPath),
|
filePath: projectPath),
|
||||||
compilationOptions: compilationOptions,
|
compilationOptions: compilationOptions,
|
||||||
parseOptions: parseOptions));
|
parseOptions: parseOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
return DoOperationAndReportProgressAsync(ProjectLoadOperation.Resolve, projectPath, projectFileInfo.TargetFramework, async () =>
|
return DoOperationAndReportProgressAsync(ProjectLoadOperation.Resolve, projectPath, projectFileInfo.TargetFramework, async () =>
|
||||||
{
|
{
|
||||||
var projectDirectory = Path.GetDirectoryName(projectPath);
|
var projectDirectory = Path.GetDirectoryName(projectPath);
|
||||||
|
|
||||||
// parse command line arguments
|
// parse command line arguments
|
||||||
var commandLineParser = GetLanguageService<ICommandLineParserService>(projectFileInfo.Language);
|
var commandLineParser = GetLanguageService<ICommandLineParserService>(projectFileInfo.Language);
|
||||||
|
|
||||||
if (commandLineParser is null)
|
if (commandLineParser is null)
|
||||||
{
|
{
|
||||||
var message = string.Format(WorkspaceMSBuildResources.Unable_to_find_a_0_for_1, nameof(ICommandLineParserService), projectFileInfo.Language);
|
var message = string.Format(WorkspaceMSBuildResources.Unable_to_find_a_0_for_1, nameof(ICommandLineParserService), projectFileInfo.Language);
|
||||||
throw new Exception(message);
|
throw new Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandLineArgs = commandLineParser.Parse(
|
var commandLineArgs = commandLineParser.Parse(
|
||||||
arguments: projectFileInfo.CommandLineArgs,
|
arguments: projectFileInfo.CommandLineArgs,
|
||||||
baseDirectory: projectDirectory,
|
baseDirectory: projectDirectory,
|
||||||
isInteractive: false,
|
isInteractive: false,
|
||||||
sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory());
|
sdkDirectory: RuntimeEnvironment.GetRuntimeDirectory());
|
||||||
|
|
||||||
var assemblyName = commandLineArgs.CompilationName;
|
var assemblyName = commandLineArgs.CompilationName;
|
||||||
if (RoslynString.IsNullOrWhiteSpace(assemblyName))
|
if (RoslynString.IsNullOrWhiteSpace(assemblyName))
|
||||||
{
|
{
|
||||||
// if there isn't an assembly name, make one from the file path.
|
// if there isn't an assembly name, make one from the file path.
|
||||||
// Note: This may not be necessary any longer if the command line args
|
// Note: This may not be necessary any longer if the command line args
|
||||||
// always produce a valid compilation name.
|
// always produce a valid compilation name.
|
||||||
assemblyName = GetAssemblyNameFromProjectPath(projectPath);
|
assemblyName = GetAssemblyNameFromProjectPath(projectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure sure that doc-comments are parsed
|
// Ensure sure that doc-comments are parsed
|
||||||
var parseOptions = commandLineArgs.ParseOptions;
|
var parseOptions = commandLineArgs.ParseOptions;
|
||||||
if (parseOptions.DocumentationMode == DocumentationMode.None)
|
if (parseOptions.DocumentationMode == DocumentationMode.None)
|
||||||
{
|
{
|
||||||
parseOptions = parseOptions.WithDocumentationMode(DocumentationMode.Parse);
|
parseOptions = parseOptions.WithDocumentationMode(DocumentationMode.Parse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add all the extra options that are really behavior overrides
|
// add all the extra options that are really behavior overrides
|
||||||
var metadataService = GetWorkspaceService<IMetadataService>();
|
var metadataService = GetWorkspaceService<IMetadataService>();
|
||||||
var compilationOptions = commandLineArgs.CompilationOptions
|
var compilationOptions = commandLineArgs.CompilationOptions
|
||||||
.WithXmlReferenceResolver(new XmlFileResolver(projectDirectory))
|
.WithXmlReferenceResolver(new XmlFileResolver(projectDirectory))
|
||||||
.WithSourceReferenceResolver(new SourceFileResolver([], projectDirectory))
|
.WithSourceReferenceResolver(new SourceFileResolver([], projectDirectory))
|
||||||
// TODO: https://github.com/dotnet/roslyn/issues/4967
|
// TODO: https://github.com/dotnet/roslyn/issues/4967
|
||||||
.WithMetadataReferenceResolver(new WorkspaceMetadataFileReferenceResolver(metadataService, new RelativePathResolver([], projectDirectory)))
|
.WithMetadataReferenceResolver(new WorkspaceMetadataFileReferenceResolver(metadataService, new RelativePathResolver([], projectDirectory)))
|
||||||
.WithStrongNameProvider(new DesktopStrongNameProvider(commandLineArgs.KeyFileSearchPaths, Path.GetTempPath()))
|
.WithStrongNameProvider(new DesktopStrongNameProvider(commandLineArgs.KeyFileSearchPaths, Path.GetTempPath()))
|
||||||
.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);
|
.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);
|
||||||
|
|
||||||
var documents = CreateDocumentInfos(projectFileInfo.Documents, projectId, commandLineArgs.Encoding);
|
var documents = CreateDocumentInfos(projectFileInfo.Documents, projectId, commandLineArgs.Encoding);
|
||||||
var additionalDocuments = CreateDocumentInfos(projectFileInfo.AdditionalDocuments, projectId, commandLineArgs.Encoding);
|
var additionalDocuments = CreateDocumentInfos(projectFileInfo.AdditionalDocuments, projectId, commandLineArgs.Encoding);
|
||||||
var analyzerConfigDocuments = CreateDocumentInfos(projectFileInfo.AnalyzerConfigDocuments, projectId, commandLineArgs.Encoding);
|
var analyzerConfigDocuments = CreateDocumentInfos(projectFileInfo.AnalyzerConfigDocuments, projectId, commandLineArgs.Encoding);
|
||||||
CheckForDuplicateDocuments(documents.Concat(additionalDocuments).Concat(analyzerConfigDocuments), projectPath, projectId);
|
CheckForDuplicateDocuments(documents.Concat(additionalDocuments).Concat(analyzerConfigDocuments), projectPath, projectId);
|
||||||
|
|
||||||
var analyzerReferences = ResolveAnalyzerReferences(commandLineArgs);
|
var analyzerReferences = ResolveAnalyzerReferences(commandLineArgs);
|
||||||
|
|
||||||
var resolvedReferences = await ResolveReferencesAsync(projectId, projectFileInfo, commandLineArgs, cancellationToken).ConfigureAwait(false);
|
var resolvedReferences = await ResolveReferencesAsync(projectId, projectFileInfo, commandLineArgs, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return ProjectInfo.Create(
|
return ProjectInfo.Create(
|
||||||
new ProjectInfo.ProjectAttributes(
|
new ProjectInfo.ProjectAttributes(
|
||||||
projectId,
|
projectId,
|
||||||
version,
|
version,
|
||||||
projectName,
|
projectName,
|
||||||
assemblyName,
|
assemblyName,
|
||||||
language,
|
language,
|
||||||
compilationOutputInfo: new CompilationOutputInfo(projectFileInfo.IntermediateOutputFilePath, projectFileInfo.GeneratedFilesOutputDirectory),
|
compilationOutputInfo: new CompilationOutputInfo(projectFileInfo.IntermediateOutputFilePath, projectFileInfo.GeneratedFilesOutputDirectory),
|
||||||
checksumAlgorithm: commandLineArgs.ChecksumAlgorithm,
|
checksumAlgorithm: commandLineArgs.ChecksumAlgorithm,
|
||||||
filePath: projectPath,
|
filePath: projectPath,
|
||||||
outputFilePath: projectFileInfo.OutputFilePath,
|
outputFilePath: projectFileInfo.OutputFilePath,
|
||||||
outputRefFilePath: projectFileInfo.OutputRefFilePath,
|
outputRefFilePath: projectFileInfo.OutputRefFilePath,
|
||||||
isSubmission: false),
|
isSubmission: false),
|
||||||
compilationOptions: compilationOptions,
|
compilationOptions: compilationOptions,
|
||||||
parseOptions: parseOptions,
|
parseOptions: parseOptions,
|
||||||
documents: documents,
|
documents: documents,
|
||||||
projectReferences: resolvedReferences.ProjectReferences,
|
projectReferences: resolvedReferences.ProjectReferences,
|
||||||
metadataReferences: resolvedReferences.MetadataReferences,
|
metadataReferences: resolvedReferences.MetadataReferences,
|
||||||
analyzerReferences: analyzerReferences,
|
analyzerReferences: analyzerReferences,
|
||||||
additionalDocuments: additionalDocuments,
|
additionalDocuments: additionalDocuments,
|
||||||
hostObjectType: null)
|
hostObjectType: null)
|
||||||
.WithDefaultNamespace(projectFileInfo.DefaultNamespace)
|
.WithDefaultNamespace(projectFileInfo.DefaultNamespace)
|
||||||
.WithAnalyzerConfigDocuments(analyzerConfigDocuments);
|
.WithAnalyzerConfigDocuments(analyzerConfigDocuments);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetAssemblyNameFromProjectPath(string? projectFilePath)
|
private static string GetAssemblyNameFromProjectPath(string? projectFilePath)
|
||||||
{
|
{
|
||||||
var assemblyName = Path.GetFileNameWithoutExtension(projectFilePath);
|
var assemblyName = Path.GetFileNameWithoutExtension(projectFilePath);
|
||||||
|
|
||||||
// if this is still unreasonable, use a fixed name.
|
// if this is still unreasonable, use a fixed name.
|
||||||
if (RoslynString.IsNullOrWhiteSpace(assemblyName))
|
if (RoslynString.IsNullOrWhiteSpace(assemblyName))
|
||||||
{
|
{
|
||||||
assemblyName = "assembly";
|
assemblyName = "assembly";
|
||||||
}
|
}
|
||||||
|
|
||||||
return assemblyName;
|
return assemblyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<AnalyzerReference> ResolveAnalyzerReferences(CommandLineArguments commandLineArgs)
|
private IEnumerable<AnalyzerReference> ResolveAnalyzerReferences(CommandLineArguments commandLineArgs)
|
||||||
{
|
{
|
||||||
// The one line that is changed from the original Roslyn code:
|
// The one line that is changed from the original Roslyn code:
|
||||||
var analyzerAssemblyLoaderProvider = GetWorkspaceService<IAnalyzerAssemblyLoaderProvider>();
|
var analyzerAssemblyLoaderProvider = GetWorkspaceService<IAnalyzerAssemblyLoaderProvider>();
|
||||||
if (analyzerAssemblyLoaderProvider is null)
|
if (analyzerAssemblyLoaderProvider is null)
|
||||||
{
|
{
|
||||||
var message = string.Format(WorkspaceMSBuildResources.Unable_to_find_0, nameof(IAnalyzerService));
|
var message = string.Format(WorkspaceMSBuildResources.Unable_to_find_0, nameof(IAnalyzerService));
|
||||||
throw new Exception(message);
|
throw new Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var analyzerLoader = analyzerAssemblyLoaderProvider.SharedShadowCopyLoader;
|
var analyzerLoader = analyzerAssemblyLoaderProvider.SharedShadowCopyLoader;
|
||||||
|
|
||||||
foreach (var path in commandLineArgs.AnalyzerReferences.Select(r => r.FilePath))
|
foreach (var path in commandLineArgs.AnalyzerReferences.Select(r => r.FilePath))
|
||||||
{
|
{
|
||||||
string? fullPath;
|
string? fullPath;
|
||||||
|
|
||||||
if (PathUtilities.IsAbsolute(path))
|
if (PathUtilities.IsAbsolute(path))
|
||||||
{
|
{
|
||||||
fullPath = FileUtilities.TryNormalizeAbsolutePath(path);
|
fullPath = FileUtilities.TryNormalizeAbsolutePath(path);
|
||||||
|
|
||||||
if (fullPath != null && File.Exists(fullPath))
|
if (fullPath != null && File.Exists(fullPath))
|
||||||
{
|
{
|
||||||
analyzerLoader.AddDependencyLocation(fullPath);
|
analyzerLoader.AddDependencyLocation(fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var analyzerReferences = commandLineArgs.ResolveAnalyzerReferences(analyzerLoader).Distinct(Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.Worker.AnalyzerReferencePathComparer.Instance);
|
var analyzerReferences = commandLineArgs.ResolveAnalyzerReferences(analyzerLoader).Distinct(Microsoft.CodeAnalysis.MSBuild.MSBuildProjectLoader.Worker.AnalyzerReferencePathComparer.Instance);
|
||||||
|
|
||||||
var isolatedReferences = IsolatedAnalyzerReferenceSet.CreateIsolatedAnalyzerReferencesAsync(
|
var isolatedReferences = IsolatedAnalyzerReferenceSet.CreateIsolatedAnalyzerReferencesAsync(
|
||||||
@@ -424,80 +424,80 @@ public partial class CustomMsBuildProjectLoader
|
|||||||
CancellationToken.None).VerifyCompleted();
|
CancellationToken.None).VerifyCompleted();
|
||||||
|
|
||||||
return isolatedReferences;
|
return isolatedReferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableArray<DocumentInfo> CreateDocumentInfos(IReadOnlyList<DocumentFileInfo> documentFileInfos, ProjectId projectId, Encoding? encoding)
|
private ImmutableArray<DocumentInfo> CreateDocumentInfos(IReadOnlyList<DocumentFileInfo> documentFileInfos, ProjectId projectId, Encoding? encoding)
|
||||||
{
|
{
|
||||||
var results = new FixedSizeArrayBuilder<DocumentInfo>(documentFileInfos.Count);
|
var results = new FixedSizeArrayBuilder<DocumentInfo>(documentFileInfos.Count);
|
||||||
|
|
||||||
foreach (var info in documentFileInfos)
|
foreach (var info in documentFileInfos)
|
||||||
{
|
{
|
||||||
GetDocumentNameAndFolders(info.LogicalPath, out var name, out var folders);
|
GetDocumentNameAndFolders(info.LogicalPath, out var name, out var folders);
|
||||||
|
|
||||||
var documentInfo = DocumentInfo.Create(
|
var documentInfo = DocumentInfo.Create(
|
||||||
DocumentId.CreateNewId(projectId, debugName: info.FilePath),
|
DocumentId.CreateNewId(projectId, debugName: info.FilePath),
|
||||||
name,
|
name,
|
||||||
folders,
|
folders,
|
||||||
SourceCodeKind.Regular,
|
SourceCodeKind.Regular,
|
||||||
new WorkspaceFileTextLoader(_solutionServices, info.FilePath, encoding),
|
new WorkspaceFileTextLoader(_solutionServices, info.FilePath, encoding),
|
||||||
info.FilePath,
|
info.FilePath,
|
||||||
info.IsGenerated);
|
info.IsGenerated);
|
||||||
|
|
||||||
results.Add(documentInfo);
|
results.Add(documentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.MoveToImmutable();
|
return results.MoveToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly char[] s_directorySplitChars = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar];
|
private static readonly char[] s_directorySplitChars = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar];
|
||||||
|
|
||||||
private static void GetDocumentNameAndFolders(string logicalPath, out string name, out ImmutableArray<string> folders)
|
private static void GetDocumentNameAndFolders(string logicalPath, out string name, out ImmutableArray<string> folders)
|
||||||
{
|
{
|
||||||
var pathNames = logicalPath.Split(s_directorySplitChars, StringSplitOptions.RemoveEmptyEntries);
|
var pathNames = logicalPath.Split(s_directorySplitChars, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (pathNames.Length > 0)
|
if (pathNames.Length > 0)
|
||||||
{
|
{
|
||||||
folders = pathNames.Length > 1
|
folders = pathNames.Length > 1
|
||||||
? [.. pathNames.Take(pathNames.Length - 1)]
|
? [.. pathNames.Take(pathNames.Length - 1)]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
name = pathNames[^1];
|
name = pathNames[^1];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
name = logicalPath;
|
name = logicalPath;
|
||||||
folders = [];
|
folders = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckForDuplicateDocuments(ImmutableArray<DocumentInfo> documents, string? projectFilePath, ProjectId projectId)
|
private void CheckForDuplicateDocuments(ImmutableArray<DocumentInfo> documents, string? projectFilePath, ProjectId projectId)
|
||||||
{
|
{
|
||||||
var paths = new HashSet<string>(PathUtilities.Comparer);
|
var paths = new HashSet<string>(PathUtilities.Comparer);
|
||||||
foreach (var doc in documents)
|
foreach (var doc in documents)
|
||||||
{
|
{
|
||||||
if (doc.FilePath is null)
|
if (doc.FilePath is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (paths.Contains(doc.FilePath))
|
if (paths.Contains(doc.FilePath))
|
||||||
{
|
{
|
||||||
var message = string.Format(WorkspacesResources.Duplicate_source_file_0_in_project_1, doc.FilePath, projectFilePath);
|
var message = string.Format(WorkspacesResources.Duplicate_source_file_0_in_project_1, doc.FilePath, projectFilePath);
|
||||||
var diagnostic = new ProjectDiagnostic(WorkspaceDiagnosticKind.Warning, message, projectId);
|
var diagnostic = new ProjectDiagnostic(WorkspaceDiagnosticKind.Warning, message, projectId);
|
||||||
|
|
||||||
_diagnosticReporter.Report(diagnostic);
|
_diagnosticReporter.Report(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
paths.Add(doc.FilePath);
|
paths.Add(doc.FilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TLanguageService? GetLanguageService<TLanguageService>(string languageName)
|
private TLanguageService? GetLanguageService<TLanguageService>(string languageName)
|
||||||
where TLanguageService : ILanguageService
|
where TLanguageService : ILanguageService
|
||||||
=> _solutionServices
|
=> _solutionServices
|
||||||
.GetLanguageServices(languageName)
|
.GetLanguageServices(languageName)
|
||||||
.GetService<TLanguageService>();
|
.GetService<TLanguageService>();
|
||||||
|
|
||||||
private TWorkspaceService? GetWorkspaceService<TWorkspaceService>()
|
private TWorkspaceService? GetWorkspaceService<TWorkspaceService>()
|
||||||
where TWorkspaceService : IWorkspaceService
|
where TWorkspaceService : IWorkspaceService
|
||||||
=> _solutionServices
|
=> _solutionServices
|
||||||
.GetService<TWorkspaceService>();
|
.GetService<TWorkspaceService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,400 +9,400 @@ namespace SharpIDE.Application.Features.Analysis.ProjectLoader;
|
|||||||
|
|
||||||
public partial class CustomMsBuildProjectLoader
|
public partial class CustomMsBuildProjectLoader
|
||||||
{
|
{
|
||||||
private sealed partial class CustomWorker
|
private sealed partial class CustomWorker
|
||||||
{
|
{
|
||||||
private readonly struct ResolvedReferences
|
private readonly struct ResolvedReferences
|
||||||
{
|
{
|
||||||
public ImmutableHashSet<ProjectReference> ProjectReferences { get; }
|
public ImmutableHashSet<ProjectReference> ProjectReferences { get; }
|
||||||
public ImmutableArray<MetadataReference> MetadataReferences { get; }
|
public ImmutableArray<MetadataReference> MetadataReferences { get; }
|
||||||
|
|
||||||
public ResolvedReferences(ImmutableHashSet<ProjectReference> projectReferences, ImmutableArray<MetadataReference> metadataReferences)
|
public ResolvedReferences(ImmutableHashSet<ProjectReference> projectReferences, ImmutableArray<MetadataReference> metadataReferences)
|
||||||
{
|
{
|
||||||
ProjectReferences = projectReferences;
|
ProjectReferences = projectReferences;
|
||||||
MetadataReferences = metadataReferences;
|
MetadataReferences = metadataReferences;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This type helps produces lists of metadata and project references. Initially, it contains a list of metadata references.
|
/// This type helps produces lists of metadata and project references. Initially, it contains a list of metadata references.
|
||||||
/// As project references are added, the metadata references that match those project references are removed.
|
/// As project references are added, the metadata references that match those project references are removed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private sealed class ResolvedReferencesBuilder
|
private sealed class ResolvedReferencesBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full list of <see cref="MetadataReference"/>s.
|
/// The full list of <see cref="MetadataReference"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ImmutableArray<MetadataReference> _metadataReferences;
|
private readonly ImmutableArray<MetadataReference> _metadataReferences;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A map of every metadata reference file paths to a set of indices whether than file path
|
/// A map of every metadata reference file paths to a set of indices whether than file path
|
||||||
/// exists in the list. It is expected that there may be multiple metadata references for the
|
/// exists in the list. It is expected that there may be multiple metadata references for the
|
||||||
/// same file path in the case where multiple extern aliases are provided.
|
/// same file path in the case where multiple extern aliases are provided.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ImmutableDictionary<string, HashSet<int>> _pathToIndicesMap;
|
private readonly ImmutableDictionary<string, HashSet<int>> _pathToIndicesMap;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A set of indices into <see cref="_metadataReferences"/> that are to be removed.
|
/// A set of indices into <see cref="_metadataReferences"/> that are to be removed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly HashSet<int> _indicesToRemove;
|
private readonly HashSet<int> _indicesToRemove;
|
||||||
|
|
||||||
private readonly ImmutableHashSet<ProjectReference>.Builder _projectReferences;
|
private readonly ImmutableHashSet<ProjectReference>.Builder _projectReferences;
|
||||||
|
|
||||||
public ResolvedReferencesBuilder(IEnumerable<MetadataReference> metadataReferences)
|
public ResolvedReferencesBuilder(IEnumerable<MetadataReference> metadataReferences)
|
||||||
{
|
{
|
||||||
_metadataReferences = [.. metadataReferences];
|
_metadataReferences = [.. metadataReferences];
|
||||||
_pathToIndicesMap = CreatePathToIndexMap(_metadataReferences);
|
_pathToIndicesMap = CreatePathToIndexMap(_metadataReferences);
|
||||||
_indicesToRemove = [];
|
_indicesToRemove = [];
|
||||||
_projectReferences = ImmutableHashSet.CreateBuilder<ProjectReference>();
|
_projectReferences = ImmutableHashSet.CreateBuilder<ProjectReference>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableDictionary<string, HashSet<int>> CreatePathToIndexMap(ImmutableArray<MetadataReference> metadataReferences)
|
private static ImmutableDictionary<string, HashSet<int>> CreatePathToIndexMap(ImmutableArray<MetadataReference> metadataReferences)
|
||||||
{
|
{
|
||||||
var builder = ImmutableDictionary.CreateBuilder<string, HashSet<int>>(PathUtilities.Comparer);
|
var builder = ImmutableDictionary.CreateBuilder<string, HashSet<int>>(PathUtilities.Comparer);
|
||||||
|
|
||||||
for (var index = 0; index < metadataReferences.Length; index++)
|
for (var index = 0; index < metadataReferences.Length; index++)
|
||||||
{
|
{
|
||||||
var filePath = GetFilePath(metadataReferences[index]);
|
var filePath = GetFilePath(metadataReferences[index]);
|
||||||
if (filePath != null)
|
if (filePath != null)
|
||||||
{
|
{
|
||||||
builder.MultiAdd(filePath, index);
|
builder.MultiAdd(filePath, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToImmutable();
|
return builder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? GetFilePath(MetadataReference metadataReference)
|
private static string? GetFilePath(MetadataReference metadataReference)
|
||||||
{
|
{
|
||||||
return metadataReference switch
|
return metadataReference switch
|
||||||
{
|
{
|
||||||
PortableExecutableReference portableExecutableReference => portableExecutableReference.FilePath,
|
PortableExecutableReference portableExecutableReference => portableExecutableReference.FilePath,
|
||||||
UnresolvedMetadataReference unresolvedMetadataReference => unresolvedMetadataReference.Reference,
|
UnresolvedMetadataReference unresolvedMetadataReference => unresolvedMetadataReference.Reference,
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddProjectReference(ProjectReference projectReference)
|
public void AddProjectReference(ProjectReference projectReference)
|
||||||
{
|
{
|
||||||
_projectReferences.Add(projectReference);
|
_projectReferences.Add(projectReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SwapMetadataReferenceForProjectReference(ProjectReference projectReference, params string?[] possibleMetadataReferencePaths)
|
public void SwapMetadataReferenceForProjectReference(ProjectReference projectReference, params string?[] possibleMetadataReferencePaths)
|
||||||
{
|
{
|
||||||
foreach (var path in possibleMetadataReferencePaths)
|
foreach (var path in possibleMetadataReferencePaths)
|
||||||
{
|
{
|
||||||
if (path != null)
|
if (path != null)
|
||||||
{
|
{
|
||||||
Remove(path);
|
Remove(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddProjectReference(projectReference);
|
AddProjectReference(projectReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if a metadata reference with the given file path is contained within this list.
|
/// Returns true if a metadata reference with the given file path is contained within this list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Contains(string? filePath)
|
public bool Contains(string? filePath)
|
||||||
=> filePath != null
|
=> filePath != null
|
||||||
&& _pathToIndicesMap.ContainsKey(filePath);
|
&& _pathToIndicesMap.ContainsKey(filePath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the metadata reference with the given file path from this list.
|
/// Removes the metadata reference with the given file path from this list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Remove(string filePath)
|
public void Remove(string filePath)
|
||||||
{
|
{
|
||||||
if (filePath != null && _pathToIndicesMap.TryGetValue(filePath, out var indices))
|
if (filePath != null && _pathToIndicesMap.TryGetValue(filePath, out var indices))
|
||||||
{
|
{
|
||||||
_indicesToRemove.AddRange(indices);
|
_indicesToRemove.AddRange(indices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProjectInfo? SelectProjectInfoByOutput(IEnumerable<ProjectInfo> projectInfos)
|
public ProjectInfo? SelectProjectInfoByOutput(IEnumerable<ProjectInfo> projectInfos)
|
||||||
{
|
{
|
||||||
foreach (var projectInfo in projectInfos)
|
foreach (var projectInfo in projectInfos)
|
||||||
{
|
{
|
||||||
var outputFilePath = projectInfo.OutputFilePath;
|
var outputFilePath = projectInfo.OutputFilePath;
|
||||||
var outputRefFilePath = projectInfo.OutputRefFilePath;
|
var outputRefFilePath = projectInfo.OutputRefFilePath;
|
||||||
if (outputFilePath != null &&
|
if (outputFilePath != null &&
|
||||||
outputRefFilePath != null &&
|
outputRefFilePath != null &&
|
||||||
(Contains(outputFilePath) || Contains(outputRefFilePath)))
|
(Contains(outputFilePath) || Contains(outputRefFilePath)))
|
||||||
{
|
{
|
||||||
return projectInfo;
|
return projectInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutableArray<UnresolvedMetadataReference> GetUnresolvedMetadataReferences()
|
public ImmutableArray<UnresolvedMetadataReference> GetUnresolvedMetadataReferences()
|
||||||
{
|
{
|
||||||
var builder = ImmutableArray.CreateBuilder<UnresolvedMetadataReference>();
|
var builder = ImmutableArray.CreateBuilder<UnresolvedMetadataReference>();
|
||||||
|
|
||||||
foreach (var metadataReference in GetMetadataReferences())
|
foreach (var metadataReference in GetMetadataReferences())
|
||||||
{
|
{
|
||||||
if (metadataReference is UnresolvedMetadataReference unresolvedMetadataReference)
|
if (metadataReference is UnresolvedMetadataReference unresolvedMetadataReference)
|
||||||
{
|
{
|
||||||
builder.Add(unresolvedMetadataReference);
|
builder.Add(unresolvedMetadataReference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToImmutableAndClear();
|
return builder.ToImmutableAndClear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableArray<MetadataReference> GetMetadataReferences()
|
private ImmutableArray<MetadataReference> GetMetadataReferences()
|
||||||
{
|
{
|
||||||
var builder = ImmutableArray.CreateBuilder<MetadataReference>();
|
var builder = ImmutableArray.CreateBuilder<MetadataReference>();
|
||||||
|
|
||||||
// used to eliminate duplicates
|
// used to eliminate duplicates
|
||||||
var _ = PooledHashSet<MetadataReference>.GetInstance(out var set);
|
var _ = PooledHashSet<MetadataReference>.GetInstance(out var set);
|
||||||
|
|
||||||
for (var index = 0; index < _metadataReferences.Length; index++)
|
for (var index = 0; index < _metadataReferences.Length; index++)
|
||||||
{
|
{
|
||||||
var reference = _metadataReferences[index];
|
var reference = _metadataReferences[index];
|
||||||
if (!_indicesToRemove.Contains(index) && set.Add(reference))
|
if (!_indicesToRemove.Contains(index) && set.Add(reference))
|
||||||
{
|
{
|
||||||
builder.Add(reference);
|
builder.Add(reference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToImmutableAndClear();
|
return builder.ToImmutableAndClear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableHashSet<ProjectReference> GetProjectReferences()
|
private ImmutableHashSet<ProjectReference> GetProjectReferences()
|
||||||
=> _projectReferences.ToImmutable();
|
=> _projectReferences.ToImmutable();
|
||||||
|
|
||||||
public ResolvedReferences ToResolvedReferences()
|
public ResolvedReferences ToResolvedReferences()
|
||||||
=> new(GetProjectReferences(), GetMetadataReferences());
|
=> new(GetProjectReferences(), GetMetadataReferences());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ResolvedReferences> ResolveReferencesAsync(ProjectId id, ProjectFileInfo projectFileInfo, CommandLineArguments commandLineArgs, CancellationToken cancellationToken)
|
private async Task<ResolvedReferences> ResolveReferencesAsync(ProjectId id, ProjectFileInfo projectFileInfo, CommandLineArguments commandLineArgs, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// First, gather all of the metadata references from the command-line arguments.
|
// First, gather all of the metadata references from the command-line arguments.
|
||||||
var resolvedMetadataReferences = commandLineArgs.ResolveMetadataReferences(
|
var resolvedMetadataReferences = commandLineArgs.ResolveMetadataReferences(
|
||||||
new WorkspaceMetadataFileReferenceResolver(
|
new WorkspaceMetadataFileReferenceResolver(
|
||||||
metadataService: _solutionServices.GetRequiredService<IMetadataService>(),
|
metadataService: _solutionServices.GetRequiredService<IMetadataService>(),
|
||||||
pathResolver: new RelativePathResolver(commandLineArgs.ReferencePaths, commandLineArgs.BaseDirectory)));
|
pathResolver: new RelativePathResolver(commandLineArgs.ReferencePaths, commandLineArgs.BaseDirectory)));
|
||||||
|
|
||||||
var builder = new ResolvedReferencesBuilder(resolvedMetadataReferences);
|
var builder = new ResolvedReferencesBuilder(resolvedMetadataReferences);
|
||||||
|
|
||||||
var projectDirectory = Path.GetDirectoryName(projectFileInfo.FilePath);
|
var projectDirectory = Path.GetDirectoryName(projectFileInfo.FilePath);
|
||||||
RoslynDebug.AssertNotNull(projectDirectory);
|
RoslynDebug.AssertNotNull(projectDirectory);
|
||||||
|
|
||||||
// Next, iterate through all project references in the file and create project references.
|
// Next, iterate through all project references in the file and create project references.
|
||||||
foreach (var projectFileReference in projectFileInfo.ProjectReferences)
|
foreach (var projectFileReference in projectFileInfo.ProjectReferences)
|
||||||
{
|
{
|
||||||
var aliases = projectFileReference.Aliases;
|
var aliases = projectFileReference.Aliases;
|
||||||
|
|
||||||
if (_pathResolver.TryGetAbsoluteProjectPath(projectFileReference.Path, baseDirectory: projectDirectory, _discoveredProjectOptions.OnPathFailure, out var projectReferencePath))
|
if (_pathResolver.TryGetAbsoluteProjectPath(projectFileReference.Path, baseDirectory: projectDirectory, _discoveredProjectOptions.OnPathFailure, out var projectReferencePath))
|
||||||
{
|
{
|
||||||
// The easiest case is to add a reference to a project we already know about.
|
// The easiest case is to add a reference to a project we already know about.
|
||||||
if (TryAddReferenceToKnownProject(id, projectReferencePath, aliases, builder))
|
if (TryAddReferenceToKnownProject(id, projectReferencePath, aliases, builder))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectFileReference.ReferenceOutputAssembly)
|
if (projectFileReference.ReferenceOutputAssembly)
|
||||||
{
|
{
|
||||||
// If we don't know how to load a project (that is, it's not a language we support), we can still
|
// If we don't know how to load a project (that is, it's not a language we support), we can still
|
||||||
// attempt to verify that its output exists on disk and is included in our set of metadata references.
|
// attempt to verify that its output exists on disk and is included in our set of metadata references.
|
||||||
// If it is, we'll just leave it in place.
|
// If it is, we'll just leave it in place.
|
||||||
if (!IsProjectLoadable(projectReferencePath) &&
|
if (!IsProjectLoadable(projectReferencePath) &&
|
||||||
await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
|
await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If metadata is preferred, see if the project reference's output exists on disk and is included
|
// If metadata is preferred, see if the project reference's output exists on disk and is included
|
||||||
// in our metadata references. If it is, don't create a project reference; we'll just use the metadata.
|
// in our metadata references. If it is, don't create a project reference; we'll just use the metadata.
|
||||||
if (_preferMetadataForReferencesOfDiscoveredProjects &&
|
if (_preferMetadataForReferencesOfDiscoveredProjects &&
|
||||||
await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
|
await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, we'll try to load and reference the project.
|
// Finally, we'll try to load and reference the project.
|
||||||
if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false))
|
if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Load the project but do not add a reference:
|
// Load the project but do not add a reference:
|
||||||
_ = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false);
|
_ = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We weren't able to handle this project reference, so add it without further processing.
|
// We weren't able to handle this project reference, so add it without further processing.
|
||||||
var unknownProjectId = _projectMap.GetOrCreateProjectId(projectFileReference.Path);
|
var unknownProjectId = _projectMap.GetOrCreateProjectId(projectFileReference.Path);
|
||||||
var newProjectReference = CreateProjectReference(from: id, to: unknownProjectId, aliases);
|
var newProjectReference = CreateProjectReference(from: id, to: unknownProjectId, aliases);
|
||||||
builder.AddProjectReference(newProjectReference);
|
builder.AddProjectReference(newProjectReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are there still any unresolved metadata references? If so, remove them and report diagnostics.
|
// Are there still any unresolved metadata references? If so, remove them and report diagnostics.
|
||||||
foreach (var unresolvedMetadataReference in builder.GetUnresolvedMetadataReferences())
|
foreach (var unresolvedMetadataReference in builder.GetUnresolvedMetadataReferences())
|
||||||
{
|
{
|
||||||
var filePath = unresolvedMetadataReference.Reference;
|
var filePath = unresolvedMetadataReference.Reference;
|
||||||
|
|
||||||
builder.Remove(filePath);
|
builder.Remove(filePath);
|
||||||
|
|
||||||
_diagnosticReporter.Report(new ProjectDiagnostic(
|
_diagnosticReporter.Report(new ProjectDiagnostic(
|
||||||
WorkspaceDiagnosticKind.Warning,
|
WorkspaceDiagnosticKind.Warning,
|
||||||
string.Format(WorkspaceMSBuildResources.Unresolved_metadata_reference_removed_from_project_0, filePath),
|
string.Format(WorkspaceMSBuildResources.Unresolved_metadata_reference_removed_from_project_0, filePath),
|
||||||
id));
|
id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToResolvedReferences();
|
return builder.ToResolvedReferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> TryLoadAndAddReferenceAsync(ProjectId id, string projectReferencePath, ImmutableArray<string> aliases, ResolvedReferencesBuilder builder, CancellationToken cancellationToken)
|
private async Task<bool> TryLoadAndAddReferenceAsync(ProjectId id, string projectReferencePath, ImmutableArray<string> aliases, ResolvedReferencesBuilder builder, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var projectReferenceInfos = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false);
|
var projectReferenceInfos = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (projectReferenceInfos.IsEmpty)
|
if (projectReferenceInfos.IsEmpty)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the project reference info whose output we have a metadata reference for.
|
// Find the project reference info whose output we have a metadata reference for.
|
||||||
ProjectInfo? projectReferenceInfo = null;
|
ProjectInfo? projectReferenceInfo = null;
|
||||||
foreach (var info in projectReferenceInfos)
|
foreach (var info in projectReferenceInfos)
|
||||||
{
|
{
|
||||||
var outputFilePath = info.OutputFilePath;
|
var outputFilePath = info.OutputFilePath;
|
||||||
var outputRefFilePath = info.OutputRefFilePath;
|
var outputRefFilePath = info.OutputRefFilePath;
|
||||||
if (outputFilePath != null &&
|
if (outputFilePath != null &&
|
||||||
outputRefFilePath != null &&
|
outputRefFilePath != null &&
|
||||||
(builder.Contains(outputFilePath) || builder.Contains(outputRefFilePath)))
|
(builder.Contains(outputFilePath) || builder.Contains(outputRefFilePath)))
|
||||||
{
|
{
|
||||||
projectReferenceInfo = info;
|
projectReferenceInfo = info;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectReferenceInfo is null)
|
if (projectReferenceInfo is null)
|
||||||
{
|
{
|
||||||
// We didn't find the project reference info that matches any of our metadata references.
|
// We didn't find the project reference info that matches any of our metadata references.
|
||||||
// In this case, we'll go ahead and use the first project reference info that was found,
|
// In this case, we'll go ahead and use the first project reference info that was found,
|
||||||
// but report a warning because this likely means that either a metadata reference path
|
// but report a warning because this likely means that either a metadata reference path
|
||||||
// or a project output path is incorrect.
|
// or a project output path is incorrect.
|
||||||
|
|
||||||
projectReferenceInfo = projectReferenceInfos[0];
|
projectReferenceInfo = projectReferenceInfos[0];
|
||||||
|
|
||||||
_diagnosticReporter.Report(new ProjectDiagnostic(
|
_diagnosticReporter.Report(new ProjectDiagnostic(
|
||||||
WorkspaceDiagnosticKind.Warning,
|
WorkspaceDiagnosticKind.Warning,
|
||||||
string.Format(WorkspaceMSBuildResources.Found_project_reference_without_a_matching_metadata_reference_0, projectReferencePath),
|
string.Format(WorkspaceMSBuildResources.Found_project_reference_without_a_matching_metadata_reference_0, projectReferencePath),
|
||||||
id));
|
id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ProjectReferenceExists(to: id, from: projectReferenceInfo))
|
if (!ProjectReferenceExists(to: id, from: projectReferenceInfo))
|
||||||
{
|
{
|
||||||
var newProjectReference = CreateProjectReference(from: id, to: projectReferenceInfo.Id, aliases);
|
var newProjectReference = CreateProjectReference(from: id, to: projectReferenceInfo.Id, aliases);
|
||||||
builder.SwapMetadataReferenceForProjectReference(newProjectReference, projectReferenceInfo.OutputRefFilePath, projectReferenceInfo.OutputFilePath);
|
builder.SwapMetadataReferenceForProjectReference(newProjectReference, projectReferenceInfo.OutputRefFilePath, projectReferenceInfo.OutputFilePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This project already has a reference on us. Don't introduce a circularity by referencing it.
|
// This project already has a reference on us. Don't introduce a circularity by referencing it.
|
||||||
// However, if the project's output doesn't exist on disk, we need to remove from our list of
|
// However, if the project's output doesn't exist on disk, we need to remove from our list of
|
||||||
// metadata references to avoid failures later. Essentially, the concern here is that the metadata
|
// metadata references to avoid failures later. Essentially, the concern here is that the metadata
|
||||||
// reference is an UnresolvedMetadataReference, which will throw when we try to create a
|
// reference is an UnresolvedMetadataReference, which will throw when we try to create a
|
||||||
// Compilation with it.
|
// Compilation with it.
|
||||||
|
|
||||||
var outputRefFilePath = projectReferenceInfo.OutputRefFilePath;
|
var outputRefFilePath = projectReferenceInfo.OutputRefFilePath;
|
||||||
if (outputRefFilePath != null && !File.Exists(outputRefFilePath))
|
if (outputRefFilePath != null && !File.Exists(outputRefFilePath))
|
||||||
{
|
{
|
||||||
builder.Remove(outputRefFilePath);
|
builder.Remove(outputRefFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputFilePath = projectReferenceInfo.OutputFilePath;
|
var outputFilePath = projectReferenceInfo.OutputFilePath;
|
||||||
if (outputFilePath != null && !File.Exists(outputFilePath))
|
if (outputFilePath != null && !File.Exists(outputFilePath))
|
||||||
{
|
{
|
||||||
builder.Remove(outputFilePath);
|
builder.Remove(outputFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that we return true even if we don't actually add a reference due to a circularity because,
|
// Note that we return true even if we don't actually add a reference due to a circularity because,
|
||||||
// in that case, we've still handled everything.
|
// in that case, we've still handled everything.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsProjectLoadable(string projectPath)
|
private bool IsProjectLoadable(string projectPath)
|
||||||
=> _projectFileExtensionRegistry.TryGetLanguageNameFromProjectPath(projectPath, DiagnosticReportingMode.Ignore, out _);
|
=> _projectFileExtensionRegistry.TryGetLanguageNameFromProjectPath(projectPath, DiagnosticReportingMode.Ignore, out _);
|
||||||
|
|
||||||
private async Task<bool> VerifyUnloadableProjectOutputExistsAsync(string projectPath, ResolvedReferencesBuilder builder, CancellationToken cancellationToken)
|
private async Task<bool> VerifyUnloadableProjectOutputExistsAsync(string projectPath, ResolvedReferencesBuilder builder, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var buildHost = await _buildHostProcessManager.GetBuildHostWithFallbackAsync(projectPath, cancellationToken).ConfigureAwait(false);
|
var buildHost = await _buildHostProcessManager.GetBuildHostWithFallbackAsync(projectPath, cancellationToken).ConfigureAwait(false);
|
||||||
var outputFilePath = await buildHost.TryGetProjectOutputPathAsync(projectPath, cancellationToken).ConfigureAwait(false);
|
var outputFilePath = await buildHost.TryGetProjectOutputPathAsync(projectPath, cancellationToken).ConfigureAwait(false);
|
||||||
return outputFilePath != null
|
return outputFilePath != null
|
||||||
&& builder.Contains(outputFilePath)
|
&& builder.Contains(outputFilePath)
|
||||||
&& File.Exists(outputFilePath);
|
&& File.Exists(outputFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> VerifyProjectOutputExistsAsync(string projectPath, ResolvedReferencesBuilder builder, CancellationToken cancellationToken)
|
private async Task<bool> VerifyProjectOutputExistsAsync(string projectPath, ResolvedReferencesBuilder builder, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Note: Load the project, but don't report failures.
|
// Note: Load the project, but don't report failures.
|
||||||
var projectFileInfos = await LoadProjectFileInfosAsync(projectPath, DiagnosticReportingOptions.IgnoreAll, cancellationToken).ConfigureAwait(false);
|
var projectFileInfos = await LoadProjectFileInfosAsync(projectPath, DiagnosticReportingOptions.IgnoreAll, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var projectFileInfo in projectFileInfos)
|
foreach (var projectFileInfo in projectFileInfos)
|
||||||
{
|
{
|
||||||
var outputFilePath = projectFileInfo.OutputFilePath;
|
var outputFilePath = projectFileInfo.OutputFilePath;
|
||||||
var outputRefFilePath = projectFileInfo.OutputRefFilePath;
|
var outputRefFilePath = projectFileInfo.OutputRefFilePath;
|
||||||
|
|
||||||
if ((builder.Contains(outputFilePath) && File.Exists(outputFilePath)) ||
|
if ((builder.Contains(outputFilePath) && File.Exists(outputFilePath)) ||
|
||||||
(builder.Contains(outputRefFilePath) && File.Exists(outputRefFilePath)))
|
(builder.Contains(outputRefFilePath) && File.Exists(outputRefFilePath)))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProjectReference CreateProjectReference(ProjectId from, ProjectId to, ImmutableArray<string> aliases)
|
private ProjectReference CreateProjectReference(ProjectId from, ProjectId to, ImmutableArray<string> aliases)
|
||||||
{
|
{
|
||||||
var newReference = new ProjectReference(to, aliases);
|
var newReference = new ProjectReference(to, aliases);
|
||||||
_projectIdToProjectReferencesMap.MultiAdd(from, newReference);
|
_projectIdToProjectReferencesMap.MultiAdd(from, newReference);
|
||||||
return newReference;
|
return newReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ProjectReferenceExists(ProjectId to, ProjectId from)
|
private bool ProjectReferenceExists(ProjectId to, ProjectId from)
|
||||||
=> _projectIdToProjectReferencesMap.TryGetValue(from, out var references)
|
=> _projectIdToProjectReferencesMap.TryGetValue(from, out var references)
|
||||||
&& references.Any(pr => pr.ProjectId == to);
|
&& references.Any(pr => pr.ProjectId == to);
|
||||||
|
|
||||||
private static bool ProjectReferenceExists(ProjectId to, ProjectInfo from)
|
private static bool ProjectReferenceExists(ProjectId to, ProjectInfo from)
|
||||||
=> from.ProjectReferences.Any(pr => pr.ProjectId == to);
|
=> from.ProjectReferences.Any(pr => pr.ProjectId == to);
|
||||||
|
|
||||||
private bool TryAddReferenceToKnownProject(
|
private bool TryAddReferenceToKnownProject(
|
||||||
ProjectId id,
|
ProjectId id,
|
||||||
string projectReferencePath,
|
string projectReferencePath,
|
||||||
ImmutableArray<string> aliases,
|
ImmutableArray<string> aliases,
|
||||||
ResolvedReferencesBuilder builder)
|
ResolvedReferencesBuilder builder)
|
||||||
{
|
{
|
||||||
if (_projectMap.TryGetIdsByProjectPath(projectReferencePath, out var projectReferenceIds))
|
if (_projectMap.TryGetIdsByProjectPath(projectReferencePath, out var projectReferenceIds))
|
||||||
{
|
{
|
||||||
foreach (var projectReferenceId in projectReferenceIds)
|
foreach (var projectReferenceId in projectReferenceIds)
|
||||||
{
|
{
|
||||||
// Don't add a reference if the project already has a reference on us. Otherwise, it will cause a circularity.
|
// Don't add a reference if the project already has a reference on us. Otherwise, it will cause a circularity.
|
||||||
if (ProjectReferenceExists(to: id, from: projectReferenceId))
|
if (ProjectReferenceExists(to: id, from: projectReferenceId))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputRefFilePath = _projectMap.GetOutputRefFilePathById(projectReferenceId);
|
var outputRefFilePath = _projectMap.GetOutputRefFilePathById(projectReferenceId);
|
||||||
var outputFilePath = _projectMap.GetOutputFilePathById(projectReferenceId);
|
var outputFilePath = _projectMap.GetOutputFilePathById(projectReferenceId);
|
||||||
|
|
||||||
if (builder.Contains(outputRefFilePath) ||
|
if (builder.Contains(outputRefFilePath) ||
|
||||||
builder.Contains(outputFilePath))
|
builder.Contains(outputFilePath))
|
||||||
{
|
{
|
||||||
var newProjectReference = CreateProjectReference(from: id, to: projectReferenceId, aliases);
|
var newProjectReference = CreateProjectReference(from: id, to: projectReferenceId, aliases);
|
||||||
builder.SwapMetadataReferenceForProjectReference(newProjectReference, outputRefFilePath, outputFilePath);
|
builder.SwapMetadataReferenceForProjectReference(newProjectReference, outputRefFilePath, outputFilePath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,68 +56,68 @@ public partial class CustomMsBuildProjectLoader(Workspace workspace, ImmutableDi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the <see cref="SolutionInfo"/> for the specified solution file, including all projects referenced by the solution file and
|
/// Loads the <see cref="SolutionInfo"/> for the specified solution file, including all projects referenced by the solution file and
|
||||||
/// all the projects referenced by the project files.
|
/// all the projects referenced by the project files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="solutionFilePath">The path to the solution file to be loaded. This may be an absolute path or a path relative to the
|
/// <param name="solutionFilePath">The path to the solution file to be loaded. This may be an absolute path or a path relative to the
|
||||||
/// current working directory.</param>
|
/// current working directory.</param>
|
||||||
/// <param name="progress">An optional <see cref="IProgress{T}"/> that will receive updates as the solution is loaded.</param>
|
/// <param name="progress">An optional <see cref="IProgress{T}"/> that will receive updates as the solution is loaded.</param>
|
||||||
/// <param name="msbuildLogger">An optional <see cref="ILogger"/> that will log MSBuild results.</param>
|
/// <param name="msbuildLogger">An optional <see cref="ILogger"/> that will log MSBuild results.</param>
|
||||||
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> to allow cancellation of this operation.</param>
|
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> to allow cancellation of this operation.</param>
|
||||||
public new async Task<(SolutionInfo, Dictionary<ProjectId, ProjectFileInfo>)> LoadSolutionInfoAsync(
|
public new async Task<(SolutionInfo, Dictionary<ProjectId, ProjectFileInfo>)> LoadSolutionInfoAsync(
|
||||||
string solutionFilePath,
|
string solutionFilePath,
|
||||||
IProgress<ProjectLoadProgress>? progress = null,
|
IProgress<ProjectLoadProgress>? progress = null,
|
||||||
ILogger? msbuildLogger = null,
|
ILogger? msbuildLogger = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (solutionFilePath == null)
|
if (solutionFilePath == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(solutionFilePath));
|
throw new ArgumentNullException(nameof(solutionFilePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
var reportingMode = GetReportingModeForUnrecognizedProjects();
|
var reportingMode = GetReportingModeForUnrecognizedProjects();
|
||||||
|
|
||||||
var reportingOptions = new DiagnosticReportingOptions(
|
var reportingOptions = new DiagnosticReportingOptions(
|
||||||
onPathFailure: reportingMode,
|
onPathFailure: reportingMode,
|
||||||
onLoaderFailure: reportingMode);
|
onLoaderFailure: reportingMode);
|
||||||
|
|
||||||
var (absoluteSolutionPath, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, _pathResolver, reportingMode, cancellationToken).ConfigureAwait(false);
|
var (absoluteSolutionPath, projects) = await SolutionFileReader.ReadSolutionFileAsync(solutionFilePath, _pathResolver, reportingMode, cancellationToken).ConfigureAwait(false);
|
||||||
var projectPaths = projects.SelectAsArray(p => p.ProjectPath);
|
var projectPaths = projects.SelectAsArray(p => p.ProjectPath);
|
||||||
|
|
||||||
using (_dataGuard.DisposableWait(cancellationToken))
|
using (_dataGuard.DisposableWait(cancellationToken))
|
||||||
{
|
{
|
||||||
SetSolutionProperties(absoluteSolutionPath);
|
SetSolutionProperties(absoluteSolutionPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
IBinLogPathProvider binLogPathProvider = null!; // TODO: Fix
|
IBinLogPathProvider binLogPathProvider = null!; // TODO: Fix
|
||||||
|
|
||||||
var buildHostProcessManager = new BuildHostProcessManager(Properties, binLogPathProvider, _loggerFactory);
|
var buildHostProcessManager = new BuildHostProcessManager(Properties, binLogPathProvider, _loggerFactory);
|
||||||
await using var _ = buildHostProcessManager.ConfigureAwait(false);
|
await using var _ = buildHostProcessManager.ConfigureAwait(false);
|
||||||
|
|
||||||
var worker = new CustomWorker(
|
var worker = new CustomWorker(
|
||||||
_solutionServices,
|
_solutionServices,
|
||||||
_diagnosticReporter,
|
_diagnosticReporter,
|
||||||
_pathResolver,
|
_pathResolver,
|
||||||
_projectFileExtensionRegistry,
|
_projectFileExtensionRegistry,
|
||||||
buildHostProcessManager,
|
buildHostProcessManager,
|
||||||
projectPaths,
|
projectPaths,
|
||||||
// TryGetAbsoluteSolutionPath should not return an invalid path
|
// TryGetAbsoluteSolutionPath should not return an invalid path
|
||||||
baseDirectory: Path.GetDirectoryName(absoluteSolutionPath)!,
|
baseDirectory: Path.GetDirectoryName(absoluteSolutionPath)!,
|
||||||
projectMap: null,
|
projectMap: null,
|
||||||
progress,
|
progress,
|
||||||
requestedProjectOptions: reportingOptions,
|
requestedProjectOptions: reportingOptions,
|
||||||
discoveredProjectOptions: reportingOptions,
|
discoveredProjectOptions: reportingOptions,
|
||||||
preferMetadataForReferencesOfDiscoveredProjects: false);
|
preferMetadataForReferencesOfDiscoveredProjects: false);
|
||||||
|
|
||||||
var (projectInfos, projectFileInfos) = await worker.LoadAsync(cancellationToken).ConfigureAwait(false);
|
var (projectInfos, projectFileInfos) = await worker.LoadAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// construct workspace from loaded project infos
|
// construct workspace from loaded project infos
|
||||||
var solutionInfo = SolutionInfo.Create(
|
var solutionInfo = SolutionInfo.Create(
|
||||||
SolutionId.CreateNewId(debugName: absoluteSolutionPath),
|
SolutionId.CreateNewId(debugName: absoluteSolutionPath),
|
||||||
version: default,
|
version: default,
|
||||||
absoluteSolutionPath,
|
absoluteSolutionPath,
|
||||||
projectInfos);
|
projectInfos);
|
||||||
|
|
||||||
return (solutionInfo, projectFileInfos);
|
return (solutionInfo, projectFileInfos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user