Adding dotnet-projectmodel-server back because ASP.NET needs it to unblock themselves from start migrating to msbuild. We need to remove this as soon as ASP.NET can migrate all their projects to csproj and the tooling is in place.

This commit is contained in:
Livar Cunha 2016-09-07 14:14:41 -07:00
parent ab8e1b707b
commit 55c57608f8
31 changed files with 1981 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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