convert spaces to tabs

This commit is contained in:
Matt Parker
2025-12-13 12:31:09 +10:00
parent a149806818
commit a74553a4ca
3 changed files with 852 additions and 852 deletions

View File

@@ -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>();
} }
} }

View File

@@ -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;
} }
} }
} }

View File

@@ -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);
} }
} }