Everything in the same project

- Build in intrinsic commands as part of dotnet
This commit is contained in:
David Fowler 2016-01-30 21:47:50 -08:00
parent 31c718d5d3
commit dfc59eb20e
166 changed files with 156 additions and 827 deletions

View file

@ -0,0 +1,65 @@
// 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;
using Microsoft.Extensions.Logging;
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,
WorkspaceContext workspaceContext,
IDictionary<int, ProjectManager> projects,
ILoggerFactory loggerFactory)
{
_hostName = hostName;
_projects = projects;
_queue = new ProcessingQueue(new NetworkStream(acceptedSocket), loggerFactory);
_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,
loggerFactory,
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> currentSearchPaths)
{
var result = new List<DiagnosticMessage>();
var project = context.ProjectFile;
var libraries = context.LibraryManager.GetLibraries();
var updatedSearchPath = GetUpdatedSearchPaths(currentSearchPaths, 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,56 @@
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,75 @@
// 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 Microsoft.DotNet.Cli.Compiler.Common;
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> currentSearchPaths)
{
var snapshot = new ProjectContextSnapshot();
var allDependencyDiagnostics = new List<DiagnosticMessage>();
allDependencyDiagnostics.AddRange(context.LibraryManager.GetAllDiagnostics());
allDependencyDiagnostics.AddRange(DependencyTypeChangeFinder.Diagnose(context, currentSearchPaths));
var diagnosticsLookup = allDependencyDiagnostics.ToLookup(d => d.Source);
var allExports = context.CreateExporter(configuration)
.GetAllExports()
.ToDictionary(export => export.Library.GetUniqueName());
var allSourceFiles = new List<string>(context.ProjectFile.Files.SourceFiles);
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 pair in allExports)
{
var export = pair.Value;
allSourceFiles.AddRange(export.SourceReferences);
allFileReferences.AddRange(export.CompilationAssemblies.Select(asset => asset.ResolvedPath));
var diagnostics = diagnosticsLookup[export.Library].ToList();
var description = DependencyDescription.Create(export.Library, diagnostics, allExports);
allDependencies[description.Name] = description;
var projectDescription = export.Library as ProjectDescription;
if (projectDescription != null && projectDescription.Identity.Name != context.ProjectFile.Name)
{
allProjectReferences.Add(ProjectReferenceDescription.Create(projectDescription));
}
}
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;
}
}
}

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.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, WorkspaceContext workspaceContext, IReadOnlyList<string> projectSearchPaths)
{
var projectContextsCollection = workspaceContext.GetProjectContextCollection(projectDirectory);
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.ProjectContexts)
{
snapshot.ProjectContexts[projectContext.TargetFramework] =
ProjectContextSnapshot.Create(projectContext, configuration, currentSearchPaths);
}
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 object CreatePayload(ProjectContextSnapshot local)
{
return 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 object CreatePayload(ProjectContextSnapshot local)
{
return 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 object CreatePayload(ProjectContextSnapshot local)
{
return new DiagnosticsListMessage(
local.DependencyDiagnostics,
local.TargetFramework?.ToPayload());
}
protected override void SetValue(ProjectContextSnapshot local, ProjectContextSnapshot remote)
{
remote.DependencyDiagnostics = local.DependencyDiagnostics;
}
}
}

View file

@ -0,0 +1,29 @@
// 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 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 object CreatePayload(ProjectSnapshot local)
{
return local.GlobalErrorMessage;
}
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot remote)
{
remote.GlobalErrorMessage = local.GlobalErrorMessage;
}
}
}

View file

@ -0,0 +1,37 @@
// 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))
{
var payload = CreatePayload(local);
_transmit(MessageType, payload);
SetValue(local, remote);
}
}
protected abstract void SetValue(T local, T remote);
protected abstract object CreatePayload(T local);
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 object CreatePayload(ProjectSnapshot local)
{
return 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 object CreatePayload(ProjectSnapshot local)
{
return 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 object CreatePayload(ProjectContextSnapshot local)
{
return 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 object CreatePayload(ProjectContextSnapshot local)
{
return 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,80 @@
// 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.Server.Helpers;
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,
Dictionary<string, LibraryExport> allExports)
{
var name = library.GetUniqueName();
return new DependencyDescription
{
Name = name,
DisplayName = library.Identity.Name,
Version = library.Identity.Version?.ToNormalizedString(),
Type = library.Identity.Type.Value,
Resolved = library.Resolved,
Path = library.Path,
Dependencies = library.Dependencies.Select(dependency => new DependencyItem
{
Name = dependency.GetUniqueName(),
Version = allExports[dependency.GetUniqueName()].Library.Identity.Version?.ToNormalizedString()
}),
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))
};
}
}
}

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,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.
namespace Microsoft.DotNet.ProjectModel.Server.Models
{
internal class ProjectReferenceDescription
{
private ProjectReferenceDescription() { }
public string Name { get; set; }
public string Path { get; set; }
public string WrappedProjectPath { 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) &&
string.Equals(WrappedProjectPath, other.WrappedProjectPath);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static ProjectReferenceDescription Create(ProjectDescription description)
{
var targetFrameworkInformation = description.TargetFrameworkInfo;
string wrappedProjectPath = null;
if (!string.IsNullOrEmpty(targetFrameworkInformation?.WrappedProject) &&
description.Project != null)
{
wrappedProjectPath = System.IO.Path.Combine(
description.Project.ProjectDirectory,
targetFrameworkInformation.WrappedProject);
wrappedProjectPath = System.IO.Path.GetFullPath(wrappedProjectPath);
}
return new ProjectReferenceDescription
{
Name = description.Identity.Name,
Path = description.Path,
WrappedProjectPath = wrappedProjectPath,
};
}
}
}

View file

@ -0,0 +1,90 @@
// 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.ProjectModel.Server.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Microsoft.DotNet.ProjectModel.Server
{
internal class ProcessingQueue
{
private readonly BinaryReader _reader;
private readonly BinaryWriter _writer;
private readonly ILogger _log;
public ProcessingQueue(Stream stream, ILoggerFactory loggerFactory)
{
_reader = new BinaryReader(stream);
_writer = new BinaryWriter(stream);
_log = loggerFactory.CreateLogger<ProcessingQueue>();
}
public event Action<Message> OnReceive;
public void Start()
{
_log.LogInformation("Start");
new Thread(ReceiveMessages).Start();
}
public bool Send(Action<BinaryWriter> writeAction)
{
lock (_writer)
{
try
{
writeAction(_writer);
return true;
}
catch (IOException ex)
{
// swallow
_log.LogWarning($"Ignore {nameof(IOException)} during sending message: \"{ex.Message}\".");
}
catch (Exception ex)
{
_log.LogWarning($"Unexpected exception {ex.GetType().Name} during sending message: \"{ex.Message}\".");
throw;
}
}
return false;
}
public bool Send(Message message)
{
return Send(_writer =>
{
_log.LogInformation($"Send ({message})");
_writer.Write(JsonConvert.SerializeObject(message));
});
}
private void ReceiveMessages()
{
try
{
while (true)
{
var content = _reader.ReadString();
var message = JsonConvert.DeserializeObject<Message>(content);
_log.LogInformation($"OnReceive({message})");
OnReceive(message);
}
}
catch (IOException ex)
{
_log.LogWarning($"Ignore {nameof(IOException)} during receiving messages: \"{ex}\".");
}
catch (Exception ex)
{
_log.LogError($"Unexpected exception {ex.GetType().Name} during receiving messages: \"{ex}\".");
}
}
}
}

View file

@ -0,0 +1,162 @@
// 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.Diagnostics;
using System.Net;
using System.Net.Sockets;
using Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.ProjectModel.Server
{
public class ProjectModelServerCommand
{
private readonly Dictionary<int, ProjectManager> _projects;
private readonly WorkspaceContext _workspaceContext;
private readonly ProtocolManager _protocolManager;
private readonly ILoggerFactory _loggerFactory;
private readonly string _hostName;
private readonly int _port;
private Socket _listenSocket;
public ProjectModelServerCommand(int port, string hostName, ILoggerFactory loggerFactory)
{
_port = port;
_hostName = hostName;
_loggerFactory = loggerFactory;
_protocolManager = new ProtocolManager(maxVersion: 4, loggerFactory: _loggerFactory);
_workspaceContext = WorkspaceContext.Create();
_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(() =>
{
var loggerFactory = new LoggerFactory();
loggerFactory.AddConsole(verbose.HasValue() ? LogLevel.Debug : LogLevel.Information);
var logger = loggerFactory.CreateLogger<ProjectModelServerCommand>();
if (!MonitorHostProcess(hostpid, logger))
{
return 1;
}
var intPort = CheckPort(port, logger);
if (intPort == -1)
{
return 1;
}
if (!hostname.HasValue())
{
logger.LogError($"Option \"{hostname.LongName}\" is missing.");
return 1;
}
var program = new ProjectModelServerCommand(intPort, hostname.Value(), loggerFactory);
program.OpenChannel();
return 0;
});
return app.Execute(args);
}
public void OpenChannel()
{
var logger = _loggerFactory.CreateLogger($"OpenChannel");
_listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, _port));
_listenSocket.Listen(10);
logger.LogInformation($"Process ID {Process.GetCurrentProcess().Id}");
logger.LogInformation($"Listening on port {_port}");
while (true)
{
var acceptSocket = _listenSocket.Accept();
logger.LogInformation($"Client accepted {acceptSocket.LocalEndPoint}");
var connection = new ConnectionContext(acceptSocket,
_hostName,
_protocolManager,
_workspaceContext,
_projects,
_loggerFactory);
connection.QueueStart();
}
}
public void Shutdown()
{
if (_listenSocket.Connected)
{
_listenSocket.Shutdown(SocketShutdown.Both);
}
}
private static int CheckPort(CommandOption port, ILogger logger)
{
if (!port.HasValue())
{
logger.LogError($"Option \"{port.LongName}\" is missing.");
}
int result;
if (int.TryParse(port.Value(), out result))
{
return result;
}
else
{
logger.LogError($"Option \"{port.LongName}\" is not a valid Int32 value.");
return -1;
}
}
private static bool MonitorHostProcess(CommandOption host, ILogger logger)
{
if (!host.HasValue())
{
logger.LogError($"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();
};
logger.LogDebug($"Server will exit when process {hostPID} exits.");
return true;
}
else
{
logger.LogError($"Option \"{host.LongName}\" is not a valid Int32 value.");
return false;
}
}
}
}

View file

@ -0,0 +1,320 @@
// 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.ProjectModel.Server.Helpers;
using Microsoft.DotNet.ProjectModel.Server.Messengers;
using Microsoft.DotNet.ProjectModel.Server.Models;
using Microsoft.Extensions.Logging;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel.Server
{
internal class ProjectManager
{
private readonly ILogger _log;
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<int> _refreshDependencies = new Trigger<int>();
private readonly Trigger<int> _filesChanged = new Trigger<int>();
private ProjectSnapshot _local = new ProjectSnapshot();
private ProjectSnapshot _remote = new ProjectSnapshot();
private readonly WorkspaceContext _workspaceContext;
private int? _contextProtocolVersion;
private readonly List<Messenger<ProjectContextSnapshot>> _messengers;
private ProjectDiagnosticsMessenger _projectDiagnosticsMessenger;
private GlobalErrorMessenger _globalErrorMessenger;
private ProjectInformationMessenger _projectInforamtionMessenger;
public ProjectManager(int contextId,
ILoggerFactory loggerFactory,
WorkspaceContext workspaceContext,
ProtocolManager protocolManager)
{
Id = contextId;
_log = loggerFactory.CreateLogger<ProjectManager>();
_workspaceContext = workspaceContext;
_protocolManager = protocolManager;
_messengers = new List<Messenger<ProjectContextSnapshot>>
{
new DependencyDiagnosticsMessenger(Transmit),
new ReferencesMessenger(Transmit),
new DependenciesMessenger(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)
{
// TODO: review error handing logic
_log.LogError($"Error 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()
{
_log.LogInformation("Begin draining inbox.");
while (ProcessMessage()) { }
_log.LogInformation("Finish draining inbox.");
}
private bool ProcessMessage()
{
Message message;
lock (_inbox)
{
if (!_inbox.Any())
{
return false;
}
message = _inbox.Dequeue();
Debug.Assert(message != null);
}
_log.LogInformation($"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:
case MessageTypes.RestoreComplete:
_refreshDependencies.Value = 0;
break;
case MessageTypes.FilesChanged:
_filesChanged.Value = 0;
break;
}
return true;
}
private void Initialize(Message message)
{
if (_initializedContext != null)
{
_log.LogWarning($"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);
_log.LogInformation($"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();
_refreshDependencies.ClearAssigned();
newSnapshot = ProjectSnapshot.Create(_appPath.Value, _configure.Value, _workspaceContext, _remote.ProjectSearchPaths);
}
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,111 @@
// 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;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace Microsoft.DotNet.ProjectModel.Server
{
internal class ProtocolManager
{
/// <summary>
/// Environment variable for overriding protocol.
/// </summary>
public const string EnvDthProtocol = "DTH_PROTOCOL";
private readonly ILogger _log;
public ProtocolManager(int maxVersion, ILoggerFactory loggerFactory)
{
MaxVersion = maxVersion;
_log = loggerFactory.CreateLogger<ProtocolManager>();
// 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;
}
_log.LogInformation("Initializing the protocol negotiation.");
if (EnvironmentOverridden)
{
_log.LogInformation($"DTH protocol negotiation is override by environment variable {EnvDthProtocol} and set to {CurrentVersion}.");
return;
}
var tokenValue = message.Payload?["Version"];
if (tokenValue == null)
{
_log.LogInformation("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.
_log.LogInformation("Protocol negotiation failed. Protocol version 0 is invalid.");
return;
}
CurrentVersion = Math.Min(preferredVersion, MaxVersion);
_log.LogInformation($"Protocol negotiation successed. Use protocol {CurrentVersion}");
if (message.Sender != null)
{
_log.LogInformation("Respond to protocol negotiation.");
message.Sender.Transmit(Message.FromPayload(
MessageTypes.ProtocolVersion,
0,
new { Version = CurrentVersion }));
}
else
{
_log.LogInformation($"{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;
}
}
}