Merge pull request #4152 from livarcocc/bring_back_project_model_server
Adding dotnet-projectmodel-server back
This commit is contained in:
commit
5035746370
32 changed files with 1984 additions and 1 deletions
|
@ -10,6 +10,7 @@ using System.Text;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using Microsoft.DotNet.Configurer;
|
using Microsoft.DotNet.Configurer;
|
||||||
using Microsoft.DotNet.PlatformAbstractions;
|
using Microsoft.DotNet.PlatformAbstractions;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server;
|
||||||
using Microsoft.DotNet.Tools.Build;
|
using Microsoft.DotNet.Tools.Build;
|
||||||
using Microsoft.DotNet.Tools.Compiler;
|
using Microsoft.DotNet.Tools.Compiler;
|
||||||
using Microsoft.DotNet.Tools.Compiler.Csc;
|
using Microsoft.DotNet.Tools.Compiler.Csc;
|
||||||
|
@ -45,7 +46,8 @@ namespace Microsoft.DotNet.Cli
|
||||||
["run3"] = Run3Command.Run,
|
["run3"] = Run3Command.Run,
|
||||||
["restore3"] = Restore3Command.Run,
|
["restore3"] = Restore3Command.Run,
|
||||||
["pack3"] = Pack3Command.Run,
|
["pack3"] = Pack3Command.Run,
|
||||||
["migrate"] = MigrateCommand.Run
|
["migrate"] = MigrateCommand.Run,
|
||||||
|
["projectmodel-server"] = ProjectModelServerCommand.Run,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
internal class ConnectionContext
|
||||||
|
{
|
||||||
|
private readonly string _hostName;
|
||||||
|
private readonly ProcessingQueue _queue;
|
||||||
|
private readonly IDictionary<int, ProjectManager> _projects;
|
||||||
|
|
||||||
|
public ConnectionContext(Socket acceptedSocket,
|
||||||
|
string hostName,
|
||||||
|
ProtocolManager protocolManager,
|
||||||
|
DesignTimeWorkspace workspaceContext,
|
||||||
|
IDictionary<int, ProjectManager> projects)
|
||||||
|
{
|
||||||
|
_hostName = hostName;
|
||||||
|
_projects = projects;
|
||||||
|
|
||||||
|
_queue = new ProcessingQueue(new NetworkStream(acceptedSocket));
|
||||||
|
_queue.OnReceive += message =>
|
||||||
|
{
|
||||||
|
if (protocolManager.IsProtocolNegotiation(message))
|
||||||
|
{
|
||||||
|
message.Sender = this;
|
||||||
|
protocolManager.Negotiate(message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message.Sender = this;
|
||||||
|
ProjectManager projectManager;
|
||||||
|
if (!_projects.TryGetValue(message.ContextId, out projectManager))
|
||||||
|
{
|
||||||
|
projectManager = new ProjectManager(message.ContextId,
|
||||||
|
workspaceContext,
|
||||||
|
protocolManager);
|
||||||
|
|
||||||
|
_projects[message.ContextId] = projectManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
projectManager.OnReceive(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueStart()
|
||||||
|
{
|
||||||
|
_queue.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Transmit(Message message)
|
||||||
|
{
|
||||||
|
message.HostId = _hostName;
|
||||||
|
return _queue.Send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Graph;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Helpers
|
||||||
|
{
|
||||||
|
internal class DependencyTypeChangeFinder
|
||||||
|
{
|
||||||
|
public static IEnumerable<DiagnosticMessage> Diagnose(
|
||||||
|
ProjectContext context,
|
||||||
|
IEnumerable<string> previousSearchPaths)
|
||||||
|
{
|
||||||
|
var result = new List<DiagnosticMessage>();
|
||||||
|
var project = context.ProjectFile;
|
||||||
|
var libraries = context.LibraryManager.GetLibraries();
|
||||||
|
|
||||||
|
var updatedSearchPath = GetUpdatedSearchPaths(previousSearchPaths, project.ResolveSearchPaths());
|
||||||
|
var projectCandiates = GetProjectCandidates(updatedSearchPath);
|
||||||
|
var rootDependencies = libraries.FirstOrDefault(library => string.Equals(library.Identity.Name, project.Name))
|
||||||
|
?.Dependencies
|
||||||
|
?.ToDictionary(libraryRange => libraryRange.Name);
|
||||||
|
|
||||||
|
foreach (var library in libraries)
|
||||||
|
{
|
||||||
|
var diagnostic = Validate(library, projectCandiates, rootDependencies);
|
||||||
|
if (diagnostic != null)
|
||||||
|
{
|
||||||
|
result.Add(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DiagnosticMessage Validate(LibraryDescription library,
|
||||||
|
HashSet<string> projectCandidates,
|
||||||
|
Dictionary<string, LibraryRange> rootDependencies)
|
||||||
|
{
|
||||||
|
if (!library.Resolved || projectCandidates == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundCandidate = projectCandidates.Contains(library.Identity.Name);
|
||||||
|
|
||||||
|
if ((library.Identity.Type == LibraryType.Project && !foundCandidate) ||
|
||||||
|
(library.Identity.Type == LibraryType.Package && foundCandidate))
|
||||||
|
{
|
||||||
|
library.Resolved = false;
|
||||||
|
|
||||||
|
var libraryRange = rootDependencies[library.Identity.Name];
|
||||||
|
|
||||||
|
return new DiagnosticMessage(
|
||||||
|
ErrorCodes.NU1010,
|
||||||
|
$"The type of dependency {library.Identity.Name} was changed.",
|
||||||
|
libraryRange.SourceFilePath,
|
||||||
|
DiagnosticMessageSeverity.Error,
|
||||||
|
libraryRange.SourceLine,
|
||||||
|
libraryRange.SourceColumn,
|
||||||
|
library);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string> GetProjectCandidates(IEnumerable<string> searchPaths)
|
||||||
|
{
|
||||||
|
if (searchPaths == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HashSet<string>(searchPaths.Where(path => Directory.Exists(path))
|
||||||
|
.SelectMany(path => Directory.GetDirectories(path))
|
||||||
|
.Where(path => File.Exists(Path.Combine(path, Project.FileName)))
|
||||||
|
.Select(path => Path.GetFileName(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the search paths if they're updated. Otherwise returns null.
|
||||||
|
/// </summary>
|
||||||
|
private static IEnumerable<string> GetUpdatedSearchPaths(IEnumerable<string> oldSearchPaths,
|
||||||
|
IEnumerable<string> newSearchPaths)
|
||||||
|
{
|
||||||
|
// The oldSearchPaths is null when the current project is not initialized. It is not necessary to
|
||||||
|
// validate the dependency in this case.
|
||||||
|
if (oldSearchPaths == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Enumerable.SequenceEqual(oldSearchPaths, newSearchPaths))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSearchPaths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Helpers
|
||||||
|
{
|
||||||
|
public static class JTokenExtensions
|
||||||
|
{
|
||||||
|
public static string GetValue(this JToken token, string name)
|
||||||
|
{
|
||||||
|
return GetValue<string>(token, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TVal GetValue<TVal>(this JToken token, string name)
|
||||||
|
{
|
||||||
|
var value = token?[name];
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
return value.Value<TVal>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(TVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using Microsoft.DotNet.ProjectModel.Graph;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Helpers
|
||||||
|
{
|
||||||
|
public static class LibraryExtensions
|
||||||
|
{
|
||||||
|
public static string GetUniqueName(this LibraryDescription library)
|
||||||
|
{
|
||||||
|
var identity = library.Identity;
|
||||||
|
return identity.Type != LibraryType.ReferenceAssembly ? identity.Name : $"fx/{identity.Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetUniqueName(this LibraryRange range)
|
||||||
|
{
|
||||||
|
return range.Target != LibraryType.ReferenceAssembly ? range.Name : $"fx/{range.Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using Microsoft.DotNet.ProjectModel.Resolution;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public static class NuGetFrameworkExtensions
|
||||||
|
{
|
||||||
|
public static FrameworkData ToPayload(this NuGetFramework framework)
|
||||||
|
{
|
||||||
|
return new FrameworkData
|
||||||
|
{
|
||||||
|
ShortName = framework.GetShortFolderName(),
|
||||||
|
FrameworkName = framework.DotNetFrameworkName,
|
||||||
|
FriendlyName = FrameworkReferenceResolver.Default.GetFriendlyFrameworkName(framework),
|
||||||
|
RedistListPath = FrameworkReferenceResolver.Default.GetFrameworkRedistListPath(framework)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Helpers
|
||||||
|
{
|
||||||
|
public static class ProjectExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<string> ResolveSearchPaths(this Project project)
|
||||||
|
{
|
||||||
|
GlobalSettings settings;
|
||||||
|
return project.ResolveSearchPaths(out settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> ResolveSearchPaths(this Project project, out GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
if (project == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchPaths = new HashSet<string> { Directory.GetParent(project.ProjectDirectory).FullName };
|
||||||
|
|
||||||
|
globalSettings = project.ResolveGlobalSettings();
|
||||||
|
if (globalSettings != null)
|
||||||
|
{
|
||||||
|
foreach (var searchPath in globalSettings.ProjectSearchPaths)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(globalSettings.DirectoryPath, searchPath);
|
||||||
|
searchPaths.Add(Path.GetFullPath(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GlobalSettings ResolveGlobalSettings(this Project project)
|
||||||
|
{
|
||||||
|
if (project == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalSettings settings;
|
||||||
|
var root = ProjectRootResolver.ResolveRootDirectory(project.ProjectDirectory);
|
||||||
|
if (GlobalSettings.TryGetGlobalSettings(root, out settings))
|
||||||
|
{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.Cli.Compiler.Common;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Files;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Graph;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Helpers;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
internal class ProjectContextSnapshot
|
||||||
|
{
|
||||||
|
public string RootDependency { get; set; }
|
||||||
|
public NuGetFramework TargetFramework { get; set; }
|
||||||
|
public IReadOnlyList<string> SourceFiles { get; set; }
|
||||||
|
public CommonCompilerOptions CompilerOptions { get; set; }
|
||||||
|
public IReadOnlyList<ProjectReferenceDescription> ProjectReferences { get; set; }
|
||||||
|
public IReadOnlyList<string> FileReferences { get; set; }
|
||||||
|
public IReadOnlyList<DiagnosticMessage> DependencyDiagnostics { get; set; }
|
||||||
|
public IDictionary<string, DependencyDescription> Dependencies { get; set; }
|
||||||
|
|
||||||
|
public static ProjectContextSnapshot Create(ProjectContext context, string configuration, IEnumerable<string> previousSearchPaths)
|
||||||
|
{
|
||||||
|
var snapshot = new ProjectContextSnapshot();
|
||||||
|
|
||||||
|
var allDependencyDiagnostics = new List<DiagnosticMessage>();
|
||||||
|
allDependencyDiagnostics.AddRange(context.LibraryManager.GetAllDiagnostics());
|
||||||
|
allDependencyDiagnostics.AddRange(DependencyTypeChangeFinder.Diagnose(context, previousSearchPaths));
|
||||||
|
|
||||||
|
var diagnosticsLookup = allDependencyDiagnostics.ToLookup(d => d.Source);
|
||||||
|
|
||||||
|
var allExports = context.CreateExporter(configuration)
|
||||||
|
.GetAllExports()
|
||||||
|
.ToDictionary(export => export.Library.Identity.Name);
|
||||||
|
|
||||||
|
var allSourceFiles = new List<string>(GetSourceFiles(context, configuration));
|
||||||
|
var allFileReferences = new List<string>();
|
||||||
|
var allProjectReferences = new List<ProjectReferenceDescription>();
|
||||||
|
var allDependencies = new Dictionary<string, DependencyDescription>();
|
||||||
|
|
||||||
|
// All exports are returned. When the same library name have a ReferenceAssembly type export and a Package type export
|
||||||
|
// both will be listed as dependencies. Prefix "fx/" will be added to ReferenceAssembly type dependency.
|
||||||
|
foreach (var export in allExports.Values)
|
||||||
|
{
|
||||||
|
allSourceFiles.AddRange(export.SourceReferences.Select(f => f.ResolvedPath));
|
||||||
|
var diagnostics = diagnosticsLookup[export.Library].ToList();
|
||||||
|
var description = DependencyDescription.Create(export.Library, diagnostics, allExports);
|
||||||
|
allDependencies[description.Name] = description;
|
||||||
|
|
||||||
|
var projectReferene = ProjectReferenceDescription.Create(export.Library);
|
||||||
|
if (projectReferene != null && export.Library.Identity.Name != context.ProjectFile.Name)
|
||||||
|
{
|
||||||
|
allProjectReferences.Add(projectReferene);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (export.Library.Identity.Type != LibraryType.Project)
|
||||||
|
{
|
||||||
|
allFileReferences.AddRange(export.CompilationAssemblies.Select(asset => asset.ResolvedPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.RootDependency = context.ProjectFile.Name;
|
||||||
|
snapshot.TargetFramework = context.TargetFramework;
|
||||||
|
snapshot.SourceFiles = allSourceFiles.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(path => path).ToList();
|
||||||
|
snapshot.CompilerOptions = context.GetLanguageSpecificCompilerOptions(context.TargetFramework, configuration);
|
||||||
|
snapshot.ProjectReferences = allProjectReferences.OrderBy(reference => reference.Name).ToList();
|
||||||
|
snapshot.FileReferences = allFileReferences.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(path => path).ToList();
|
||||||
|
snapshot.DependencyDiagnostics = allDependencyDiagnostics;
|
||||||
|
snapshot.Dependencies = allDependencies;
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetSourceFiles(ProjectContext context, string configuration)
|
||||||
|
{
|
||||||
|
var compilerOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration);
|
||||||
|
|
||||||
|
if (compilerOptions.CompileInclude == null)
|
||||||
|
{
|
||||||
|
return context.ProjectFile.Files.SourceFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null);
|
||||||
|
|
||||||
|
return includeFiles.Select(f => f.SourcePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Helpers;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
internal class ProjectSnapshot
|
||||||
|
{
|
||||||
|
public Project Project { get; set; }
|
||||||
|
public string GlobalJsonPath { get; set; }
|
||||||
|
public IReadOnlyList<string> ProjectSearchPaths { get; set; }
|
||||||
|
public IReadOnlyList<DiagnosticMessage> ProjectDiagnostics { get; set; }
|
||||||
|
public ErrorMessage GlobalErrorMessage { get; set; }
|
||||||
|
public Dictionary<NuGetFramework, ProjectContextSnapshot> ProjectContexts { get; } = new Dictionary<NuGetFramework, ProjectContextSnapshot>();
|
||||||
|
|
||||||
|
public static ProjectSnapshot Create(string projectDirectory,
|
||||||
|
string configuration,
|
||||||
|
DesignTimeWorkspace workspaceContext,
|
||||||
|
IReadOnlyList<string> previousSearchPaths,
|
||||||
|
bool clearWorkspaceContextCache)
|
||||||
|
{
|
||||||
|
var projectContextsCollection = workspaceContext.GetProjectContextCollection(projectDirectory, clearWorkspaceContextCache);
|
||||||
|
if (!projectContextsCollection.ProjectContexts.Any())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Unable to find project.json in '{projectDirectory}'");
|
||||||
|
}
|
||||||
|
GlobalSettings globalSettings;
|
||||||
|
var currentSearchPaths = projectContextsCollection.Project.ResolveSearchPaths(out globalSettings);
|
||||||
|
|
||||||
|
var snapshot = new ProjectSnapshot();
|
||||||
|
snapshot.Project = projectContextsCollection.Project;
|
||||||
|
snapshot.ProjectDiagnostics = new List<DiagnosticMessage>(projectContextsCollection.ProjectDiagnostics);
|
||||||
|
snapshot.ProjectSearchPaths = currentSearchPaths.ToList();
|
||||||
|
snapshot.GlobalJsonPath = globalSettings?.FilePath;
|
||||||
|
|
||||||
|
foreach (var projectContext in projectContextsCollection.FrameworkOnlyContexts)
|
||||||
|
{
|
||||||
|
snapshot.ProjectContexts[projectContext.TargetFramework] =
|
||||||
|
ProjectContextSnapshot.Create(projectContext, configuration, previousSearchPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
public class MessageTypes
|
||||||
|
{
|
||||||
|
// Incoming
|
||||||
|
public const string Initialize = nameof(Initialize);
|
||||||
|
public const string ChangeConfiguration = nameof(ChangeConfiguration);
|
||||||
|
public const string RefreshDependencies = nameof(RefreshDependencies);
|
||||||
|
public const string RestoreComplete = nameof(RestoreComplete);
|
||||||
|
public const string FilesChanged = nameof(FilesChanged);
|
||||||
|
public const string GetDiagnostics = nameof(GetDiagnostics);
|
||||||
|
public const string ProtocolVersion = nameof(ProtocolVersion);
|
||||||
|
|
||||||
|
// Outgoing
|
||||||
|
public const string Error = nameof(Error);
|
||||||
|
public const string ProjectInformation = nameof(ProjectInformation);
|
||||||
|
public const string Diagnostics = nameof(Diagnostics);
|
||||||
|
public const string DependencyDiagnostics = nameof(DependencyDiagnostics);
|
||||||
|
public const string Dependencies = nameof(Dependencies);
|
||||||
|
public const string CompilerOptions = nameof(CompilerOptions);
|
||||||
|
public const string References = nameof(References);
|
||||||
|
public const string Sources = nameof(Sources);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class CompilerOptionsMessenger : Messenger<ProjectContextSnapshot>
|
||||||
|
{
|
||||||
|
public CompilerOptionsMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.CompilerOptions, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.CompilerOptions != null &&
|
||||||
|
Equals(local.CompilerOptions, remote.CompilerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectContextSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new CompilationOptionsMessage
|
||||||
|
{
|
||||||
|
Framework = local.TargetFramework.ToPayload(),
|
||||||
|
Options = local.CompilerOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.CompilerOptions = local.CompilerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CompilationOptionsMessage
|
||||||
|
{
|
||||||
|
public FrameworkData Framework { get; set; }
|
||||||
|
|
||||||
|
public CommonCompilerOptions Options { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class DependenciesMessenger : Messenger<ProjectContextSnapshot>
|
||||||
|
{
|
||||||
|
public DependenciesMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.Dependencies, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.Dependencies != null &&
|
||||||
|
string.Equals(local.RootDependency, remote.RootDependency) &&
|
||||||
|
Equals(local.TargetFramework, remote.TargetFramework) &&
|
||||||
|
Enumerable.SequenceEqual(local.Dependencies, remote.Dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectContextSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new DependenciesMessage
|
||||||
|
{
|
||||||
|
Framework = local.TargetFramework.ToPayload(),
|
||||||
|
RootDependency = local.RootDependency,
|
||||||
|
Dependencies = local.Dependencies
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.Dependencies = local.Dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DependenciesMessage
|
||||||
|
{
|
||||||
|
public FrameworkData Framework { get; set; }
|
||||||
|
public string RootDependency { get; set; }
|
||||||
|
public IDictionary<string, DependencyDescription> Dependencies { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class DependencyDiagnosticsMessenger : Messenger<ProjectContextSnapshot>
|
||||||
|
{
|
||||||
|
public DependencyDiagnosticsMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.DependencyDiagnostics, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.DependencyDiagnostics != null &&
|
||||||
|
Enumerable.SequenceEqual(local.DependencyDiagnostics, remote.DependencyDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectContextSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new DiagnosticsListMessage(
|
||||||
|
local.DependencyDiagnostics,
|
||||||
|
local.TargetFramework?.ToPayload()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.DependencyDiagnostics = local.DependencyDiagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class GlobalErrorMessenger : Messenger<ProjectSnapshot>
|
||||||
|
{
|
||||||
|
public GlobalErrorMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.Error, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote != null && Equals(local.GlobalErrorMessage, remote.GlobalErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
if (local.GlobalErrorMessage != null)
|
||||||
|
{
|
||||||
|
send(local.GlobalErrorMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
send(new ErrorMessage
|
||||||
|
{
|
||||||
|
Message = null,
|
||||||
|
Path = null,
|
||||||
|
Line = -1,
|
||||||
|
Column = -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.GlobalErrorMessage = local.GlobalErrorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal abstract class Messenger<T> where T : class
|
||||||
|
{
|
||||||
|
protected readonly Action<string, object> _transmit;
|
||||||
|
|
||||||
|
public Messenger(string messageType, Action<string, object> transmit)
|
||||||
|
{
|
||||||
|
_transmit = transmit;
|
||||||
|
|
||||||
|
MessageType = messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MessageType { get; }
|
||||||
|
|
||||||
|
public void UpdateRemote(T local, T remote)
|
||||||
|
{
|
||||||
|
if (!CheckDifference(local, remote))
|
||||||
|
{
|
||||||
|
SendPayload(local, payload => _transmit(MessageType, payload));
|
||||||
|
SetValue(local, remote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void SetValue(T local, T remote);
|
||||||
|
protected abstract void SendPayload(T local, Action<object> send);
|
||||||
|
protected abstract bool CheckDifference(T local, T remote);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class ProjectDiagnosticsMessenger : Messenger<ProjectSnapshot>
|
||||||
|
{
|
||||||
|
public ProjectDiagnosticsMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.Diagnostics, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.ProjectDiagnostics != null &&
|
||||||
|
Enumerable.SequenceEqual(local.ProjectDiagnostics, remote.ProjectDiagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new DiagnosticsListMessage(local.ProjectDiagnostics));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.ProjectDiagnostics = local.ProjectDiagnostics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class ProjectInformationMessenger : Messenger<ProjectSnapshot>
|
||||||
|
{
|
||||||
|
public ProjectInformationMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.ProjectInformation, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.Project != null &&
|
||||||
|
string.Equals(local.Project.Name, remote.Project.Name) &&
|
||||||
|
string.Equals(local.GlobalJsonPath, remote.GlobalJsonPath) &&
|
||||||
|
Enumerable.SequenceEqual(local.Project.GetTargetFrameworks().Select(f => f.FrameworkName),
|
||||||
|
remote.Project.GetTargetFrameworks().Select(f => f.FrameworkName)) &&
|
||||||
|
Enumerable.SequenceEqual(local.Project.GetConfigurations(), remote.Project.GetConfigurations()) &&
|
||||||
|
Enumerable.SequenceEqual(local.Project.Commands, remote.Project.Commands) &&
|
||||||
|
Enumerable.SequenceEqual(local.ProjectSearchPaths, remote.ProjectSearchPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new ProjectInformationMessage(local.Project, local.GlobalJsonPath, local.ProjectSearchPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.Project = local.Project;
|
||||||
|
remote.GlobalJsonPath = local.GlobalJsonPath;
|
||||||
|
remote.ProjectSearchPaths = local.ProjectSearchPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProjectInformationMessage
|
||||||
|
{
|
||||||
|
public ProjectInformationMessage(Project project,
|
||||||
|
string gloablJsonPath,
|
||||||
|
IReadOnlyList<string> projectSearchPaths)
|
||||||
|
{
|
||||||
|
Name = project.Name;
|
||||||
|
Frameworks = project.GetTargetFrameworks().Select(f => f.FrameworkName.ToPayload()).ToList();
|
||||||
|
Configurations = project.GetConfigurations().ToList();
|
||||||
|
Commands = project.Commands;
|
||||||
|
ProjectSearchPaths = projectSearchPaths;
|
||||||
|
GlobalJsonPath = gloablJsonPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<FrameworkData> Frameworks { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<string> Configurations { get; }
|
||||||
|
|
||||||
|
public IDictionary<string, string> Commands { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<string> ProjectSearchPaths { get; }
|
||||||
|
|
||||||
|
public string GlobalJsonPath { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class ReferencesMessenger : Messenger<ProjectContextSnapshot>
|
||||||
|
{
|
||||||
|
public ReferencesMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.References, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.FileReferences != null &&
|
||||||
|
remote.ProjectReferences != null &&
|
||||||
|
Enumerable.SequenceEqual(local.FileReferences, remote.FileReferences) &&
|
||||||
|
Enumerable.SequenceEqual(local.ProjectReferences, remote.ProjectReferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectContextSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new ReferencesMessage
|
||||||
|
{
|
||||||
|
Framework = local.TargetFramework.ToPayload(),
|
||||||
|
ProjectReferences = local.ProjectReferences,
|
||||||
|
FileReferences = local.FileReferences
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.FileReferences = local.FileReferences;
|
||||||
|
remote.ProjectReferences = local.ProjectReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReferencesMessage
|
||||||
|
{
|
||||||
|
public FrameworkData Framework { get; set; }
|
||||||
|
public IReadOnlyList<string> FileReferences { get; set; }
|
||||||
|
public IReadOnlyList<ProjectReferenceDescription> ProjectReferences { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
|
||||||
|
{
|
||||||
|
internal class SourcesMessenger : Messenger<ProjectContextSnapshot>
|
||||||
|
{
|
||||||
|
public SourcesMessenger(Action<string, object> transmit)
|
||||||
|
: base(MessageTypes.Sources, transmit)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override bool CheckDifference(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
return remote.SourceFiles != null &&
|
||||||
|
Enumerable.SequenceEqual(local.SourceFiles, remote.SourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SendPayload(ProjectContextSnapshot local, Action<object> send)
|
||||||
|
{
|
||||||
|
send(new SourcesMessage
|
||||||
|
{
|
||||||
|
Framework = local.TargetFramework.ToPayload(),
|
||||||
|
Files = local.SourceFiles,
|
||||||
|
GeneratedFiles = new Dictionary<string, string>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetValue(ProjectContextSnapshot local, ProjectContextSnapshot remote)
|
||||||
|
{
|
||||||
|
remote.SourceFiles = local.SourceFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SourcesMessage
|
||||||
|
{
|
||||||
|
public FrameworkData Framework { get; set; }
|
||||||
|
public IReadOnlyList<string> Files { get; set; }
|
||||||
|
public IDictionary<string, string> GeneratedFiles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Compilation;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Graph;
|
||||||
|
using NuGet.Versioning;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class DependencyDescription
|
||||||
|
{
|
||||||
|
private DependencyDescription() { }
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
public string DisplayName { get; private set; }
|
||||||
|
|
||||||
|
public string Version { get; private set; }
|
||||||
|
|
||||||
|
public string Path { get; private set; }
|
||||||
|
|
||||||
|
public string Type { get; private set; }
|
||||||
|
|
||||||
|
public bool Resolved { get; private set; }
|
||||||
|
|
||||||
|
public IEnumerable<DependencyItem> Dependencies { get; private set; }
|
||||||
|
|
||||||
|
public IEnumerable<DiagnosticMessageView> Errors { get; private set; }
|
||||||
|
|
||||||
|
public IEnumerable<DiagnosticMessageView> Warnings { get; private set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as DependencyDescription;
|
||||||
|
|
||||||
|
return other != null &&
|
||||||
|
Resolved == other.Resolved &&
|
||||||
|
string.Equals(Name, other.Name) &&
|
||||||
|
object.Equals(Version, other.Version) &&
|
||||||
|
string.Equals(Path, other.Path) &&
|
||||||
|
string.Equals(Type, other.Type) &&
|
||||||
|
Enumerable.SequenceEqual(Dependencies, other.Dependencies) &&
|
||||||
|
Enumerable.SequenceEqual(Errors, other.Errors) &&
|
||||||
|
Enumerable.SequenceEqual(Warnings, other.Warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// These objects are currently POCOs and we're overriding equals
|
||||||
|
// so that things like Enumerable.SequenceEqual just work.
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DependencyDescription Create(LibraryDescription library,
|
||||||
|
List<DiagnosticMessage> diagnostics,
|
||||||
|
IDictionary<string, LibraryExport> exportsLookup)
|
||||||
|
{
|
||||||
|
var result = new DependencyDescription
|
||||||
|
{
|
||||||
|
Name = library.Identity.Name,
|
||||||
|
DisplayName = library.Identity.Name,
|
||||||
|
Version = (library.Identity.Version ?? new NuGetVersion("1.0.0")).ToNormalizedString(),
|
||||||
|
Type = library.Identity.Type.Value,
|
||||||
|
Resolved = library.Resolved,
|
||||||
|
Path = library.Path,
|
||||||
|
Dependencies = library.Dependencies.Select(dependency => GetDependencyItem(dependency, exportsLookup)),
|
||||||
|
Errors = diagnostics.Where(d => d.Severity == DiagnosticMessageSeverity.Error)
|
||||||
|
.Select(d => new DiagnosticMessageView(d)),
|
||||||
|
Warnings = diagnostics.Where(d => d.Severity == DiagnosticMessageSeverity.Warning)
|
||||||
|
.Select(d => new DiagnosticMessageView(d))
|
||||||
|
};
|
||||||
|
|
||||||
|
var msbuildLibrary = library as MSBuildProjectDescription;
|
||||||
|
if (msbuildLibrary != null)
|
||||||
|
{
|
||||||
|
result.Path = msbuildLibrary.MSBuildProjectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DependencyItem GetDependencyItem(LibraryRange dependency,
|
||||||
|
IDictionary<string, LibraryExport> exportsLookup)
|
||||||
|
{
|
||||||
|
return new DependencyItem
|
||||||
|
{
|
||||||
|
Name = dependency.Name,
|
||||||
|
Version = exportsLookup[dependency.Name].Library.Identity.Version?.ToNormalizedString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class DependencyItem
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as DependencyItem;
|
||||||
|
return other != null &&
|
||||||
|
string.Equals(Name, other.Name) &&
|
||||||
|
object.Equals(Version, other.Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// These objects are currently POCOs and we're overriding equals
|
||||||
|
// so that things like Enumerable.SequenceEqual just work.
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class DiagnosticMessageGroup
|
||||||
|
{
|
||||||
|
public DiagnosticMessageGroup(IEnumerable<DiagnosticMessage> diagnostics)
|
||||||
|
: this(framework: null, diagnostics: diagnostics)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public DiagnosticMessageGroup(NuGetFramework framework, IEnumerable<DiagnosticMessage> diagnostics)
|
||||||
|
{
|
||||||
|
Framework = framework;
|
||||||
|
Diagnostics = diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<DiagnosticMessage> Diagnostics { get; }
|
||||||
|
|
||||||
|
public NuGetFramework Framework { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class DiagnosticMessageView
|
||||||
|
{
|
||||||
|
public DiagnosticMessageView(DiagnosticMessage data)
|
||||||
|
{
|
||||||
|
ErrorCode = data.ErrorCode;
|
||||||
|
SourceFilePath = data.SourceFilePath;
|
||||||
|
Message = data.Message;
|
||||||
|
Severity = data.Severity;
|
||||||
|
StartLine = data.StartLine;
|
||||||
|
StartColumn = data.StartColumn;
|
||||||
|
EndLine = data.EndLine;
|
||||||
|
EndColumn = data.EndColumn;
|
||||||
|
FormattedMessage = data.FormattedMessage;
|
||||||
|
|
||||||
|
var description = data.Source as LibraryDescription;
|
||||||
|
if (description != null)
|
||||||
|
{
|
||||||
|
Source = new
|
||||||
|
{
|
||||||
|
Name = description.Identity.Name,
|
||||||
|
Version = description.Identity.Version?.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ErrorCode { get; }
|
||||||
|
|
||||||
|
public string SourceFilePath { get; }
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
|
public DiagnosticMessageSeverity Severity { get; }
|
||||||
|
|
||||||
|
public int StartLine { get; }
|
||||||
|
|
||||||
|
public int StartColumn { get; }
|
||||||
|
|
||||||
|
public int EndLine { get; }
|
||||||
|
|
||||||
|
public int EndColumn { get; }
|
||||||
|
|
||||||
|
public string FormattedMessage { get; }
|
||||||
|
|
||||||
|
public object Source { get; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as DiagnosticMessageView;
|
||||||
|
|
||||||
|
return other != null &&
|
||||||
|
Severity == other.Severity &&
|
||||||
|
StartLine == other.StartLine &&
|
||||||
|
StartColumn == other.StartColumn &&
|
||||||
|
EndLine == other.EndLine &&
|
||||||
|
EndColumn == other.EndColumn &&
|
||||||
|
string.Equals(ErrorCode, other.ErrorCode, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(SourceFilePath, other.SourceFilePath, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(Message, other.Message, StringComparison.Ordinal) &&
|
||||||
|
object.Equals(Source, other.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class DiagnosticsListMessage
|
||||||
|
{
|
||||||
|
public DiagnosticsListMessage(IEnumerable<DiagnosticMessage> diagnostics) :
|
||||||
|
this(diagnostics, frameworkData: null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticsListMessage(IEnumerable<DiagnosticMessage> diagnostics, FrameworkData frameworkData) :
|
||||||
|
this(diagnostics.Select(msg => new DiagnosticMessageView(msg)).ToList(), frameworkData)
|
||||||
|
{
|
||||||
|
if (diagnostics == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(diagnostics));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticsListMessage(IEnumerable<DiagnosticMessageView> diagnostics) :
|
||||||
|
this(diagnostics, frameworkData: null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiagnosticsListMessage(IEnumerable<DiagnosticMessageView> diagnostics, FrameworkData frameworkData)
|
||||||
|
{
|
||||||
|
if (diagnostics == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(diagnostics));
|
||||||
|
}
|
||||||
|
|
||||||
|
Diagnostics = diagnostics;
|
||||||
|
Errors = diagnostics.Where(msg => msg.Severity == DiagnosticMessageSeverity.Error).ToList();
|
||||||
|
Warnings = diagnostics.Where(msg => msg.Severity == DiagnosticMessageSeverity.Warning).ToList();
|
||||||
|
Framework = frameworkData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameworkData Framework { get; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IEnumerable<DiagnosticMessageView> Diagnostics { get; }
|
||||||
|
|
||||||
|
public IList<DiagnosticMessageView> Errors { get; }
|
||||||
|
|
||||||
|
public IList<DiagnosticMessageView> Warnings { get; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as DiagnosticsListMessage;
|
||||||
|
|
||||||
|
return other != null &&
|
||||||
|
Enumerable.SequenceEqual(Errors, other.Errors) &&
|
||||||
|
Enumerable.SequenceEqual(Warnings, other.Warnings) &&
|
||||||
|
object.Equals(Framework, other.Framework);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class ErrorMessage
|
||||||
|
{
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public int Line { get; set; }
|
||||||
|
public int Column { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var payload = obj as ErrorMessage;
|
||||||
|
return payload != null &&
|
||||||
|
string.Equals(Message, payload.Message, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(Path, payload.Path, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
Line == payload.Line &&
|
||||||
|
Column == payload.Column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
public class FrameworkData
|
||||||
|
{
|
||||||
|
public string FrameworkName { get; set; }
|
||||||
|
public string FriendlyName { get; set; }
|
||||||
|
public string ShortName { get; set; }
|
||||||
|
public string RedistListPath { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as FrameworkData;
|
||||||
|
|
||||||
|
return other != null &&
|
||||||
|
string.Equals(FrameworkName, other.FrameworkName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
// These objects are currently POCOs and we're overriding equals
|
||||||
|
// so that things like Enumerable.SequenceEqual just work.
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
internal class Message
|
||||||
|
{
|
||||||
|
public static Message FromPayload(string messageType, int contextId, object payload)
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
MessageType = messageType,
|
||||||
|
ContextId = contextId,
|
||||||
|
Payload = payload is JToken ? (JToken)payload : JToken.FromObject(payload)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message() { }
|
||||||
|
|
||||||
|
public string MessageType { get; set; }
|
||||||
|
|
||||||
|
public string HostId { get; set; }
|
||||||
|
|
||||||
|
public int ContextId { get; set; } = -1;
|
||||||
|
|
||||||
|
public JToken Payload { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public ConnectionContext Sender { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"({HostId}, {MessageType}, {ContextId}) -> {Payload?.ToString(Formatting.Indented)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||||
|
{
|
||||||
|
internal class ProjectReferenceDescription
|
||||||
|
{
|
||||||
|
private ProjectReferenceDescription() { }
|
||||||
|
|
||||||
|
public FrameworkData Framework { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var other = obj as ProjectReferenceDescription;
|
||||||
|
return other != null &&
|
||||||
|
string.Equals(Name, other.Name) &&
|
||||||
|
string.Equals(Path, other.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a ProjectReferenceDescription from given LibraryDescription. If the library doesn't
|
||||||
|
/// represent a project reference returns null.
|
||||||
|
/// </summary>
|
||||||
|
public static ProjectReferenceDescription Create(LibraryDescription library)
|
||||||
|
{
|
||||||
|
if (library is ProjectDescription)
|
||||||
|
{
|
||||||
|
return new ProjectReferenceDescription
|
||||||
|
{
|
||||||
|
Framework = library.Framework.ToPayload(),
|
||||||
|
Name = library.Identity.Name,
|
||||||
|
Path = library.Path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (library is MSBuildProjectDescription)
|
||||||
|
{
|
||||||
|
return new ProjectReferenceDescription
|
||||||
|
{
|
||||||
|
Framework = library.Framework.ToPayload(),
|
||||||
|
Name = library.Identity.Name,
|
||||||
|
Path = ((MSBuildProjectDescription)library).MSBuildProjectPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
internal class ProcessingQueue
|
||||||
|
{
|
||||||
|
private readonly BinaryReader _reader;
|
||||||
|
private readonly BinaryWriter _writer;
|
||||||
|
|
||||||
|
public ProcessingQueue(Stream stream)
|
||||||
|
{
|
||||||
|
_reader = new BinaryReader(stream);
|
||||||
|
_writer = new BinaryWriter(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<Message> OnReceive;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine("Start");
|
||||||
|
new Thread(ReceiveMessages).Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Send(Action<BinaryWriter> writeAction)
|
||||||
|
{
|
||||||
|
lock (_writer)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writeAction(_writer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// swallow
|
||||||
|
Reporter.Output.WriteLine($"Ignore {nameof(IOException)} during sending message: \"{ex.Message}\".");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine($"Unexpected exception {ex.GetType().Name} during sending message: \"{ex.Message}\".");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Send(Message message)
|
||||||
|
{
|
||||||
|
return Send(_writer =>
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine($"OnSend ({message})");
|
||||||
|
_writer.Write(JsonConvert.SerializeObject(message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReceiveMessages()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var content = _reader.ReadString();
|
||||||
|
var message = JsonConvert.DeserializeObject<Message>(content);
|
||||||
|
|
||||||
|
Reporter.Output.WriteLine($"OnReceive ({message})");
|
||||||
|
OnReceive(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine($"Ignore {nameof(IOException)} during receiving messages: \"{ex}\".");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"Unexpected exception {ex.GetType().Name} during receiving messages: \"{ex}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
src/dotnet/commands/dotnet-projectmodel-server/Program.cs
Normal file
161
src/dotnet/commands/dotnet-projectmodel-server/Program.cs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using Microsoft.DotNet.Cli.CommandLine;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
public class ProjectModelServerCommand
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, ProjectManager> _projects;
|
||||||
|
private readonly DesignTimeWorkspace _workspaceContext;
|
||||||
|
private readonly ProtocolManager _protocolManager;
|
||||||
|
private readonly string _hostName;
|
||||||
|
private readonly int _port;
|
||||||
|
private Socket _listenSocket;
|
||||||
|
|
||||||
|
public ProjectModelServerCommand(int port, string hostName)
|
||||||
|
{
|
||||||
|
_port = port;
|
||||||
|
_hostName = hostName;
|
||||||
|
_protocolManager = new ProtocolManager(maxVersion: 4);
|
||||||
|
_workspaceContext = new DesignTimeWorkspace(ProjectReaderSettings.ReadFromEnvironment());
|
||||||
|
_projects = new Dictionary<int, ProjectManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Run(string[] args)
|
||||||
|
{
|
||||||
|
var app = new CommandLineApplication();
|
||||||
|
app.Name = "dotnet-projectmodel-server";
|
||||||
|
app.Description = ".NET Project Model Server";
|
||||||
|
app.FullName = ".NET Design Time Server";
|
||||||
|
app.Description = ".NET Design Time Server";
|
||||||
|
app.HelpOption("-?|-h|--help");
|
||||||
|
|
||||||
|
var verbose = app.Option("--verbose", "Verbose ouput", CommandOptionType.NoValue);
|
||||||
|
var hostpid = app.Option("--host-pid", "The process id of the host", CommandOptionType.SingleValue);
|
||||||
|
var hostname = app.Option("--host-name", "The name of the host", CommandOptionType.SingleValue);
|
||||||
|
var port = app.Option("--port", "The TCP port used for communication", CommandOptionType.SingleValue);
|
||||||
|
|
||||||
|
app.OnExecute(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!MonitorHostProcess(hostpid))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var intPort = CheckPort(port);
|
||||||
|
if (intPort == -1)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hostname.HasValue())
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"Option \"{hostname.LongName}\" is missing.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var program = new ProjectModelServerCommand(intPort, hostname.Value());
|
||||||
|
program.OpenChannel();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"Unhandled exception in server main: {ex}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.Execute(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenChannel()
|
||||||
|
{
|
||||||
|
_listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
_listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, _port));
|
||||||
|
_listenSocket.Listen(10);
|
||||||
|
|
||||||
|
Reporter.Output.WriteLine($"Process ID {Process.GetCurrentProcess().Id}");
|
||||||
|
Reporter.Output.WriteLine($"Listening on port {_port}");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var acceptSocket = _listenSocket.Accept();
|
||||||
|
Reporter.Output.WriteLine($"Client accepted {acceptSocket.LocalEndPoint}");
|
||||||
|
|
||||||
|
var connection = new ConnectionContext(acceptSocket,
|
||||||
|
_hostName,
|
||||||
|
_protocolManager,
|
||||||
|
_workspaceContext,
|
||||||
|
_projects);
|
||||||
|
|
||||||
|
connection.QueueStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shutdown()
|
||||||
|
{
|
||||||
|
if (_listenSocket.Connected)
|
||||||
|
{
|
||||||
|
_listenSocket.Shutdown(SocketShutdown.Both);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CheckPort(CommandOption port)
|
||||||
|
{
|
||||||
|
if (!port.HasValue())
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"Option \"{port.LongName}\" is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int result;
|
||||||
|
if (int.TryParse(port.Value(), out result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"Option \"{port.LongName}\" is not a valid Int32 value.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MonitorHostProcess(CommandOption host)
|
||||||
|
{
|
||||||
|
if (!host.HasValue())
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Option \"{host.LongName}\" is missing.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hostPID;
|
||||||
|
if (int.TryParse(host.Value(), out hostPID))
|
||||||
|
{
|
||||||
|
var hostProcess = Process.GetProcessById(hostPID);
|
||||||
|
hostProcess.EnableRaisingEvents = true;
|
||||||
|
hostProcess.Exited += (s, e) =>
|
||||||
|
{
|
||||||
|
Process.GetCurrentProcess().Kill();
|
||||||
|
};
|
||||||
|
|
||||||
|
Reporter.Output.WriteLine($"Server will exit when process {hostPID} exits.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"Option \"{host.LongName}\" is not a valid Int32 value.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
327
src/dotnet/commands/dotnet-projectmodel-server/ProjectManager.cs
Normal file
327
src/dotnet/commands/dotnet-projectmodel-server/ProjectManager.cs
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Helpers;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Messengers;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
internal class ProjectManager
|
||||||
|
{
|
||||||
|
private readonly object _processingLock = new object();
|
||||||
|
private readonly Queue<Message> _inbox = new Queue<Message>();
|
||||||
|
private readonly ProtocolManager _protocolManager;
|
||||||
|
|
||||||
|
private ConnectionContext _initializedContext;
|
||||||
|
|
||||||
|
// triggers
|
||||||
|
private readonly Trigger<string> _appPath = new Trigger<string>();
|
||||||
|
private readonly Trigger<string> _configure = new Trigger<string>();
|
||||||
|
private readonly Trigger<bool> _refreshDependencies = new Trigger<bool>();
|
||||||
|
private readonly Trigger<int> _filesChanged = new Trigger<int>();
|
||||||
|
|
||||||
|
private ProjectSnapshot _local = new ProjectSnapshot();
|
||||||
|
private ProjectSnapshot _remote = new ProjectSnapshot();
|
||||||
|
|
||||||
|
private readonly DesignTimeWorkspace _workspaceContext;
|
||||||
|
private int? _contextProtocolVersion;
|
||||||
|
|
||||||
|
private readonly List<Messenger<ProjectContextSnapshot>> _messengers;
|
||||||
|
|
||||||
|
private ProjectDiagnosticsMessenger _projectDiagnosticsMessenger;
|
||||||
|
private GlobalErrorMessenger _globalErrorMessenger;
|
||||||
|
private ProjectInformationMessenger _projectInforamtionMessenger;
|
||||||
|
|
||||||
|
public ProjectManager(
|
||||||
|
int contextId,
|
||||||
|
DesignTimeWorkspace workspaceContext,
|
||||||
|
ProtocolManager protocolManager)
|
||||||
|
{
|
||||||
|
Id = contextId;
|
||||||
|
_workspaceContext = workspaceContext;
|
||||||
|
_protocolManager = protocolManager;
|
||||||
|
|
||||||
|
_messengers = new List<Messenger<ProjectContextSnapshot>>
|
||||||
|
{
|
||||||
|
new ReferencesMessenger(Transmit),
|
||||||
|
new DependenciesMessenger(Transmit),
|
||||||
|
new DependencyDiagnosticsMessenger(Transmit),
|
||||||
|
new CompilerOptionsMessenger(Transmit),
|
||||||
|
new SourcesMessenger(Transmit)
|
||||||
|
};
|
||||||
|
|
||||||
|
_projectDiagnosticsMessenger = new ProjectDiagnosticsMessenger(Transmit);
|
||||||
|
_globalErrorMessenger = new GlobalErrorMessenger(Transmit);
|
||||||
|
_projectInforamtionMessenger = new ProjectInformationMessenger(Transmit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Id { get; }
|
||||||
|
|
||||||
|
public string ProjectPath { get { return _appPath.Value; } }
|
||||||
|
|
||||||
|
public int ProtocolVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_contextProtocolVersion.HasValue)
|
||||||
|
{
|
||||||
|
return _contextProtocolVersion.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _protocolManager.CurrentVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReceive(Message message)
|
||||||
|
{
|
||||||
|
lock (_inbox)
|
||||||
|
{
|
||||||
|
_inbox.Enqueue(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadPool.QueueUserWorkItem(state => ((ProjectManager)state).ProcessLoop(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Transmit(string messageType, object payload)
|
||||||
|
{
|
||||||
|
var message = Message.FromPayload(messageType, Id, payload);
|
||||||
|
_initializedContext.Transmit(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessLoop()
|
||||||
|
{
|
||||||
|
if (!Monitor.TryEnter(_processingLock))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_inbox)
|
||||||
|
{
|
||||||
|
if (!_inbox.Any())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DoProcessLoop();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Reporter.Error.WriteLine($"A unexpected exception occurred: {ex}");
|
||||||
|
|
||||||
|
var error = new ErrorMessage
|
||||||
|
{
|
||||||
|
Message = ex.Message
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileFormatException = ex as FileFormatException;
|
||||||
|
if (fileFormatException != null)
|
||||||
|
{
|
||||||
|
error.Path = fileFormatException.Path;
|
||||||
|
error.Line = fileFormatException.Line;
|
||||||
|
error.Column = fileFormatException.Column;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = Message.FromPayload(MessageTypes.Error, Id, error);
|
||||||
|
|
||||||
|
_initializedContext.Transmit(message);
|
||||||
|
_remote.GlobalErrorMessage = error;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Monitor.Exit(_processingLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoProcessLoop()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
DrainInbox();
|
||||||
|
|
||||||
|
UpdateProject();
|
||||||
|
SendOutgingMessages();
|
||||||
|
|
||||||
|
lock (_inbox)
|
||||||
|
{
|
||||||
|
if (_inbox.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrainInbox()
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine("Begin draining inbox.");
|
||||||
|
|
||||||
|
while (ProcessMessage()) { }
|
||||||
|
|
||||||
|
Reporter.Output.WriteLine("Finish draining inbox.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ProcessMessage()
|
||||||
|
{
|
||||||
|
Message message;
|
||||||
|
|
||||||
|
lock (_inbox)
|
||||||
|
{
|
||||||
|
if (!_inbox.Any())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = _inbox.Dequeue();
|
||||||
|
Debug.Assert(message != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reporter.Output.WriteLine($"Received {message.MessageType}");
|
||||||
|
|
||||||
|
switch (message.MessageType)
|
||||||
|
{
|
||||||
|
case MessageTypes.Initialize:
|
||||||
|
Initialize(message);
|
||||||
|
break;
|
||||||
|
case MessageTypes.ChangeConfiguration:
|
||||||
|
// TODO: what if the payload is null or represent empty string?
|
||||||
|
_configure.Value = message.Payload.GetValue("Configuration");
|
||||||
|
break;
|
||||||
|
case MessageTypes.RefreshDependencies:
|
||||||
|
// In the case of RefreshDependencies request, the cache will not be reset in any case. The value
|
||||||
|
// is set so as to trigger refresh action in later loop.
|
||||||
|
_refreshDependencies.Value = false;
|
||||||
|
break;
|
||||||
|
case MessageTypes.RestoreComplete:
|
||||||
|
// In the case of RestoreComplete request, the value of the 'Reset' property in payload will determine
|
||||||
|
// if the cache should be reset. If the property doesn't exist, cache will be reset.
|
||||||
|
_refreshDependencies.Value = message.Payload.HasValues ? message.Payload.Value<bool>("Reset") : true;
|
||||||
|
break;
|
||||||
|
case MessageTypes.FilesChanged:
|
||||||
|
_filesChanged.Value = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize(Message message)
|
||||||
|
{
|
||||||
|
if (_initializedContext != null)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine($"Received {message.MessageType} message more than once for {_appPath.Value}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initializedContext = message.Sender;
|
||||||
|
_appPath.Value = message.Payload.GetValue("ProjectFolder");
|
||||||
|
_configure.Value = message.Payload.GetValue("Configuration") ?? "Debug";
|
||||||
|
|
||||||
|
var version = message.Payload.GetValue<int>("Version");
|
||||||
|
if (version != 0 && !_protocolManager.EnvironmentOverridden)
|
||||||
|
{
|
||||||
|
_contextProtocolVersion = Math.Min(version, _protocolManager.MaxVersion);
|
||||||
|
Reporter.Output.WriteLine($"Set context protocol version to {_contextProtocolVersion.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UpdateProject()
|
||||||
|
{
|
||||||
|
ProjectSnapshot newSnapshot = null;
|
||||||
|
|
||||||
|
if (_appPath.WasAssigned || _configure.WasAssigned || _filesChanged.WasAssigned || _refreshDependencies.WasAssigned)
|
||||||
|
{
|
||||||
|
_appPath.ClearAssigned();
|
||||||
|
_configure.ClearAssigned();
|
||||||
|
_filesChanged.ClearAssigned();
|
||||||
|
|
||||||
|
bool resetCache = _refreshDependencies.WasAssigned ? _refreshDependencies.Value : false;
|
||||||
|
_refreshDependencies.ClearAssigned();
|
||||||
|
|
||||||
|
newSnapshot = ProjectSnapshot.Create(_appPath.Value,
|
||||||
|
_configure.Value,
|
||||||
|
_workspaceContext,
|
||||||
|
_remote.ProjectSearchPaths,
|
||||||
|
clearWorkspaceContextCache: resetCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSnapshot == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_local = newSnapshot;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendOutgingMessages()
|
||||||
|
{
|
||||||
|
_projectInforamtionMessenger.UpdateRemote(_local, _remote);
|
||||||
|
_projectDiagnosticsMessenger.UpdateRemote(_local, _remote);
|
||||||
|
|
||||||
|
var unprocessedFrameworks = new HashSet<NuGetFramework>(_remote.ProjectContexts.Keys);
|
||||||
|
foreach (var pair in _local.ProjectContexts)
|
||||||
|
{
|
||||||
|
ProjectContextSnapshot localProjectSnapshot = pair.Value;
|
||||||
|
ProjectContextSnapshot remoteProjectSnapshot;
|
||||||
|
|
||||||
|
if (!_remote.ProjectContexts.TryGetValue(pair.Key, out remoteProjectSnapshot))
|
||||||
|
{
|
||||||
|
remoteProjectSnapshot = new ProjectContextSnapshot();
|
||||||
|
_remote.ProjectContexts[pair.Key] = remoteProjectSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
unprocessedFrameworks.Remove(pair.Key);
|
||||||
|
|
||||||
|
foreach (var messenger in _messengers)
|
||||||
|
{
|
||||||
|
messenger.UpdateRemote(localProjectSnapshot,
|
||||||
|
remoteProjectSnapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all processed frameworks from the remote view
|
||||||
|
foreach (var framework in unprocessedFrameworks)
|
||||||
|
{
|
||||||
|
_remote.ProjectContexts.Remove(framework);
|
||||||
|
}
|
||||||
|
|
||||||
|
_globalErrorMessenger.UpdateRemote(_local, _remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Trigger<TValue>
|
||||||
|
{
|
||||||
|
private TValue _value;
|
||||||
|
|
||||||
|
public bool WasAssigned { get; private set; }
|
||||||
|
|
||||||
|
public void ClearAssigned()
|
||||||
|
{
|
||||||
|
WasAssigned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue Value
|
||||||
|
{
|
||||||
|
get { return _value; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
WasAssigned = true;
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ProjectModel.Server
|
||||||
|
{
|
||||||
|
internal class ProtocolManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Environment variable for overriding protocol.
|
||||||
|
/// </summary>
|
||||||
|
public const string EnvDthProtocol = "DTH_PROTOCOL";
|
||||||
|
|
||||||
|
public ProtocolManager(int maxVersion)
|
||||||
|
{
|
||||||
|
MaxVersion = maxVersion;
|
||||||
|
|
||||||
|
// initialized to the highest supported version or environment overridden value
|
||||||
|
int? protocol = GetProtocolVersionFromEnvironment();
|
||||||
|
|
||||||
|
if (protocol.HasValue)
|
||||||
|
{
|
||||||
|
CurrentVersion = protocol.Value;
|
||||||
|
EnvironmentOverridden = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentVersion = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxVersion { get; }
|
||||||
|
|
||||||
|
public int CurrentVersion { get; private set; }
|
||||||
|
|
||||||
|
public bool EnvironmentOverridden { get; }
|
||||||
|
|
||||||
|
public bool IsProtocolNegotiation(Message message)
|
||||||
|
{
|
||||||
|
return message?.MessageType == MessageTypes.ProtocolVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Negotiate(Message message)
|
||||||
|
{
|
||||||
|
if (!IsProtocolNegotiation(message))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reporter.Output.WriteLine("Initializing the protocol negotiation.");
|
||||||
|
|
||||||
|
if (EnvironmentOverridden)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine($"DTH protocol negotiation is override by environment variable {EnvDthProtocol} and set to {CurrentVersion}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenValue = message.Payload?["Version"];
|
||||||
|
if (tokenValue == null)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine("Protocol negotiation failed. Version property is missing in payload.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var preferredVersion = tokenValue.ToObject<int>();
|
||||||
|
if (preferredVersion == 0)
|
||||||
|
{
|
||||||
|
// the preferred version can't be zero. either property is missing or the the payload is corrupted.
|
||||||
|
Reporter.Output.WriteLine("Protocol negotiation failed. Protocol version 0 is invalid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentVersion = Math.Min(preferredVersion, MaxVersion);
|
||||||
|
Reporter.Output.WriteLine($"Protocol negotiation successed. Use protocol {CurrentVersion}");
|
||||||
|
|
||||||
|
if (message.Sender != null)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine("Respond to protocol negotiation.");
|
||||||
|
message.Sender.Transmit(Message.FromPayload(
|
||||||
|
MessageTypes.ProtocolVersion,
|
||||||
|
0,
|
||||||
|
new { Version = CurrentVersion }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine($"{nameof(Message.Sender)} is null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int? GetProtocolVersionFromEnvironment()
|
||||||
|
{
|
||||||
|
// look for the environment variable DTH_PROTOCOL, if it is set override the protocol version.
|
||||||
|
// this is for debugging.
|
||||||
|
var strProtocol = Environment.GetEnvironmentVariable(EnvDthProtocol);
|
||||||
|
int intProtocol = -1;
|
||||||
|
if (!string.IsNullOrEmpty(strProtocol) && Int32.TryParse(strProtocol, out intProtocol))
|
||||||
|
{
|
||||||
|
return intProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue