diff --git a/src/OmniSharp.Abstractions/Models/v1/FilesChanged/FileChangeType.cs b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs
similarity index 73%
rename from src/OmniSharp.Abstractions/Models/v1/FilesChanged/FileChangeType.cs
rename to src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs
index 7297a8343a..218513e78b 100644
--- a/src/OmniSharp.Abstractions/Models/v1/FilesChanged/FileChangeType.cs
+++ b/src/OmniSharp.Abstractions/FileWatching/FileChangeType.cs
@@ -1,4 +1,4 @@
-namespace OmniSharp.Models.FilesChanged
+namespace OmniSharp.FileWatching
{
public enum FileChangeType
{
diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemNotifier.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemNotifier.cs
new file mode 100644
index 0000000000..35c4cd2b64
--- /dev/null
+++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemNotifier.cs
@@ -0,0 +1,14 @@
+using OmniSharp.Models.FilesChanged;
+
+namespace OmniSharp.FileWatching
+{
+ public interface IFileSystemNotifier
+ {
+ ///
+ /// Notifiers any relevant file system watchers when a file is created, changed, or deleted.
+ ///
+ /// The path to the file that was changed.
+ /// The type of change. Hosts are not required to pass a change type.
+ void Notify(string filePath, FileChangeType changeType = FileChangeType.Unspecified);
+ }
+}
diff --git a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs
index 71d7d146de..9e6f4d0c46 100644
--- a/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs
+++ b/src/OmniSharp.Abstractions/FileWatching/IFileSystemWatcher.cs
@@ -1,20 +1,16 @@
-using System;
-using OmniSharp.Models.FilesChanged;
+using OmniSharp.Models.FilesChanged;
namespace OmniSharp.FileWatching
{
- // TODO: Flesh out this API more
+ public delegate void FileSystemNotificationCallback(string filePath, FileChangeType changeType);
+
public interface IFileSystemWatcher
{
- void Watch(string path, Action callback);
-
///
- /// Called when a file is created, changed, or deleted.
+ /// Call to watch a file or directory path for changes.
///
- /// The path to the file
- /// The type of change. Hosts are not required to pass a change type
- void TriggerChange(string path, FileChangeType changeType);
-
- void WatchDirectory(string path, Action callback);
+ /// The file or directory path to watch.
+ /// The callback that will be invoked when a change occurs in the watched file or directory.
+ void Watch(string fileOrDirectoryPath, FileSystemNotificationCallback callback);
}
}
diff --git a/src/OmniSharp.Abstractions/Models/v1/FilesChanged/FilesChangedRequest.cs b/src/OmniSharp.Abstractions/Models/v1/FilesChanged/FilesChangedRequest.cs
index e61a2056f6..6b720de559 100644
--- a/src/OmniSharp.Abstractions/Models/v1/FilesChanged/FilesChangedRequest.cs
+++ b/src/OmniSharp.Abstractions/Models/v1/FilesChanged/FilesChangedRequest.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using OmniSharp.FileWatching;
using OmniSharp.Mef;
namespace OmniSharp.Models.FilesChanged
diff --git a/src/OmniSharp.Host/CompositionHostBuilder.cs b/src/OmniSharp.Host/CompositionHostBuilder.cs
index 65967b5818..6a786d8e5e 100644
--- a/src/OmniSharp.Host/CompositionHostBuilder.cs
+++ b/src/OmniSharp.Host/CompositionHostBuilder.cs
@@ -70,6 +70,7 @@ public CompositionHost Build()
config = config
.WithProvider(MefValueProvider.From(_serviceProvider))
+ .WithProvider(MefValueProvider.From(fileSystemWatcher))
.WithProvider(MefValueProvider.From(fileSystemWatcher))
.WithProvider(MefValueProvider.From(memoryCache))
.WithProvider(MefValueProvider.From(loggerFactory))
diff --git a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs
index 548f3dbd43..4c85f97897 100644
--- a/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs
+++ b/src/OmniSharp.Host/FileWatching/ManualFileSystemWatcher.cs
@@ -5,39 +5,43 @@
namespace OmniSharp.FileWatching
{
- public class ManualFileSystemWatcher : IFileSystemWatcher
+ internal class ManualFileSystemWatcher : IFileSystemWatcher, IFileSystemNotifier
{
- private readonly Dictionary> _callbacks = new Dictionary>();
- private readonly Dictionary> _directoryCallBacks = new Dictionary>();
+ private readonly object _gate = new object();
+ private readonly Dictionary _callbacks;
- public void TriggerChange(string path, FileChangeType changeType)
+ public ManualFileSystemWatcher()
{
- if (_callbacks.TryGetValue(path, out var callback))
- {
- callback(path, changeType);
- }
-
- var directoryPath = Path.GetDirectoryName(path);
- if (_directoryCallBacks.TryGetValue(directoryPath, out var fileCallback))
- {
- fileCallback(path, changeType);
- }
+ _callbacks = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
- public void Watch(string path, Action callback)
+ public void Notify(string filePath, FileChangeType changeType = FileChangeType.Unspecified)
{
- _callbacks[path] = callback;
+ lock (_gate)
+ {
+ if (_callbacks.TryGetValue(filePath, out var fileCallback))
+ {
+ fileCallback(filePath, changeType);
+ }
+
+ var directoryPath = Path.GetDirectoryName(filePath);
+ if (_callbacks.TryGetValue(directoryPath, out var directoryCallback))
+ {
+ directoryCallback(filePath, changeType);
+ }
+ }
}
- public void WatchDirectory(string path, Action callback)
+ public void Watch(string fileOrDirectoryPath, FileSystemNotificationCallback callback)
{
- if (_directoryCallBacks.TryGetValue(path, out var existingCallback))
+ lock (_gate)
{
- _directoryCallBacks[path] = callback + existingCallback;
- }
- else
- {
- _directoryCallBacks[path] = callback;
+ if (_callbacks.TryGetValue(fileOrDirectoryPath, out var existingCallback))
+ {
+ callback = callback + existingCallback;
+ }
+
+ _callbacks[fileOrDirectoryPath] = callback;
}
}
}
diff --git a/src/OmniSharp.MSBuild/Logging/MSBuildDiagnostic.cs b/src/OmniSharp.MSBuild/Logging/MSBuildDiagnostic.cs
new file mode 100644
index 0000000000..8cc9cf1baf
--- /dev/null
+++ b/src/OmniSharp.MSBuild/Logging/MSBuildDiagnostic.cs
@@ -0,0 +1,43 @@
+namespace OmniSharp.MSBuild.Logging
+{
+ public class MSBuildDiagnostic
+ {
+ public MSBuildDiagnosticSeverity Severity { get; }
+ public string Message { get; }
+ public string File { get; }
+ public string ProjectFile { get; }
+ public string Subcategory { get; }
+ public string Code { get; }
+ public int LineNumber { get; }
+ public int ColumnNumber { get; }
+ public int EndLineNumber { get; }
+ public int EndColumnNumber { get; }
+
+ private MSBuildDiagnostic(
+ MSBuildDiagnosticSeverity severity,
+ string message, string file, string projectFile, string subcategory, string code,
+ int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber)
+ {
+ Severity = severity;
+ Message = message;
+ File = file;
+ ProjectFile = projectFile;
+ Subcategory = subcategory;
+ Code = code;
+ LineNumber = lineNumber;
+ ColumnNumber = columnNumber;
+ EndLineNumber = endLineNumber;
+ EndColumnNumber = endColumnNumber;
+ }
+
+ public static MSBuildDiagnostic CreateFrom(Microsoft.Build.Framework.BuildErrorEventArgs args)
+ => new MSBuildDiagnostic(MSBuildDiagnosticSeverity.Error,
+ args.Message, args.File, args.ProjectFile, args.Subcategory, args.Code,
+ args.LineNumber, args.ColumnNumber, args.EndLineNumber, args.EndColumnNumber);
+
+ public static MSBuildDiagnostic CreateFrom(Microsoft.Build.Framework.BuildWarningEventArgs args)
+ => new MSBuildDiagnostic(MSBuildDiagnosticSeverity.Error,
+ args.Message, args.File, args.ProjectFile, args.Subcategory, args.Code,
+ args.LineNumber, args.ColumnNumber, args.EndLineNumber, args.EndColumnNumber);
+ }
+}
diff --git a/src/OmniSharp.MSBuild/Logging/MSBuildDiagnosticSeverity.cs b/src/OmniSharp.MSBuild/Logging/MSBuildDiagnosticSeverity.cs
new file mode 100644
index 0000000000..ec771e3830
--- /dev/null
+++ b/src/OmniSharp.MSBuild/Logging/MSBuildDiagnosticSeverity.cs
@@ -0,0 +1,8 @@
+namespace OmniSharp.MSBuild.Logging
+{
+ public enum MSBuildDiagnosticSeverity
+ {
+ Error,
+ Warning
+ }
+}
diff --git a/src/OmniSharp.MSBuild/Logging/MSBuildLogger.cs b/src/OmniSharp.MSBuild/Logging/MSBuildLogger.cs
new file mode 100644
index 0000000000..12a652a731
--- /dev/null
+++ b/src/OmniSharp.MSBuild/Logging/MSBuildLogger.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.Extensions.Logging;
+
+namespace OmniSharp.MSBuild.Logging
+{
+ internal class MSBuildLogger : Microsoft.Build.Utilities.Logger
+ {
+ private readonly ILogger _logger;
+ private readonly List _diagnostics;
+
+ public MSBuildLogger(ILogger logger)
+ {
+ _logger = logger;
+ _diagnostics = new List();
+ }
+
+ public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource)
+ {
+ eventSource.ErrorRaised += OnError;
+ eventSource.WarningRaised += OnWarning;
+ }
+
+ public ImmutableArray GetDiagnostics() =>
+ _diagnostics.ToImmutableArray();
+
+ private void OnError(object sender, Microsoft.Build.Framework.BuildErrorEventArgs args)
+ {
+ _logger.LogError(args.Message);
+ _diagnostics.Add(MSBuildDiagnostic.CreateFrom(args));
+ }
+
+ private void OnWarning(object sender, Microsoft.Build.Framework.BuildWarningEventArgs args)
+ {
+ _logger.LogWarning(args.Message);
+ _diagnostics.Add(MSBuildDiagnostic.CreateFrom(args));
+ }
+ }
+}
diff --git a/src/OmniSharp.MSBuild/MSBuildLogForwarder.cs b/src/OmniSharp.MSBuild/MSBuildLogForwarder.cs
deleted file mode 100644
index 39d06afeb2..0000000000
--- a/src/OmniSharp.MSBuild/MSBuildLogForwarder.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.Logging;
-using OmniSharp.MSBuild.Models.Events;
-
-namespace OmniSharp.MSBuild
-{
- public class MSBuildLogForwarder : Microsoft.Build.Framework.ILogger
- {
- private readonly ILogger _logger;
- private readonly ICollection _diagnostics;
- private readonly IList _callOnShutdown;
-
- public MSBuildLogForwarder(ILogger logger, ICollection diagnostics)
- {
- _logger = logger;
- _diagnostics = diagnostics;
- _callOnShutdown = new List();
- }
-
- public string Parameters { get; set; }
-
- public Microsoft.Build.Framework.LoggerVerbosity Verbosity { get; set; }
-
- public void Initialize(Microsoft.Build.Framework.IEventSource eventSource)
- {
- eventSource.ErrorRaised += OnError;
- eventSource.WarningRaised += OnWarning;
- _callOnShutdown.Add(() =>
- {
- eventSource.ErrorRaised -= OnError;
- eventSource.WarningRaised -= OnWarning;
- });
- }
-
- public void Shutdown()
- {
- foreach (var action in _callOnShutdown)
- {
- action();
- }
- }
-
- private void AddDiagnostic(string logLevel, string fileName, string message, int lineNumber, int columnNumber, int endLineNumber, int endColumnNumber)
- {
- if (_diagnostics == null)
- {
- return;
- }
-
- _diagnostics.Add(new MSBuildDiagnosticsMessage()
- {
- LogLevel = logLevel,
- FileName = fileName,
- Text = message,
- StartLine = lineNumber,
- StartColumn = columnNumber,
- EndLine = endLineNumber,
- EndColumn = endColumnNumber
- });
- }
-
- private void OnError(object sender, Microsoft.Build.Framework.BuildErrorEventArgs args)
- {
- _logger.LogError(args.Message);
-
- AddDiagnostic("Error", args.File, args.Message, args.LineNumber, args.ColumnNumber, args.EndLineNumber, args.EndColumnNumber);
- }
-
- private void OnWarning(object sender, Microsoft.Build.Framework.BuildWarningEventArgs args)
- {
- _logger.LogWarning(args.Message);
-
- AddDiagnostic("Warning", args.File, args.Message, args.LineNumber, args.ColumnNumber, args.EndLineNumber, args.EndColumnNumber);
- }
- }
-}
\ No newline at end of file
diff --git a/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs b/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs
deleted file mode 100644
index 6f9ba7e17f..0000000000
--- a/src/OmniSharp.MSBuild/MSBuildProjectSystem.cs
+++ /dev/null
@@ -1,616 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Composition;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using OmniSharp.Eventing;
-using OmniSharp.FileWatching;
-using OmniSharp.Models.Events;
-using OmniSharp.Models.FilesChanged;
-using OmniSharp.Models.UpdateBuffer;
-using OmniSharp.Models.WorkspaceInformation;
-using OmniSharp.MSBuild.Discovery;
-using OmniSharp.MSBuild.Models;
-using OmniSharp.MSBuild.Models.Events;
-using OmniSharp.MSBuild.ProjectFile;
-using OmniSharp.MSBuild.Resolution;
-using OmniSharp.MSBuild.SolutionParsing;
-using OmniSharp.Options;
-using OmniSharp.Services;
-
-namespace OmniSharp.MSBuild
-{
- [Export(typeof(IProjectSystem)), Shared]
- public class MSBuildProjectSystem : IProjectSystem
- {
- private readonly IOmniSharpEnvironment _environment;
- private readonly OmniSharpWorkspace _workspace;
- private readonly MSBuildInstance _msbuildInstance;
- private readonly DotNetCliService _dotNetCli;
- private readonly MetadataFileReferenceCache _metadataFileReferenceCache;
- private readonly IEventEmitter _eventEmitter;
- private readonly IFileSystemWatcher _fileSystemWatcher;
- private readonly ILoggerFactory _loggerFactory;
- private readonly ILogger _logger;
- private readonly PackageDependencyResolver _packageDepedencyResolver;
-
- private readonly object _gate = new object();
- private readonly Queue _projectsToProcess;
- private readonly ProjectFileInfoCollection _projects;
-
- private MSBuildOptions _options;
- private string _solutionFileOrRootPath;
-
- public string Key { get; } = "MsBuild";
- public string Language { get; } = LanguageNames.CSharp;
- public IEnumerable Extensions { get; } = new[] { ".cs" };
-
- [ImportingConstructor]
- public MSBuildProjectSystem(
- IOmniSharpEnvironment environment,
- OmniSharpWorkspace workspace,
- IMSBuildLocator msbuildLocator,
- DotNetCliService dotNetCliService,
- MetadataFileReferenceCache metadataFileReferenceCache,
- IEventEmitter eventEmitter,
- IFileSystemWatcher fileSystemWatcher,
- ILoggerFactory loggerFactory)
- {
- _environment = environment;
- _workspace = workspace;
- _msbuildInstance = msbuildLocator.RegisteredInstance;
- _dotNetCli = dotNetCliService;
- _metadataFileReferenceCache = metadataFileReferenceCache;
- _eventEmitter = eventEmitter;
- _fileSystemWatcher = fileSystemWatcher;
- _loggerFactory = loggerFactory;
-
- _projects = new ProjectFileInfoCollection();
- _projectsToProcess = new Queue();
- _logger = loggerFactory.CreateLogger();
- _packageDepedencyResolver = new PackageDependencyResolver(loggerFactory);
- }
-
- public void Initalize(IConfiguration configuration)
- {
- _options = new MSBuildOptions();
- ConfigurationBinder.Bind(configuration, _options);
-
- if (_environment.LogLevel < LogLevel.Information)
- {
- var buildEnvironmentInfo = MSBuildHelpers.GetBuildEnvironmentInfo();
- _logger.LogDebug($"MSBuild environment: {Environment.NewLine}{buildEnvironmentInfo}");
- }
-
- var initialProjectPaths = GetInitialProjectPaths();
-
- foreach (var projectPath in initialProjectPaths)
- {
- if (!File.Exists(projectPath))
- {
- _logger.LogWarning($"Found project that doesn't exist on disk: {projectPath}");
- continue;
- }
-
- var project = LoadProject(projectPath);
- if (project == null)
- {
- // Diagnostics reported while loading the project have already been logged.
- continue;
- }
-
- _projectsToProcess.Enqueue(project);
- }
-
- ProcessProjects();
- }
-
- private IEnumerable GetInitialProjectPaths()
- {
- // If a solution was provided, use it.
- if (!string.IsNullOrEmpty(_environment.SolutionFilePath))
- {
- _solutionFileOrRootPath = _environment.SolutionFilePath;
- return GetProjectPathsFromSolution(_environment.SolutionFilePath);
- }
-
- // Otherwise, assume that the path provided is a directory and look for a solution there.
- var solutionFilePath = FindSolutionFilePath(_environment.TargetDirectory, _logger);
- if (!string.IsNullOrEmpty(solutionFilePath))
- {
- _solutionFileOrRootPath = solutionFilePath;
- return GetProjectPathsFromSolution(solutionFilePath);
- }
-
- // Finally, if there isn't a single solution immediately available,
- // Just process all of the projects beneath the root path.
- _solutionFileOrRootPath = _environment.TargetDirectory;
- return Directory.GetFiles(_environment.TargetDirectory, "*.csproj", SearchOption.AllDirectories);
- }
-
- private IEnumerable GetProjectPathsFromSolution(string solutionFilePath)
- {
- _logger.LogInformation($"Detecting projects in '{solutionFilePath}'.");
-
- var solutionFile = SolutionFile.ParseFile(solutionFilePath);
- var processedProjects = new HashSet(StringComparer.OrdinalIgnoreCase);
- var result = new List();
-
- foreach (var project in solutionFile.Projects)
- {
- if (project.IsSolutionFolder)
- {
- continue;
- }
-
- // Solution files are assumed to contain relative paths to project files with Windows-style slashes.
- var projectFilePath = project.RelativePath.Replace('\\', Path.DirectorySeparatorChar);
- projectFilePath = Path.Combine(_environment.TargetDirectory, projectFilePath);
- projectFilePath = Path.GetFullPath(projectFilePath);
-
- // Have we seen this project? If so, move on.
- if (processedProjects.Contains(projectFilePath))
- {
- continue;
- }
-
- if (string.Equals(Path.GetExtension(projectFilePath), ".csproj", StringComparison.OrdinalIgnoreCase))
- {
- result.Add(projectFilePath);
- }
-
- processedProjects.Add(projectFilePath);
- }
-
- return result;
- }
-
- private void ProcessProjects()
- {
- while (_projectsToProcess.Count > 0)
- {
- var newProjects = new List();
-
- while (_projectsToProcess.Count > 0)
- {
- var project = _projectsToProcess.Dequeue();
-
- if (!_projects.ContainsKey(project.FilePath))
- {
- AddProject(project);
- }
- else
- {
- _projects[project.FilePath] = project;
- }
-
- newProjects.Add(project);
- }
-
- // Next, update all projects.
- foreach (var project in newProjects)
- {
- UpdateProject(project);
- }
-
- // Finally, check for any unresolved dependencies in the projects we just processes.
- foreach (var project in newProjects)
- {
- CheckForUnresolvedDependences(project, allowAutoRestore: true);
- }
- }
- }
-
- private void AddProject(ProjectFileInfo project)
- {
- _projects.Add(project);
-
- var compilationOptions = CreateCompilationOptions(project);
-
- var projectInfo = ProjectInfo.Create(
- id: project.Id,
- version: VersionStamp.Create(),
- name: project.Name,
- assemblyName: project.AssemblyName,
- language: LanguageNames.CSharp,
- filePath: project.FilePath,
- outputFilePath: project.TargetPath,
- compilationOptions: compilationOptions);
-
- _workspace.AddProject(projectInfo);
-
- WatchProject(project);
- }
-
- private void WatchProject(ProjectFileInfo project)
- {
- // TODO: This needs some improvement. Currently, it tracks both deletions and changes
- // as "updates". We should properly remove projects that are deleted.
- _fileSystemWatcher.Watch(project.FilePath, (file, changeType) =>
- {
- OnProjectChanged(project.FilePath, allowAutoRestore: true);
- });
-
- if (!string.IsNullOrEmpty(project.ProjectAssetsFile))
- {
- _fileSystemWatcher.Watch(project.ProjectAssetsFile, (file, changeType) =>
- {
- OnProjectChanged(project.FilePath, allowAutoRestore: false);
- });
- }
- }
-
- private static CSharpCompilationOptions CreateCompilationOptions(ProjectFileInfo projectFileInfo)
- {
- var result = new CSharpCompilationOptions(projectFileInfo.OutputKind);
-
- result = result.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);
-
- if (projectFileInfo.AllowUnsafeCode)
- {
- result = result.WithAllowUnsafe(true);
- }
-
- var specificDiagnosticOptions = new Dictionary(projectFileInfo.SuppressedDiagnosticIds.Count)
- {
- // Ensure that specific warnings about assembly references are always suppressed.
- { "CS1701", ReportDiagnostic.Suppress },
- { "CS1702", ReportDiagnostic.Suppress },
- { "CS1705", ReportDiagnostic.Suppress }
- };
-
- if (projectFileInfo.SuppressedDiagnosticIds.Any())
- {
- foreach (var id in projectFileInfo.SuppressedDiagnosticIds)
- {
- if (!specificDiagnosticOptions.ContainsKey(id))
- {
- specificDiagnosticOptions.Add(id, ReportDiagnostic.Suppress);
- }
- }
- }
-
- result = result.WithSpecificDiagnosticOptions(specificDiagnosticOptions);
-
- if (projectFileInfo.SignAssembly && !string.IsNullOrEmpty(projectFileInfo.AssemblyOriginatorKeyFile))
- {
- var keyFile = Path.Combine(projectFileInfo.Directory, projectFileInfo.AssemblyOriginatorKeyFile);
- result = result.WithStrongNameProvider(new DesktopStrongNameProvider())
- .WithCryptoKeyFile(keyFile);
- }
-
- if (!string.IsNullOrWhiteSpace(projectFileInfo.DocumentationFile))
- {
- result = result.WithXmlReferenceResolver(XmlFileResolver.Default);
- }
-
- return result;
- }
-
- private static string FindSolutionFilePath(string rootPath, ILogger logger)
- {
- var solutionsFilePaths = Directory.GetFiles(rootPath, "*.sln");
- var result = SolutionSelector.Pick(solutionsFilePaths, rootPath);
-
- if (result.Message != null)
- {
- logger.LogInformation(result.Message);
- }
-
- return result.FilePath;
- }
-
- private ProjectFileInfo LoadProject(string projectFilePath)
- {
- _logger.LogInformation($"Loading project: {projectFilePath}");
-
- ProjectFileInfo project;
- var diagnostics = new List();
-
- try
- {
- project = ProjectFileInfo.Create(projectFilePath, _environment.TargetDirectory, _loggerFactory.CreateLogger(), _msbuildInstance, _options, diagnostics);
-
- if (project == null)
- {
- _logger.LogWarning($"Failed to load project file '{projectFilePath}'.");
- }
- }
- catch (Exception ex)
- {
- _logger.LogWarning($"Failed to load project file '{projectFilePath}'.", ex);
- _eventEmitter.Error(ex, fileName: projectFilePath);
- project = null;
- }
-
- _eventEmitter.MSBuildProjectDiagnostics(projectFilePath, diagnostics);
-
- return project;
- }
-
- private void OnProjectChanged(string projectFilePath, bool allowAutoRestore)
- {
- lock (_gate)
- {
- if (_projects.TryGetValue(projectFilePath, out var oldProjectFileInfo))
- {
- var diagnostics = new List();
- var newProjectFileInfo = oldProjectFileInfo.Reload(_environment.TargetDirectory, _loggerFactory.CreateLogger(), _msbuildInstance, _options, diagnostics);
-
- if (newProjectFileInfo != null)
- {
- _projects[projectFilePath] = newProjectFileInfo;
-
- UpdateProject(newProjectFileInfo);
- CheckForUnresolvedDependences(newProjectFileInfo, allowAutoRestore);
- }
- }
-
- ProcessProjects();
- }
- }
-
- private void UpdateProject(ProjectFileInfo projectFileInfo)
- {
- var project = _workspace.CurrentSolution.GetProject(projectFileInfo.Id);
- if (project == null)
- {
- _logger.LogError($"Could not locate project in workspace: {projectFileInfo.FilePath}");
- return;
- }
-
- UpdateSourceFiles(project, projectFileInfo.SourceFiles);
- UpdateParseOptions(project, projectFileInfo.LanguageVersion, projectFileInfo.PreprocessorSymbolNames, !string.IsNullOrWhiteSpace(projectFileInfo.DocumentationFile));
- UpdateProjectReferences(project, projectFileInfo.ProjectReferences);
- UpdateReferences(project, projectFileInfo.References);
- }
-
- private void UpdateSourceFiles(Project project, IList sourceFiles)
- {
- var currentDocuments = project.Documents.ToDictionary(d => d.FilePath, d => d.Id);
-
- // Add source files to the project.
- foreach (var sourceFile in sourceFiles)
- {
- WatchDirectoryContainingFile(sourceFile);
-
- // If a document for this source file already exists in the project, carry on.
- if (currentDocuments.Remove(sourceFile))
- {
- continue;
- }
-
- // If the source file doesn't exist on disk, don't try to add it.
- if (!File.Exists(sourceFile))
- {
- continue;
- }
-
- _workspace.AddDocument(project.Id, sourceFile);
- }
-
- // Removing any remaining documents from the project.
- foreach (var currentDocument in currentDocuments)
- {
- _workspace.RemoveDocument(currentDocument.Value);
- }
- }
-
- private void WatchDirectoryContainingFile(string sourceFile) => _fileSystemWatcher.WatchDirectory(Path.GetDirectoryName(sourceFile), OnDirectoryFileChanged);
-
- private void OnDirectoryFileChanged(string path, FileChangeType changeType)
- {
- // Hosts may not have passed through a file change type
- if (changeType == FileChangeType.Unspecified && !File.Exists(path) || changeType == FileChangeType.Delete)
- {
- foreach (var documentId in _workspace.CurrentSolution.GetDocumentIdsWithFilePath(path))
- {
- _workspace.RemoveDocument(documentId);
- }
- }
-
- if (changeType == FileChangeType.Unspecified || changeType == FileChangeType.Create)
- {
- // Only add cs files. Also, make sure the path is a file, and not a directory name that happens to end in ".cs"
- if (string.Equals(Path.GetExtension(path), ".cs", StringComparison.CurrentCultureIgnoreCase) && File.Exists(path))
- {
- // Use the buffer manager to add the new file to the appropriate projects
- // Hosts that don't pass the FileChangeType may wind up updating the buffer twice
- _workspace.BufferManager.UpdateBufferAsync(new UpdateBufferRequest() { FileName = path, FromDisk = true }).Wait();
- }
- }
- }
-
- private void UpdateParseOptions(Project project, LanguageVersion languageVersion, IEnumerable preprocessorSymbolNames, bool generateXmlDocumentation)
- {
- var existingParseOptions = (CSharpParseOptions)project.ParseOptions;
-
- if (existingParseOptions.LanguageVersion == languageVersion &&
- Enumerable.SequenceEqual(existingParseOptions.PreprocessorSymbolNames, preprocessorSymbolNames) &&
- (existingParseOptions.DocumentationMode == DocumentationMode.Diagnose) == generateXmlDocumentation)
- {
- // No changes to make. Moving on.
- return;
- }
-
- var parseOptions = new CSharpParseOptions(languageVersion);
-
- if (preprocessorSymbolNames.Any())
- {
- parseOptions = parseOptions.WithPreprocessorSymbols(preprocessorSymbolNames);
- }
-
- if (generateXmlDocumentation)
- {
- parseOptions = parseOptions.WithDocumentationMode(DocumentationMode.Diagnose);
- }
-
- _workspace.SetParseOptions(project.Id, parseOptions);
- }
-
- private void UpdateProjectReferences(Project project, ImmutableArray projectReferencePaths)
- {
- _logger.LogInformation($"Update project: {project.Name}");
-
- var existingProjectReferences = new HashSet(project.ProjectReferences);
- var addedProjectReferences = new HashSet();
-
- foreach (var projectReferencePath in projectReferencePaths)
- {
- if (!_projects.TryGetValue(projectReferencePath, out var referencedProject))
- {
- if (File.Exists(projectReferencePath) &&
- projectReferencePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogInformation($"Found referenced project outside root directory: {projectReferencePath}");
-
- // We've found a project reference that we didn't know about already, but it exists on disk.
- // This is likely a project that is outside of OmniSharp's TargetDirectory.
- referencedProject = LoadProject(projectReferencePath);
-
- if (referencedProject != null)
- {
- AddProject(referencedProject);
-
- // Ensure this project is queued to be updated later.
- _projectsToProcess.Enqueue(referencedProject);
- }
- }
- }
-
- if (referencedProject == null)
- {
- _logger.LogWarning($"Unable to resolve project reference '{projectReferencePath}' for '{project.Name}'.");
- continue;
- }
-
- var projectReference = new ProjectReference(referencedProject.Id);
-
- if (existingProjectReferences.Remove(projectReference))
- {
- // This reference already exists
- continue;
- }
-
- if (!addedProjectReferences.Contains(projectReference))
- {
- _workspace.AddProjectReference(project.Id, projectReference);
- addedProjectReferences.Add(projectReference);
- }
- }
-
- foreach (var existingProjectReference in existingProjectReferences)
- {
- _workspace.RemoveProjectReference(project.Id, existingProjectReference);
- }
- }
-
- private void UpdateReferences(Project project, ImmutableArray references)
- {
- var existingReferences = new HashSet(project.MetadataReferences);
- var addedReferences = new HashSet();
-
- foreach (var referencePath in references)
- {
- if (!File.Exists(referencePath))
- {
- _logger.LogWarning($"Unable to resolve assembly '{referencePath}'");
- }
- else
- {
- var metadataReference = _metadataFileReferenceCache.GetMetadataReference(referencePath);
-
- if (existingReferences.Remove(metadataReference))
- {
- continue;
- }
-
- if (!addedReferences.Contains(metadataReference))
- {
- _logger.LogDebug($"Adding reference '{referencePath}' to '{project.Name}'.");
- _workspace.AddMetadataReference(project.Id, metadataReference);
- addedReferences.Add(metadataReference);
- }
- }
- }
-
- foreach (var existingReference in existingReferences)
- {
- _workspace.RemoveMetadataReference(project.Id, existingReference);
- }
- }
-
- private void CheckForUnresolvedDependences(ProjectFileInfo projectFile, bool allowAutoRestore)
- {
- var unresolvedPackageReferences = _packageDepedencyResolver.FindUnresolvedPackageReferences(projectFile);
- if (unresolvedPackageReferences.IsEmpty)
- {
- return;
- }
-
- var unresolvedDependencies = unresolvedPackageReferences.Select(packageReference =>
- new PackageDependency
- {
- Name = packageReference.Dependency.Id,
- Version = packageReference.Dependency.VersionRange.ToNormalizedString()
- });
-
- if (allowAutoRestore && _options.EnablePackageAutoRestore)
- {
- _dotNetCli.RestoreAsync(projectFile.Directory, onFailure: () =>
- {
- _eventEmitter.UnresolvedDepdendencies(projectFile.FilePath, unresolvedDependencies);
- });
- }
- else
- {
- _eventEmitter.UnresolvedDepdendencies(projectFile.FilePath, unresolvedDependencies);
- }
- }
-
- private ProjectFileInfo GetProjectFileInfo(string path)
- {
- if (!_projects.TryGetValue(path, out var projectFileInfo))
- {
- return null;
- }
-
- return projectFileInfo;
- }
-
- Task