Add ProjectModel server

This commit is contained in:
Troy Dai 2015-12-09 09:57:45 -08:00 committed by Troy Dai
parent 27794b89ae
commit 935cd4e281
70 changed files with 3602 additions and 78 deletions

4
.gitignore vendored
View file

@ -46,6 +46,10 @@ bld/
# Visual Studio 2015 cache/options directory
.vs/
# Visual Studio Code cache/options directory
.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

View file

@ -60,6 +60,9 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0722D325-24C8-4E83-B5AF-0A083E7F0749}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MultiProjectValidator", "tools\MultiProjectValidator\MultiProjectValidator.xproj", "{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Server", "src\Microsoft.DotNet.ProjectModel.Server\Microsoft.DotNet.ProjectModel.Server.xproj", "{1EA9AF94-5494-40DD-A05B-9D564572CCFC}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Server.Tests", "test\Microsoft.DotNet.ProjectModel.Server.Tests\Microsoft.DotNet.ProjectModel.Server.Tests.xproj", "{11C77123-E4DA-499F-8900-80C88C2C69F2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.DependencyModel", "src\Microsoft.Extensions.DependencyModel\Microsoft.Extensions.DependencyModel.xproj", "{688870C8-9843-4F9E-8576-D39290AD0F25}"
EndProject
@ -477,6 +480,38 @@ Global
{74F25188-BF63-4BF3-879B-B6CDB11ED608}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{74F25188-BF63-4BF3-879B-B6CDB11ED608}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{74F25188-BF63-4BF3-879B-B6CDB11ED608}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Debug|x64.ActiveCfg = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Debug|x64.Build.0 = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Release|Any CPU.Build.0 = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Release|x64.ActiveCfg = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.Release|x64.Build.0 = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{1EA9AF94-5494-40DD-A05B-9D564572CCFC}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Debug|x64.ActiveCfg = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Debug|x64.Build.0 = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Release|Any CPU.Build.0 = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Release|x64.ActiveCfg = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.Release|x64.Build.0 = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{11C77123-E4DA-499F-8900-80C88C2C69F2}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -507,5 +542,7 @@ Global
{08A68C6A-86F6-4ED2-89A7-B166D33E9F85} = {0722D325-24C8-4E83-B5AF-0A083E7F0749}
{688870C8-9843-4F9E-8576-D39290AD0F25} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{74F25188-BF63-4BF3-879B-B6CDB11ED608} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{1EA9AF94-5494-40DD-A05B-9D564572CCFC} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{11C77123-E4DA-499F-8900-80C88C2C69F2} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
EndGlobalSection
EndGlobal

View file

@ -76,7 +76,15 @@ Download it from https://www.cmake.org
# Restore packages
header "Restoring packages"
& "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache
& "$DnxRoot\dnu" restore "$RepoRoot\src" --quiet --runtime "$Rid" --no-cache
& "$DnxRoot\dnu" restore "$RepoRoot\test" --quiet --runtime "$Rid" --no-cache
& "$DnxRoot\dnu" restore "$RepoRoot\tools" --quiet --runtime "$Rid" --no-cache
$oldErrorAction=$ErrorActionPreference
$ErrorActionPreference="SilentlyContinue"
& "$DnxRoot\dnu" restore "$RepoRoot\testapp" --quiet --runtime "$Rid" --no-cache 2>&1 | Out-Null
$ErrorActionPreference=$oldErrorAction
if (!$?) {
Write-Host "Command failed: " "$DnxRoot\dnu" restore "$RepoRoot" --quiet --runtime "$Rid" --no-cache
Exit 1

View file

@ -75,7 +75,12 @@ else
PREFIX="$(cd -P "$(dirname "$DOTNET_PATH")/.." && pwd)"
header "Restoring packages"
$DNX_ROOT/dnu restore "$REPOROOT" --quiet --runtime "$RID" --no-cache
$DNX_ROOT/dnu restore "$REPOROOT/src" --quiet --runtime "$RID" --no-cache
$DNX_ROOT/dnu restore "$REPOROOT/test" --quiet --runtime "$RID" --no-cache
$DNX_ROOT/dnu restore "$REPOROOT/tools" --quiet --runtime "$RID" --no-cache
set +e
$DNX_ROOT/dnu restore "$REPOROOT/testapp" --quiet --runtime "$RID" --no-cache >/dev/null 2>&1
set -e
fi
header "Building corehost"

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, ProjectContextManager> _projectContextManagers;
public ConnectionContext(Socket acceptedSocket,
string hostName,
ProtocolManager protocolManager,
WorkspaceContext workspaceContext,
IDictionary<int, ProjectContextManager> projectContextManagers,
ILoggerFactory loggerFactory)
{
_hostName = hostName;
_projectContextManagers = projectContextManagers;
_queue = new ProcessingQueue(new NetworkStream(acceptedSocket), loggerFactory);
_queue.OnReceive += message =>
{
if (protocolManager.IsProtocolNegotiation(message))
{
message.Sender = this;
protocolManager.Negotiate(message);
}
else
{
message.Sender = this;
ProjectContextManager keeper;
if (!_projectContextManagers.TryGetValue(message.ContextId, out keeper))
{
keeper = new ProjectContextManager(message.ContextId,
loggerFactory,
workspaceContext,
protocolManager);
_projectContextManagers[message.ContextId] = keeper;
}
keeper.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,23 @@
// 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,
FrameworkReferenceResolver resolver)
{
return new FrameworkData
{
ShortName = framework.GetShortFolderName(),
FrameworkName = framework.DotNetFrameworkName,
FriendlyName = framework.Framework,
RedistListPath = resolver.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,13 @@
// 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 Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.InternalModels
{
internal class DependencyInfo
{
public Dictionary<string, DependencyDescription> Dependencies { get; set; }
}
}

View file

@ -0,0 +1,83 @@
// 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.InternalModels
{
internal class ProjectInfo
{
public ProjectInfo(ProjectContext context,
string configuration,
IEnumerable<string> currentSearchPaths)
{
var allExports = context.CreateExporter(configuration).GetAllExports().ToList();
var allDiagnostics = context.LibraryManager.GetAllDiagnostics();
Context = context;
Configuration = configuration;
var allSourceFiles = new List<string>(context.ProjectFile.Files.SourceFiles);
var allFileReferences = new List<string>();
foreach (var export in allExports)
{
allSourceFiles.AddRange(export.SourceReferences);
allFileReferences.AddRange(export.CompilationAssemblies.Select(asset => asset.ResolvedPath));
}
SourceFiles = allSourceFiles.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(path => path).ToList();
CompilationAssembiles = allFileReferences.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(path => path).ToList();
var allProjectReferences = new List<ProjectReferenceDescription>();
var allDependencyDiagnostics = new List<DiagnosticMessage>();
allDependencyDiagnostics.AddRange(context.LibraryManager.GetAllDiagnostics());
allDependencyDiagnostics.AddRange(DependencyTypeChangeFinder.Diagnose(Context, currentSearchPaths));
var diagnosticsLookup = allDependencyDiagnostics.ToLookup(d => d.Source);
Dependencies = new Dictionary<string, DependencyDescription>();
foreach (var library in context.LibraryManager.GetLibraries())
{
var diagnostics = diagnosticsLookup[library].ToList();
var description = DependencyDescription.Create(library, diagnostics);
Dependencies[description.Name] = description;
if (library is ProjectDescription && library.Identity.Name != context.ProjectFile.Name)
{
allProjectReferences.Add(ProjectReferenceDescription.Create((ProjectDescription)library));
}
}
DependencyDiagnostics = allDependencyDiagnostics;
ProjectReferences = allProjectReferences.OrderBy(reference => reference.Name).ToList();
}
public string Configuration { get; }
public ProjectContext Context { get; }
public string RootDependency => Context.ProjectFile.Name;
public NuGetFramework Framework => Context.TargetFramework;
public CommonCompilerOptions CompilerOptions => Context.ProjectFile.GetCompilerOptions(Framework, Configuration);
public IReadOnlyList<string> SourceFiles { get; }
public IReadOnlyList<string> CompilationAssembiles { get; }
public IReadOnlyList<ProjectReferenceDescription> ProjectReferences { get; }
public IReadOnlyList<DiagnosticMessage> DependencyDiagnostics { get; }
public Dictionary<string, DependencyDescription> Dependencies { get; }
}
}

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 System.Collections.Generic;
using Microsoft.DotNet.ProjectModel.Server.Models;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel.Server.InternalModels
{
internal class ProjectSnapshot
{
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 string RootDependency { get; set; }
}
}

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.Linq;
using System.Collections.Generic;
using System;
namespace Microsoft.DotNet.ProjectModel.Server.InternalModels
{
internal class ProjectState
{
public static ProjectState Create(string appPath,
string configuration,
WorkspaceContext workspaceContext,
IEnumerable<string> currentSearchPaths)
{
var projectContextsCollection = workspaceContext.GetProjectContextCollection(appPath);
if (!projectContextsCollection.ProjectContexts.Any())
{
throw new InvalidOperationException($"Unable to find project.json in '{appPath}'");
}
var project = projectContextsCollection.ProjectContexts.First().ProjectFile;
var projectDiagnostics = new List<DiagnosticMessage>(projectContextsCollection.ProjectDiagnostics);
var projectInfos = new List<ProjectInfo>();
foreach (var projectContext in projectContextsCollection.ProjectContexts)
{
projectInfos.Add(new ProjectInfo(
projectContext,
configuration,
currentSearchPaths));
}
return new ProjectState(project, projectDiagnostics, projectInfos);
}
private ProjectState(Project project, List<DiagnosticMessage> projectDiagnostics, List<ProjectInfo> projectInfos)
{
Project = project;
Projects = projectInfos;
Diagnostics = projectDiagnostics;
}
public Project Project { get; }
public IReadOnlyList<ProjectInfo> Projects { get; }
public IReadOnlyList<DiagnosticMessage> Diagnostics { get; }
}
}

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.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Server.Helpers;
using Microsoft.DotNet.ProjectModel.Server.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Models;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel.Server
{
internal class Snapshot
{
public static Snapshot CreateFromProject(Project project)
{
GlobalSettings globalSettings;
var projectSearchPaths = project.ResolveSearchPaths(out globalSettings);
return new Snapshot(project, globalSettings?.FilePath, projectSearchPaths);
}
public Snapshot()
{
Projects = new Dictionary<NuGetFramework, ProjectSnapshot>();
}
public Snapshot(Project project, string globalJsonPath, IEnumerable<string> projectSearchPaths)
: this()
{
Project = project;
GlobalJsonPath = globalJsonPath;
ProjectSearchPaths = projectSearchPaths.ToList();
}
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, ProjectSnapshot> Projects { get; }
}
}

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.
namespace Microsoft.DotNet.ProjectModel.Server
{
public class MessageTypes
{
// Incoming
public const string ProjectContexts = nameof(ProjectContexts);
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);
public const string AllDiagnostics = nameof(AllDiagnostics);
}
}

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.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal class CompilerOptionsMessenger : Messenger<ProjectSnapshot>
{
public CompilerOptionsMessenger(Action<string, object> transmit)
: base(MessageTypes.CompilerOptions, transmit)
{ }
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
{
return remote.CompilerOptions != null &&
Equals(local.CompilerOptions, remote.CompilerOptions);
}
protected override object CreatePayload(ProjectSnapshot local)
{
return new CompilationOptionsMessagePayload
{
Framework = local.TargetFramework.ToPayload(_resolver),
Options = local.CompilerOptions
};
}
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot remote)
{
remote.CompilerOptions = local.CompilerOptions;
}
private class CompilationOptionsMessagePayload
{
public FrameworkData Framework { get; set; }
public CommonCompilerOptions Options { get; set; }
}
}
}

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.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal class DependenciesMessenger : Messenger<ProjectSnapshot>
{
public DependenciesMessenger(Action<string, object> transmit)
: base(MessageTypes.Dependencies, transmit)
{ }
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot 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(ProjectSnapshot local)
{
return new DependenciesMessage
{
Framework = local.TargetFramework.ToPayload(_resolver),
RootDependency = local.RootDependency,
Dependencies = local.Dependencies
};
}
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot 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,35 @@
// 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.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal class DependencyDiagnosticsMessenger : Messenger<ProjectSnapshot>
{
public DependencyDiagnosticsMessenger(Action<string, object> transmit)
: base(MessageTypes.DependencyDiagnostics, transmit)
{ }
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
{
return remote.DependencyDiagnostics != null &&
Enumerable.SequenceEqual(local.DependencyDiagnostics, remote.DependencyDiagnostics);
}
protected override object CreatePayload(ProjectSnapshot local)
{
return new DiagnosticsListMessage(
local.DependencyDiagnostics,
local.TargetFramework?.ToPayload(_resolver));
}
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot 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<Snapshot>
{
public GlobalErrorMessenger(Action<string, object> transmit)
: base(MessageTypes.Error, transmit)
{ }
protected override bool CheckDifference(Snapshot local, Snapshot remote)
{
return remote != null && Equals(local.GlobalErrorMessage, remote.GlobalErrorMessage);
}
protected override object CreatePayload(Snapshot local)
{
return local.GlobalErrorMessage;
}
protected override void SetValue(Snapshot local, Snapshot remote)
{
remote.GlobalErrorMessage = local.GlobalErrorMessage;
}
}
}

View file

@ -0,0 +1,40 @@
// 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.Resolution;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal abstract class Messenger<T> where T : class
{
protected readonly FrameworkReferenceResolver _resolver;
protected readonly Action<string, object> _transmit;
public Messenger(string messageType, Action<string, object> transmit)
{
_resolver = FrameworkReferenceResolver.Default;
_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<Snapshot>
{
public ProjectDiagnosticsMessenger(Action<string, object> transmit)
: base(MessageTypes.Diagnostics, transmit)
{ }
protected override bool CheckDifference(Snapshot local, Snapshot remote)
{
return remote.ProjectDiagnostics != null &&
Enumerable.SequenceEqual(local.ProjectDiagnostics, remote.ProjectDiagnostics);
}
protected override object CreatePayload(Snapshot local)
{
return new DiagnosticsListMessage(local.ProjectDiagnostics);
}
protected override void SetValue(Snapshot local, Snapshot remote)
{
remote.ProjectDiagnostics = local.ProjectDiagnostics;
}
}
}

View file

@ -0,0 +1,70 @@
// 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.Resolution;
using Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal class ProjectInformationMessenger : Messenger<Snapshot>
{
public ProjectInformationMessenger(Action<string, object> transmit)
: base(MessageTypes.ProjectInformation, transmit)
{ }
protected override bool CheckDifference(Snapshot local, Snapshot 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(Snapshot local)
{
return new ProjectInformation(local.Project, local.GlobalJsonPath, local.ProjectSearchPaths, _resolver);
}
protected override void SetValue(Snapshot local, Snapshot remote)
{
remote.Project = local.Project;
remote.GlobalJsonPath = local.GlobalJsonPath;
remote.ProjectSearchPaths = local.ProjectSearchPaths;
}
private class ProjectInformation
{
public ProjectInformation(Project project,
string gloablJsonPath,
IEnumerable<string> projectSearchPath,
FrameworkReferenceResolver resolver)
{
Name = project.Name;
Frameworks = project.GetTargetFrameworks().Select(f => f.FrameworkName.ToPayload(resolver)).ToList();
Configurations = project.GetConfigurations().ToList();
Commands = project.Commands;
ProjectSearchPaths = new List<string>(projectSearchPath);
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,49 @@
// 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.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal class ReferencesMessenger : Messenger<ProjectSnapshot>
{
public ReferencesMessenger(Action<string, object> transmit)
: base(MessageTypes.References, transmit)
{ }
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
{
return remote.FileReferences != null &&
remote.ProjectReferences != null &&
Enumerable.SequenceEqual(local.FileReferences, remote.FileReferences) &&
Enumerable.SequenceEqual(local.ProjectReferences, remote.ProjectReferences);
}
protected override object CreatePayload(ProjectSnapshot local)
{
return new ReferencesMessage
{
Framework = local.TargetFramework.ToPayload(_resolver),
ProjectReferences = local.ProjectReferences,
FileReferences = local.FileReferences
};
}
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot 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,46 @@
// 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.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Models;
namespace Microsoft.DotNet.ProjectModel.Server.Messengers
{
internal class SourcesMessenger : Messenger<ProjectSnapshot>
{
public SourcesMessenger(Action<string, object> transmit)
: base(MessageTypes.Sources, transmit)
{ }
protected override bool CheckDifference(ProjectSnapshot local, ProjectSnapshot remote)
{
return remote.SourceFiles != null &&
Enumerable.SequenceEqual(local.SourceFiles, remote.SourceFiles);
}
protected override object CreatePayload(ProjectSnapshot local)
{
return new SourcesMessagePayload
{
Framework = local.TargetFramework.ToPayload(_resolver),
Files = local.SourceFiles,
GeneratedFiles = new Dictionary<string, string>()
};
}
protected override void SetValue(ProjectSnapshot local, ProjectSnapshot remote)
{
remote.SourceFiles = local.SourceFiles;
}
private class SourcesMessagePayload
{
public FrameworkData Framework { get; set; }
public IReadOnlyList<string> Files { get; set; }
public IDictionary<string, string> GeneratedFiles { get; set; }
}
}
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.23107" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.23107</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>1ea9af94-5494-40dd-a05b-9d564572ccfc</ProjectGuid>
<RootNamespace>Microsoft.DotNet.ProjectModel.Server</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,87 @@
// 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.Graph;
namespace Microsoft.DotNet.ProjectModel.Server.Models
{
public class DependencyDescription
{
private DependencyDescription() { }
public static DependencyDescription Create(LibraryDescription library, IEnumerable<DiagnosticMessage> diagnostics)
{
return new DependencyDescription
{
Name = library.Identity.Name,
DisplayName = GetLibraryDisplayName(library),
Version = library.Identity.Version?.ToString(),
Type = library.Identity.Type.Value,
Resolved = library.Resolved,
Path = library.Path,
Dependencies = library.Dependencies.Select(dependency => new DependencyItem
{
Name = dependency.Name,
Version = dependency.VersionRange?.ToString() // TODO: review
}),
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))
};
}
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();
}
private static string GetLibraryDisplayName(LibraryDescription library)
{
var name = library.Identity.Name;
if (library.Identity.Type == LibraryType.ReferenceAssembly && name.StartsWith("fx/"))
{
name = name.Substring(3);
}
return name;
}
}
}

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,70 @@
// 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;
using Newtonsoft.Json.Linq;
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 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,
};
}
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();
}
}
}

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,164 @@
// 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.DotNet.ProjectModel.Resolution;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.ProjectModel.Server
{
public class Program
{
private readonly Dictionary<int, ProjectContextManager> _projectContextManagers;
private readonly WorkspaceContext _workspaceContext;
private readonly ProtocolManager _protocolManager;
private readonly ILoggerFactory _loggerFactory;
private readonly string _hostName;
private readonly int _port;
private Socket _listenSocket;
public Program(int intPort, string hostName, ILoggerFactory loggerFactory)
{
_port = intPort;
_hostName = hostName;
_loggerFactory = loggerFactory;
_protocolManager = new ProtocolManager(maxVersion: 4, loggerFactory: _loggerFactory);
_workspaceContext = WorkspaceContext.Create();
_projectContextManagers = new Dictionary<int, ProjectContextManager>();
}
public static int Main(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("--hostPid", "The process id of the host", CommandOptionType.SingleValue);
var hostname = app.Option("--hostName", "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<Program>();
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 Program(intPort, hostname.Value(), loggerFactory);
program.OpenChannel();
return 0;
});
return app.Execute(args);
}
public void OpenChannel()
{
var logger = _loggerFactory.CreateLogger($"OpenChannel");
// This fixes the mono incompatibility but ties it to ipv4 connections
_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,
_projectContextManagers,
_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,401 @@
// 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.Resolution;
using Microsoft.DotNet.ProjectModel.Server.Helpers;
using Microsoft.DotNet.ProjectModel.Server.InternalModels;
using Microsoft.DotNet.ProjectModel.Server.Messengers;
using Microsoft.DotNet.ProjectModel.Server.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel.Server
{
internal class ProjectContextManager
{
private readonly ILogger _log;
private readonly object _processingLock = new object();
private readonly Queue<Message> _inbox = new Queue<Message>();
private readonly ProtocolManager _protocolManager;
private readonly List<ConnectionContext> _waitingForDiagnostics = new List<ConnectionContext>();
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 Snapshot _local = new Snapshot();
private Snapshot _remote = new Snapshot();
private readonly WorkspaceContext _workspaceContext;
private int? _contextProtocolVersion;
private readonly List<Messenger<ProjectSnapshot>> _messengers;
private ProjectDiagnosticsMessenger _projectDiagnosticsMessenger;
private GlobalErrorMessenger _globalErrorMessenger;
private ProjectInformationMessenger _projectInforamtionMessenger;
public ProjectContextManager(int contextId,
ILoggerFactory loggerFactory,
WorkspaceContext workspaceContext,
ProtocolManager protocolManager)
{
Id = contextId;
_log = loggerFactory.CreateLogger<ProjectContextManager>();
_workspaceContext = workspaceContext;
_protocolManager = protocolManager;
_messengers = new List<Messenger<ProjectSnapshot>>
{
new DependencyDiagnosticsMessenger(Transmit),
new DependenciesMessenger(Transmit),
new CompilerOptionsMessenger(Transmit),
new ReferencesMessenger(Transmit),
new SourcesMessenger(Transmit)
};
_projectDiagnosticsMessenger = new ProjectDiagnosticsMessenger(Transmit);
_globalErrorMessenger = new GlobalErrorMessenger(TransmitDiagnostics);
_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 => ((ProjectContextManager)state).ProcessLoop(), this);
}
private void Transmit(string messageType, object payload)
{
var message = Message.FromPayload(messageType, Id, payload);
_initializedContext.Transmit(message);
}
private void TransmitDiagnostics(string messageType, object payload)
{
var message = Message.FromPayload(messageType, Id, payload);
_initializedContext.Transmit(message);
foreach (var connection in _waitingForDiagnostics)
{
connection.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;
foreach (var connection in _waitingForDiagnostics)
{
connection.Transmit(message);
}
_waitingForDiagnostics.Clear();
}
}
private void DoProcessLoop()
{
while (true)
{
DrainInbox();
var allDiagnostics = new List<DiagnosticMessageGroup>();
UpdateProjectStates();
SendOutgingMessages(allDiagnostics);
SendDiagnostics(allDiagnostics);
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;
case MessageTypes.GetDiagnostics:
_waitingForDiagnostics.Add(message.Sender);
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 UpdateProjectStates()
{
ProjectState state = null;
if (_appPath.WasAssigned || _configure.WasAssigned || _filesChanged.WasAssigned || _refreshDependencies.WasAssigned)
{
_appPath.ClearAssigned();
_configure.ClearAssigned();
_filesChanged.ClearAssigned();
_refreshDependencies.ClearAssigned();
state = ProjectState.Create(_appPath.Value, _configure.Value, _workspaceContext, _remote.ProjectSearchPaths);
}
if (state == null)
{
return false;
}
var frameworkReferenceResolver = FrameworkReferenceResolver.Default;
_local = Snapshot.CreateFromProject(state.Project);
_local.ProjectDiagnostics = state.Diagnostics;
foreach (var projectInfo in state.Projects)
{
var projectWorkd = new ProjectSnapshot
{
RootDependency = projectInfo.RootDependency,
TargetFramework = projectInfo.Framework,
SourceFiles = new List<string>(projectInfo.SourceFiles),
CompilerOptions = projectInfo.CompilerOptions,
ProjectReferences = projectInfo.ProjectReferences,
FileReferences = projectInfo.CompilationAssembiles,
DependencyDiagnostics = projectInfo.DependencyDiagnostics,
Dependencies = projectInfo.Dependencies
};
_local.Projects[projectInfo.Framework] = projectWorkd;
}
return true;
}
private void SendOutgingMessages(List<DiagnosticMessageGroup> diagnostics)
{
_projectInforamtionMessenger.UpdateRemote(_local, _remote);
_projectDiagnosticsMessenger.UpdateRemote(_local, _remote);
if (_local.ProjectDiagnostics != null)
{
diagnostics.Add(new DiagnosticMessageGroup(_local.ProjectDiagnostics));
}
var unprocessedFrameworks = new HashSet<NuGetFramework>(_remote.Projects.Keys);
foreach (var pair in _local.Projects)
{
ProjectSnapshot localProjectSnapshot = pair.Value;
ProjectSnapshot remoteProjectSnapshot;
if (!_remote.Projects.TryGetValue(pair.Key, out remoteProjectSnapshot))
{
remoteProjectSnapshot = new ProjectSnapshot();
_remote.Projects[pair.Key] = remoteProjectSnapshot;
}
if (localProjectSnapshot.DependencyDiagnostics != null)
{
diagnostics.Add(new DiagnosticMessageGroup(
localProjectSnapshot.TargetFramework,
localProjectSnapshot.DependencyDiagnostics));
}
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.Projects.Remove(framework);
}
}
private void SendDiagnostics(List<DiagnosticMessageGroup> allDiagnostics)
{
_log.LogInformation($"SendDiagnostics, {allDiagnostics.Count()} diagnostics, {_waitingForDiagnostics.Count()} waiting for diagnostics.");
if (!allDiagnostics.Any())
{
return;
}
_globalErrorMessenger.UpdateRemote(_local, _remote);
// Group all of the diagnostics into group by target framework
var messages = new List<DiagnosticsListMessage>();
foreach (var g in allDiagnostics.GroupBy(g => g.Framework))
{
var frameworkData = g.Key?.ToPayload(FrameworkReferenceResolver.Default);
var messageGroup = g.SelectMany(d => d.Diagnostics).ToList();
messages.Add(new DiagnosticsListMessage(messageGroup, frameworkData));
}
// Send all diagnostics back
TransmitDiagnostics(
MessageTypes.AllDiagnostics,
messages.Select(d => JToken.FromObject(d)));
_waitingForDiagnostics.Clear();
}
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;
}
}
}

View file

@ -0,0 +1,34 @@
{
"name": "dotnet-projectmodel-server",
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"System.Console": "4.0.0-beta-23504",
"System.Collections": "4.0.11-beta-23504",
"System.Diagnostics.Process": "4.1.0-beta-23504",
"System.Linq": "4.0.1-beta-23504",
"System.Linq.Expressions": "4.0.11-beta-23504",
"System.Net.Sockets": "4.1.0-beta-23504",
"System.Runtime.Serialization.Primitives": "4.1.0-beta-23504",
"System.Threading.ThreadPool": "4.0.10-beta-23504",
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.Extensions.CommandLineUtils.Sources": {
"type": "build",
"version": "1.0.0-*"
},
"Microsoft.Extensions.Logging": "1.0.0-*",
"Microsoft.Extensions.Logging.Console": "1.0.0-*",
"Newtonsoft.Json": "7.0.1"
},
"frameworks": {
"dnxcore50": { }
},
"scripts": {
"postcompile": [
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.dll\"",
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.pdb\""
]
}
}

View file

@ -31,6 +31,28 @@ namespace Microsoft.DotNet.ProjectModel
public bool? PreserveCompilationContext { get; set; }
public override bool Equals(object obj)
{
var other = obj as CommonCompilerOptions;
return other != null &&
LanguageVersion == other.LanguageVersion &&
Platform == other.Platform &&
AllowUnsafe == other.AllowUnsafe &&
WarningsAsErrors == other.WarningsAsErrors &&
Optimize == other.Optimize &&
KeyFile == other.KeyFile &&
DelaySign == other.DelaySign &&
PublicSign == other.PublicSign &&
EmitEntryPoint == other.EmitEntryPoint &&
PreserveCompilationContext == other.PreserveCompilationContext &&
Enumerable.SequenceEqual(Defines ?? Enumerable.Empty<string>(), other.Defines ?? Enumerable.Empty<string>());
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static CommonCompilerOptions Combine(params CommonCompilerOptions[] options)
{
var result = new CommonCompilerOptions();
@ -102,6 +124,5 @@ namespace Microsoft.DotNet.ProjectModel
return result;
}
}
}

View file

@ -94,8 +94,7 @@ namespace Microsoft.DotNet.ProjectModel
return _compilerOptionsByConfiguration.Keys;
}
public CommonCompilerOptions GetCompilerOptions(NuGetFramework targetFramework,
string configurationName)
public CommonCompilerOptions GetCompilerOptions(NuGetFramework targetFramework, string configurationName)
{
// Get all project options and combine them
var rootOptions = GetCompilerOptions();

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;
using System.Collections.Generic;
using System.IO;
namespace Microsoft.DotNet.ProjectModel
{
public class ProjectContextCollection
{
public List<ProjectContext> ProjectContexts { get; } = new List<ProjectContext>();
public List<DiagnosticMessage> ProjectDiagnostics { get; } = new List<DiagnosticMessage>();
public string LockFilePath { get; set; }
public string ProjectFilePath { get; set; }
public DateTime LastProjectFileWriteTime { get; set; }
public DateTime LastLockFileWriteTime { get; set; }
public bool HasChanged
{
get
{
if (ProjectFilePath == null || !File.Exists(ProjectFilePath))
{
return true;
}
if (LastProjectFileWriteTime < File.GetLastWriteTime(ProjectFilePath))
{
return true;
}
if (LockFilePath == null || !File.Exists(LockFilePath))
{
return true;
}
if (LastLockFileWriteTime < File.GetLastWriteTime(LockFilePath))
{
return true;
}
return false;
}
}
public void Reset()
{
ProjectContexts.Clear();
ProjectFilePath = null;
LockFilePath = null;
LastLockFileWriteTime = DateTime.MinValue;
LastProjectFileWriteTime = DateTime.MinValue;
ProjectDiagnostics.Clear();
}
}
}

View file

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using System.Xml.Linq;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
@ -18,6 +19,8 @@ namespace Microsoft.DotNet.ProjectModel.Resolution
private static readonly NuGetFramework Dnx46 = new NuGetFramework(
FrameworkConstants.FrameworkIdentifiers.Dnx,
new Version(4, 6));
private static FrameworkReferenceResolver _default;
private readonly IDictionary<NuGetFramework, FrameworkInformation> _cache = new Dictionary<NuGetFramework, FrameworkInformation>();
@ -33,6 +36,19 @@ namespace Microsoft.DotNet.ProjectModel.Resolution
}
public string ReferenceAssembliesPath { get; }
public static FrameworkReferenceResolver Default
{
get
{
if (_default == null)
{
_default = new FrameworkReferenceResolver(ProjectContextBuilder.GetDefaultReferenceAssembliesPath());
}
return _default;
}
}
public bool TryGetAssembly(string name, NuGetFramework targetFramework, out string path, out Version version)
{

View file

@ -21,17 +21,15 @@ namespace Microsoft.DotNet.ProjectModel
= new ConcurrentDictionary<string, FileModelEntry<LockFile>>();
// key: project directory, target framework
private readonly ConcurrentDictionary<string, ProjectContextEntry> _projectContextsCache
= new ConcurrentDictionary<string, ProjectContextEntry>();
private readonly ConcurrentDictionary<string, ProjectContextCollection> _projectContextsCache
= new ConcurrentDictionary<string, ProjectContextCollection>();
private readonly HashSet<string> _projects = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private bool _needRefresh;
private WorkspaceContext(List<string> projectPaths, string configuration)
private WorkspaceContext(IEnumerable<string> projectPaths)
{
Configuration = configuration;
foreach (var path in projectPaths)
{
AddProject(path);
@ -40,8 +38,6 @@ namespace Microsoft.DotNet.ProjectModel
Refresh();
}
public string Configuration { get; }
/// <summary>
/// Create a WorkspaceContext from a given path.
///
@ -54,7 +50,7 @@ namespace Microsoft.DotNet.ProjectModel
/// If the given path points to a project.json, all the projects it referenced as well as itself
/// are added to the WorkspaceContext.
/// </summary>
public static WorkspaceContext CreateFrom(string projectPath, string configuration)
public static WorkspaceContext CreateFrom(string projectPath)
{
var projectPaths = ResolveProjectPath(projectPath);
if (projectPaths == null || !projectPaths.Any())
@ -62,13 +58,13 @@ namespace Microsoft.DotNet.ProjectModel
return null;
}
var context = new WorkspaceContext(projectPaths, configuration);
var context = new WorkspaceContext(projectPaths);
return context;
}
public static WorkspaceContext CreateFrom(string projectPath)
public static WorkspaceContext Create()
{
return CreateFrom(projectPath, "Debug");
return new WorkspaceContext(Enumerable.Empty<string>());
}
public void AddProject(string path)
@ -107,7 +103,7 @@ namespace Microsoft.DotNet.ProjectModel
foreach (var projectDirectory in basePaths)
{
var project = GetProject(projectDirectory);
var project = GetProject(projectDirectory).Model;
if (project == null)
{
continue;
@ -119,7 +115,7 @@ namespace Microsoft.DotNet.ProjectModel
{
foreach (var reference in GetProjectReferences(projectContext))
{
var referencedProject = GetProject(reference.Path);
var referencedProject = GetProject(reference.Path).Model;
if (referencedProject != null)
{
_projects.Add(referencedProject.ProjectDirectory);
@ -132,19 +128,24 @@ namespace Microsoft.DotNet.ProjectModel
}
public IReadOnlyList<ProjectContext> GetProjectContexts(string projectPath)
{
return GetProjectContextCollection(projectPath).ProjectContexts;
}
public ProjectContextCollection GetProjectContextCollection(string projectPath)
{
return _projectContextsCache.AddOrUpdate(
projectPath,
key => AddProjectContextEntry(key, null),
(key, oldEntry) => AddProjectContextEntry(key, oldEntry)).ProjectContexts;
(key, oldEntry) => AddProjectContextEntry(key, oldEntry));
}
private Project GetProject(string projectDirectory)
private FileModelEntry<Project> GetProject(string projectDirectory)
{
return _projectsCache.AddOrUpdate(
projectDirectory,
key => AddProjectEntry(key, null),
(key, oldEntry) => AddProjectEntry(key, oldEntry)).Model;
(key, oldEntry) => AddProjectEntry(key, oldEntry));
}
private LockFile GetLockFile(string projectDirectory)
@ -171,7 +172,7 @@ namespace Microsoft.DotNet.ProjectModel
if (currentEntry.IsInvalid)
{
Project project;
if (!ProjectReader.TryGetProject(projectDirectory, out project))
if (!ProjectReader.TryGetProject(projectDirectory, out project, currentEntry.Diagnostics))
{
currentEntry.Reset();
}
@ -208,23 +209,24 @@ namespace Microsoft.DotNet.ProjectModel
return currentEntry;
}
private ProjectContextEntry AddProjectContextEntry(string projectDirectory,
ProjectContextEntry currentEntry)
private ProjectContextCollection AddProjectContextEntry(string projectDirectory,
ProjectContextCollection currentEntry)
{
if (currentEntry == null)
{
// new entry required
currentEntry = new ProjectContextEntry();
currentEntry = new ProjectContextCollection();
}
var project = GetProject(projectDirectory);
if (project == null)
var projectEntry = GetProject(projectDirectory);
if (projectEntry.Model == null)
{
// project doesn't exist anymore
currentEntry.Reset();
return currentEntry;
}
var project = projectEntry.Model;
if (currentEntry.HasChanged)
{
currentEntry.Reset();
@ -232,7 +234,7 @@ namespace Microsoft.DotNet.ProjectModel
foreach (var framework in project.GetTargetFrameworks())
{
var builder = new ProjectContextBuilder()
.WithProjectResolver(path => GetProject(path))
.WithProjectResolver(path => GetProject(path).Model)
.WithLockFileResolver(path => GetLockFile(path))
.WithProject(project)
.WithTargetFramework(framework.FrameworkName);
@ -249,6 +251,8 @@ namespace Microsoft.DotNet.ProjectModel
currentEntry.LockFilePath = lockFilePath;
currentEntry.LastLockFileWriteTime = File.GetLastWriteTime(lockFilePath);
}
currentEntry.ProjectDiagnostics.AddRange(projectEntry.Diagnostics);
}
return currentEntry;
@ -262,6 +266,8 @@ namespace Microsoft.DotNet.ProjectModel
public string FilePath { get; set; }
public List<DiagnosticMessage> Diagnostics { get; } = new List<DiagnosticMessage>();
public void UpdateLastWriteTime()
{
_lastWriteTime = File.GetLastWriteTime(FilePath);
@ -289,60 +295,11 @@ namespace Microsoft.DotNet.ProjectModel
{
Model = null;
FilePath = null;
Diagnostics.Clear();
_lastWriteTime = DateTime.MinValue;
}
}
private class ProjectContextEntry
{
public List<ProjectContext> ProjectContexts { get; } = new List<ProjectContext>();
public string LockFilePath { get; set; }
public string ProjectFilePath { get; set; }
public DateTime LastProjectFileWriteTime { get; set; }
public DateTime LastLockFileWriteTime { get; set; }
public bool HasChanged
{
get
{
if (ProjectFilePath == null || !File.Exists(ProjectFilePath))
{
return true;
}
if (LastProjectFileWriteTime < File.GetLastWriteTime(ProjectFilePath))
{
return true;
}
if (LockFilePath == null || !File.Exists(LockFilePath))
{
return true;
}
if (LastLockFileWriteTime < File.GetLastWriteTime(LockFilePath))
{
return true;
}
return false;
}
}
public void Reset()
{
ProjectContexts.Clear();
ProjectFilePath = null;
LockFilePath = null;
LastLockFileWriteTime = DateTime.MinValue;
LastProjectFileWriteTime = DateTime.MinValue;
}
}
private static string NormalizeProjectPath(string path)
{
if (File.Exists(path) &&

View file

@ -0,0 +1,262 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Server.Tests.Helpers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public class DthStartupTests : IClassFixture<TestHelper>
{
private readonly TestHelper _testHelper;
public DthStartupTests(TestHelper helper)
{
_testHelper = helper;
}
[Fact]
public void DthStartup_GetProjectInformation()
{
var projectPath = _testHelper.FindSampleProject("EmptyConsoleApp");
Assert.NotNull(projectPath);
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
client.Initialize(projectPath);
var projectInformation = client.DrainTillFirst(MessageTypes.ProjectInformation)
.EnsureSource(server, client)
.RetrievePayloadAs<JObject>()
.AssertProperty("Name", "EmptyConsoleApp");
projectInformation.RetrievePropertyAs<JArray>("Configurations")
.AssertJArrayCount(2)
.AssertJArrayContains("Debug")
.AssertJArrayContains("Release");
var frameworkShortNames = projectInformation.RetrievePropertyAs<JArray>("Frameworks")
.AssertJArrayCount(2)
.Select(f => f["ShortName"].Value<string>());
Assert.Contains("dnxcore50", frameworkShortNames);
Assert.Contains("dnx451", frameworkShortNames);
}
}
[Theory]
[InlineData(4, 4)]
[InlineData(5, 4)]
[InlineData(3, 3)]
public void DthStartup_ProtocolNegotiation(int requestVersion, int expectVersion)
{
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
client.SetProtocolVersion(requestVersion);
var response = client.DrainTillFirst(MessageTypes.ProtocolVersion, TimeSpan.FromDays(1));
response.EnsureSource(server, client);
Assert.Equal(expectVersion, response.Payload["Version"]?.Value<int>());
}
}
[Theory]
public void DthStartup_ProtocolNegotiation_ZeroIsNoAllowed()
{
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
client.SetProtocolVersion(0);
Assert.Throws<TimeoutException>(() =>
{
client.DrainTillFirst(MessageTypes.ProtocolVersion, timeout: TimeSpan.FromSeconds(1));
});
}
}
[Fact]
public void DthCompilation_GetDiagnostics_OnEmptyConsoleApp()
{
var projectPath = _testHelper.FindSampleProject("EmptyConsoleApp");
Assert.NotNull(projectPath);
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
// Drain the inital messages
client.Initialize(projectPath);
client.SendPayLoad(projectPath, "GetDiagnostics");
var diagnosticsGroup = client.DrainTillFirst("AllDiagnostics")
.EnsureSource(server, client)
.RetrievePayloadAs<JArray>()
.AssertJArrayCount(3);
foreach (var group in diagnosticsGroup)
{
group.AsJObject()
.AssertProperty<JArray>("Errors", errorsArray => !errorsArray.Any())
.AssertProperty<JArray>("Warnings", warningsArray => !warningsArray.Any());
}
}
}
[Theory]
[InlineData("Project", "UnresolvedProjectSample", "EmptyLibrary", "Project")]
[InlineData("Package", "UnresolvedPackageSample", "NoSuchPackage", null)]
[InlineData("Package", "IncompatiblePackageSample", "Newtonsoft.Json", "Package")]
public void DthCompilation_Initialize_UnresolvedDependency(string referenceType,
string testProjectName,
string expectedUnresolvedDependency,
string expectedUnresolvedType)
{
var projectPath = _testHelper.FindSampleProject(testProjectName);
Assert.NotNull(projectPath);
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
client.Initialize(projectPath);
var unresolveDependency = client.DrainTillFirst("Dependencies")
.EnsureSource(server, client)
.RetrieveDependency(expectedUnresolvedDependency);
unresolveDependency.AssertProperty("Name", expectedUnresolvedDependency)
.AssertProperty("DisplayName", expectedUnresolvedDependency)
.AssertProperty("Resolved", false)
.AssertProperty("Type", expectedUnresolvedType);
if (expectedUnresolvedType == "Project")
{
unresolveDependency.AssertProperty("Path", Path.Combine(Path.GetDirectoryName(projectPath),
expectedUnresolvedDependency,
Project.FileName));
}
else
{
Assert.False(unresolveDependency["Path"].HasValues);
}
var referencesMessage = client.DrainTillFirst("References", TimeSpan.FromDays(1))
.EnsureSource(server, client);
if (referenceType == "Project")
{
var expectedUnresolvedProjectPath = Path.Combine(Path.GetDirectoryName(projectPath),
expectedUnresolvedDependency,
Project.FileName);
referencesMessage.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("ProjectReferences")
.AssertJArrayCount(1)
.RetrieveArraryElementAs<JObject>(0)
.AssertProperty("Name", expectedUnresolvedDependency)
.AssertProperty("Path", expectedUnresolvedProjectPath)
.AssertProperty<JToken>("WrappedProjectPath", prop => !prop.HasValues);
}
else if (referenceType == "Package")
{
referencesMessage.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("ProjectReferences")
.AssertJArrayCount(0);
}
}
}
[Fact]
public void DthNegative_BrokenProjectPathInLockFile()
{
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
// After restore the project is copied to another place so that
// the relative path in project lock file is invalid.
var movedProjectPath = _testHelper.MoveProject("BrokenProjectPathSample");
client.Initialize(movedProjectPath);
client.DrainTillFirst("DependencyDiagnostics")
.RetrieveDependencyDiagnosticsCollection()
.RetrieveDependencyDiagnosticsErrorAt(0)
.AssertProperty<string>("FormattedMessage", message => message.Contains("error NU1002"))
.RetrievePropertyAs<JObject>("Source")
.AssertProperty("Name", "EmptyLibrary");
client.DrainTillFirst("Dependencies")
.RetrieveDependency("EmptyLibrary")
.AssertProperty<JArray>("Errors", errorsArray => errorsArray.Count == 1)
.AssertProperty<JArray>("Warnings", warningsArray => warningsArray.Count == 0)
.AssertProperty("Name", "EmptyLibrary")
.AssertProperty("Resolved", false);
}
}
[Fact(Skip = "Require dotnet restore integration test")]
public void DthDependencies_UpdateGlobalJson_RefreshDependencies()
{
var projectPath = _testHelper.CreateSampleProject("DthUpdateSearchPathSample");
Assert.True(Directory.Exists(projectPath));
using (var server = new DthTestServer(_testHelper.LoggerFactory))
using (var client = new DthTestClient(server))
{
var testProject = Path.Combine(projectPath, "home", "src", "MainProject");
client.Initialize(testProject);
client.DrainTillFirst("ProjectInformation")
.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("ProjectSearchPaths")
.AssertJArrayCount(2);
client.DrainTillFirst("DependencyDiagnostics")
.RetrievePayloadAs<JObject>()
.AssertProperty<JArray>("Errors", array => array.Count == 0)
.AssertProperty<JArray>("Warnings", array => array.Count == 0);
client.DrainTillFirst("Dependencies")
.RetrieveDependency("Newtonsoft.Json")
.AssertProperty("Type", "Project")
.AssertProperty("Resolved", true)
.AssertProperty<JArray>("Errors", array => array.Count == 0, _ => "Dependency shouldn't contain any error.");
// Overwrite the global.json to remove search path to ext
File.WriteAllText(
Path.Combine(projectPath, "home", GlobalSettings.FileName),
JsonConvert.SerializeObject(new { project = new string[] { "src" } }));
client.SendPayLoad(testProject, "RefreshDependencies");
client.DrainTillFirst("ProjectInformation")
.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("ProjectSearchPaths")
.AssertJArrayCount(1)
.AssertJArrayElement(0, Path.Combine(projectPath, "home", "src"));
client.DrainTillFirst("DependencyDiagnostics")
.RetrieveDependencyDiagnosticsCollection()
.RetrieveDependencyDiagnosticsErrorAt<JObject>(0)
.AssertProperty("ErrorCode", "NU1010");
client.DrainTillFirst("Dependencies")
.RetrieveDependency("Newtonsoft.Json")
.AssertProperty("Type", "")
.AssertProperty("Resolved", false)
.RetrievePropertyAs<JArray>("Errors")
.AssertJArrayCount(1)
.RetrieveArraryElementAs<JObject>(0)
.AssertProperty("ErrorCode", "NU1010");
}
}
}
}

View file

@ -0,0 +1,273 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public class DthTestClient : IDisposable
{
private readonly string _hostId;
private readonly BinaryReader _reader;
private readonly BinaryWriter _writer;
private readonly NetworkStream _networkStream;
private readonly BlockingCollection<DthMessage> _messageQueue;
private readonly CancellationTokenSource _readCancellationToken;
// Keeps track of initialized project contexts
// REVIEW: This needs to be exposed if we ever create 2 clients in order to simulate how build
// works in visual studio
private readonly Dictionary<string, int> _projectContexts = new Dictionary<string, int>();
private int _nextContextId;
public DthTestClient(DthTestServer server)
{
var socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
socket.Connect(new IPEndPoint(IPAddress.Loopback, server.Port));
_hostId = server.HostId;
_networkStream = new NetworkStream(socket);
_reader = new BinaryReader(_networkStream);
_writer = new BinaryWriter(_networkStream);
_messageQueue = new BlockingCollection<DthMessage>();
_readCancellationToken = new CancellationTokenSource();
Task.Run(() => ReadMessage(_readCancellationToken.Token), _readCancellationToken.Token);
}
public void SendPayLoad(Project project, string messageType)
{
SendPayLoad(project.ProjectDirectory, messageType);
}
public void SendPayLoad(string projectPath, string messageType)
{
int contextId;
if (!_projectContexts.TryGetValue(projectPath, out contextId))
{
Assert.True(false, $"Unable to resolve context for {projectPath}");
}
SendPayLoad(contextId, messageType);
}
public void SendPayLoad(int contextId, string messageType)
{
SendPayLoad(contextId, messageType, new { });
}
public void SendPayLoad(int contextId, string messageType, object payload)
{
lock (_writer)
{
var message = new
{
ContextId = contextId,
HostId = _hostId,
MessageType = messageType,
Payload = payload
};
_writer.Write(JsonConvert.SerializeObject(message));
}
}
public int Initialize(string projectPath)
{
var contextId = _nextContextId++;
_projectContexts[projectPath] = contextId;
SendPayLoad(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath });
return contextId;
}
public int Initialize(string projectPath, int protocolVersion)
{
var contextId = _nextContextId++;
_projectContexts[projectPath] = contextId;
SendPayLoad(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion });
return contextId;
}
public int Initialize(string projectPath, int protocolVersion, string configuration)
{
var contextId = _nextContextId++;
_projectContexts[projectPath] = contextId;
SendPayLoad(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion, Configuration = configuration });
return contextId;
}
public void SetProtocolVersion(int version)
{
SendPayLoad(0, MessageTypes.ProtocolVersion, new { Version = version });
}
public List<DthMessage> DrainMessage(int count)
{
var result = new List<DthMessage>();
while (count > 0)
{
result.Add(GetResponse(timeout: TimeSpan.FromSeconds(10)));
count--;
}
return result;
}
public List<DthMessage> DrainAllMessages()
{
return DrainAllMessages(TimeSpan.FromSeconds(10));
}
/// <summary>
/// Read all messages from pipeline till timeout
/// </summary>
/// <param name="timeout">The timeout</param>
/// <returns>All the messages in a list</returns>
public List<DthMessage> DrainAllMessages(TimeSpan timeout)
{
var result = new List<DthMessage>();
while (true)
{
try
{
result.Add(GetResponse(timeout));
}
catch (TimeoutException)
{
return result;
}
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// Read messages from pipeline until the first match
/// </summary>]
/// <param name="type">A message type</param>
/// <returns>The first match message</returns>
public DthMessage DrainTillFirst(string type)
{
return DrainTillFirst(type, TimeSpan.FromSeconds(10));
}
/// <summary>
/// Read messages from pipeline until the first match
/// </summary>
/// <param name="type">A message type</param>
/// <param name="timeout">Timeout for each read</param>
/// <returns>The first match message</returns>
public DthMessage DrainTillFirst(string type, TimeSpan timeout)
{
while (true)
{
var next = GetResponse(timeout);
if (next.MessageType == type)
{
return next;
}
}
}
/// <summary>
/// Read messages from pipeline until the first match
/// </summary>
/// <param name="type">A message type</param>
/// <param name="timeout">Timeout</param>
/// <param name="leadingMessages">All the messages read before the first match</param>
/// <returns>The first match</returns>
public DthMessage DrainTillFirst(string type, TimeSpan timeout, out List<DthMessage> leadingMessages)
{
leadingMessages = new List<DthMessage>();
while (true)
{
var next = GetResponse(timeout);
if (next.MessageType == type)
{
return next;
}
else
{
leadingMessages.Add(next);
}
}
}
public void Dispose()
{
_reader.Dispose();
_writer.Dispose();
_networkStream.Dispose();
_readCancellationToken.Cancel();
}
private void ReadMessage(CancellationToken cancellationToken)
{
while (true)
{
try
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var content = _reader.ReadString();
var message = JsonConvert.DeserializeObject<DthMessage>(content);
_messageQueue.Add(message);
}
catch (IOException)
{
// swallow
}
catch (JsonSerializationException deserializException)
{
throw new InvalidOperationException(
$"Fail to deserailze data into {nameof(DthMessage)}.",
deserializException);
}
catch (Exception ex)
{
throw ex;
}
}
}
private DthMessage GetResponse(TimeSpan timeout)
{
DthMessage message;
if (_messageQueue.TryTake(out message, timeout))
{
return message;
}
else
{
throw new TimeoutException($"Response time out after {timeout.TotalSeconds} seconds.");
}
}
}
}

View file

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public class DthTestServer : IDisposable
{
private readonly Program _program;
private readonly Thread _thread;
public DthTestServer(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory;
Port = FindFreePort();
HostId = Guid.NewGuid().ToString();
_program = new Program(Port, HostId, LoggerFactory);
_thread = new Thread(() => { _program.OpenChannel(); });
_thread.Start();
}
public string HostId { get; }
public int Port { get; }
public ILoggerFactory LoggerFactory { get; }
public void Dispose()
{
try
{
_program.Shutdown();
}
catch (InvalidOperationException)
{
// swallow the exception if the process had been terminated.
}
}
private static int FindFreePort()
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public class DthMessage
{
public string HostId { get; set; }
public string MessageType { get; set; }
public int ContextId { get; set; }
public int Version { get; set; }
public JToken Payload { get; set; }
// for ProjectContexts message only
public Dictionary<string, int> Projects { get; set; }
}
}

View file

@ -0,0 +1,81 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public static class DthMessageCollectionExtension
{
public static IList<DthMessage> GetMessagesByFramework(this IEnumerable<DthMessage> messages, FrameworkName targetFramework)
{
return messages.Where(msg => MatchesFramework(targetFramework, msg)).ToList();
}
public static IList<DthMessage> GetMessagesByType(this IEnumerable<DthMessage> messages, string typename)
{
return messages.Where(msg => string.Equals(msg.MessageType, typename)).ToList();
}
public static DthMessage RetrieveSingleMessage(this IEnumerable<DthMessage> messages,
string typename)
{
var result = messages.SingleOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal));
if (result == null)
{
if (messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) != null)
{
Assert.False(true, $"More than one {typename} messages exist.");
}
else
{
Assert.False(true, $"{typename} message doesn't exists.");
}
}
return result;
}
public static IEnumerable<DthMessage> ContainsMessage(this IEnumerable<DthMessage> messages,
string typename)
{
var contain = messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) != null;
Assert.True(contain, $"Messages collection doesn't contain message of type {typename}.");
return messages;
}
public static IEnumerable<DthMessage> AssertDoesNotContain(this IEnumerable<DthMessage> messages, string typename)
{
var notContain = messages.FirstOrDefault(msg => string.Equals(msg.MessageType, typename, StringComparison.Ordinal)) == null;
Assert.True(notContain, $"Message collection contains message of type {typename}.");
return messages;
}
private static bool MatchesFramework(FrameworkName targetFramework, DthMessage msg)
{
if (msg.Payload.Type != JTokenType.Object)
{
return false;
}
var frameworkObj = msg.Payload["Framework"];
if (frameworkObj == null || !frameworkObj.HasValues)
{
return false;
}
return string.Equals(frameworkObj.Value<string>("FrameworkName"), targetFramework.FullName, StringComparison.OrdinalIgnoreCase);
}
}
}

View file

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public static class DthMessageExtension
{
public static JObject RetrieveDependency(this DthMessage message, string dependencyName)
{
Assert.NotNull(message);
Assert.Equal(MessageTypes.Dependencies, message.MessageType);
var payload = message.Payload as JObject;
Assert.NotNull(payload);
var dependency = payload[MessageTypes.Dependencies][dependencyName] as JObject;
Assert.NotNull(dependency);
Assert.Equal(dependencyName, dependency["Name"].Value<string>());
return dependency;
}
public static DthMessage EnsureNotContainDependency(this DthMessage message, string dependencyName)
{
Assert.NotNull(message);
Assert.Equal(MessageTypes.Dependencies, message.MessageType);
var payload = message.Payload as JObject;
Assert.NotNull(payload);
Assert.True(payload[MessageTypes.Dependencies][dependencyName] == null, $"Unexpected dependency {dependencyName} exists.");
return message;
}
public static JObject RetrieveDependencyDiagnosticsCollection(this DthMessage message)
{
Assert.NotNull(message);
Assert.Equal(MessageTypes.DependencyDiagnostics, message.MessageType);
var payload = message.Payload as JObject;
Assert.NotNull(payload);
return payload;
}
public static JObject RetrieveCompilationDiagnostics(this DthMessage message, string frameworkShortName)
{
Assert.NotNull(message);
Assert.Equal(MessageTypes.AllDiagnostics, message.MessageType);
Assert.True(message.Payload is JArray);
var payload = (JArray)message.Payload;
foreach (var each in payload)
{
Assert.True(each is JObject);
var diagnosticsOfFramework = (JObject)each;
if (string.Equals(diagnosticsOfFramework["Framework"]["ShortName"].Value<string>(),
frameworkShortName,
StringComparison.OrdinalIgnoreCase))
{
return diagnosticsOfFramework;
}
}
return null;
}
public static T RetrievePayloadAs<T>(this DthMessage message)
where T : JToken
{
Assert.NotNull(message);
AssertType<T>(message.Payload, "Payload");
return (T)message.Payload;
}
/// <summary>
/// Throws if the message is not generated in communication between given server and client
/// </summary>
public static DthMessage EnsureSource(this DthMessage message, DthTestServer server, DthTestClient client)
{
if (message.HostId != server.HostId)
{
throw new Exception($"{nameof(message.HostId)} doesn't match the one of server. Expected {server.HostId} but actually {message.HostId}.");
}
return message;
}
public static void AssertType<T>(object obj, string name)
{
Assert.True(obj is T, $"{name} is not of type {typeof(T).Name}.");
}
}
}

View file

@ -0,0 +1,85 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public static class JArrayExtensions
{
public static JArray AssertJArrayEmpty(this JArray array)
{
Assert.NotNull(array);
Assert.Empty(array);
return array;
}
public static JArray AssertJArrayNotEmpty(this JArray array)
{
Assert.NotNull(array);
Assert.NotEmpty(array);
return array;
}
public static JArray AssertJArrayCount(this JArray array, int expectedCount)
{
Assert.NotNull(array);
Assert.Equal(expectedCount, array.Count);
return array;
}
public static JArray AssertJArrayElement<T>(this JArray array, int index, T expectedElementValue)
{
Assert.NotNull(array);
var element = array[index];
Assert.NotNull(element);
Assert.Equal(expectedElementValue, element.Value<T>());
return array;
}
public static JArray AssertJArrayContains<T>(this JArray array, T value)
{
AssertJArrayContains<T>(array, element => object.Equals(element, value));
return array;
}
public static JArray AssertJArrayContains<T>(this JArray array, Func<T, bool> critiera)
{
bool contains = false;
foreach (var element in array)
{
var value = element.Value<T>();
contains = critiera(value);
if (contains)
{
break;
}
}
Assert.True(contains, "JArray doesn't contains the specified element.");
return array;
}
public static T RetrieveArraryElementAs<T>(this JArray json, int index)
where T : JToken
{
Assert.NotNull(json);
Assert.True(index >= 0 && index < json.Count, "Index out of range");
var element = json[index];
DthMessageExtension.AssertType<T>(element, $"Element at {index}");
return (T)element;
}
}
}

View file

@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Server.Tests
{
public static class JObjectExtensions
{
public static JObject AsJObject(this JToken token)
{
DthMessageExtension.AssertType<JObject>(token, nameof(JToken));
return (JObject)token;
}
public static JObject RetrieveDependencyDiagnosticsErrorAt(this JObject payload, int index)
{
Assert.NotNull(payload);
return payload.RetrievePropertyAs<JArray>("Errors")
.RetrieveArraryElementAs<JObject>(index);
}
public static T RetrieveDependencyDiagnosticsErrorAt<T>(this JObject payload, int index)
where T : JToken
{
Assert.NotNull(payload);
return payload.RetrievePropertyAs<JArray>("Errors")
.RetrieveArraryElementAs<T>(index);
}
public static T RetrievePropertyAs<T>(this JObject json, string propertyName)
where T : JToken
{
Assert.NotNull(json);
var property = json[propertyName];
Assert.NotNull(property);
DthMessageExtension.AssertType<T>(property, $"Property {propertyName}");
return (T)property;
}
public static JObject AssertProperty<T>(this JObject json, string propertyName, T expectedPropertyValue)
{
Assert.NotNull(json);
var property = json[propertyName];
Assert.NotNull(property);
Assert.Equal(expectedPropertyValue, property.Value<T>());
return json;
}
public static JObject AssertProperty<T>(this JObject json, string propertyName, Func<T, bool> assertion)
{
return AssertProperty<T>(json,
propertyName,
assertion,
value => $"Assert failed on {propertyName}.");
}
public static JObject AssertProperty<T>(this JObject json, string propertyName, Func<T, bool> assertion, Func<T, string> errorMessage)
{
Assert.NotNull(json);
var property = json[propertyName];
Assert.False(property == null, $"Property {propertyName} doesn't exist.");
var propertyValue = property.Value<T>();
Assert.False(propertyValue == null, $"Property {propertyName} of type {typeof(T).Name} doesn't exist.");
Assert.True(assertion(propertyValue),
errorMessage(propertyValue));
return json;
}
}
}

View file

@ -0,0 +1,124 @@
using System;
using System.IO;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.ProjectModel.Server.Tests.Helpers
{
public class TestHelper
{
private readonly string _tempPath;
public TestHelper()
{
LoggerFactory = new LoggerFactory();
var testVerbose = Environment.GetEnvironmentVariable("DOTNET_TEST_VERBOSE");
if (testVerbose == "2")
{
LoggerFactory.AddConsole(LogLevel.Trace);
}
else if (testVerbose == "1")
{
LoggerFactory.AddConsole(LogLevel.Information);
}
else
{
LoggerFactory.AddConsole(LogLevel.Warning);
}
_tempPath = CreateTempFolder();
var dthTestProjectsFolder = Path.Combine(FindRoot(), "testapp", "DthTestProjects");
CopyFiles(dthTestProjectsFolder, _tempPath);
var logger = LoggerFactory.CreateLogger<TestHelper>();
logger.LogInformation($"Test projects are copied to {_tempPath}");
}
public ILoggerFactory LoggerFactory { get; }
public string FindSampleProject(string name)
{
var result = Path.Combine(_tempPath, "src", name);
if (Directory.Exists(result))
{
return result;
}
else
{
return null;
}
}
public string CreateSampleProject(string name)
{
var source = Path.Combine(FindRoot(), "test", name);
if (!Directory.Exists(source))
{
return null;
}
var target = Path.Combine(CreateTempFolder(), name);
CopyFiles(source, target);
return target;
}
public string MoveProject(string projectName)
{
var projectPath = FindSampleProject(projectName);
var movedProjectPath = Path.Combine(CreateTempFolder(), projectName);
CopyFiles(projectPath, movedProjectPath);
return movedProjectPath;
}
private static string FindRoot()
{
var solutionName = "Microsoft.DotNet.Cli.sln";
var root = new DirectoryInfo(Directory.GetCurrentDirectory());
while (root != null && root.GetFiles(solutionName).Length == 0)
{
root = Directory.GetParent(root.FullName);
}
if (root != null)
{
return root.FullName;
}
else
{
return null;
}
}
private static string CreateTempFolder()
{
var result = Path.GetTempFileName();
File.Delete(result);
Directory.CreateDirectory(result);
return result;
}
private static void CopyFiles(string sourceFolder, string targetFolder)
{
if (!Directory.Exists(targetFolder))
{
Directory.CreateDirectory(targetFolder);
}
foreach (var filePath in Directory.EnumerateFiles(sourceFolder))
{
var filename = Path.GetFileName(filePath);
File.Copy(filePath, Path.Combine(targetFolder, filename));
}
foreach (var folderPath in Directory.EnumerateDirectories(sourceFolder))
{
var folderName = new DirectoryInfo(folderPath).Name;
CopyFiles(folderPath, Path.Combine(targetFolder, folderName));
}
}
}
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>11c77123-e4da-499f-8900-80c88c2c69f2</ProjectGuid>
<RootNamespace>Microsoft.DotNet.ProjectModel.Server.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,15 @@
{
"dependencies": {
"System.Dynamic.Runtime": "4.0.11-*",
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.DotNet.ProjectModel.Server": "1.0.0-*",
"Newtonsoft.Json": "7.0.1",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"frameworks": {
"dnxcore50": { }
},
"commands": {
"test": "xunit.runner.aspnet"
}
}

View file

@ -0,0 +1,3 @@
{
"projects": ["src"]
}

View file

@ -0,0 +1,8 @@
{
"dependencies": {
"EmptyLibrary": ""
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,13 @@
using System;
namespace Misc.DthTestProjects.EmptyConsoleApp
{
public class Program
{
public int Main(string[] args)
{
Console.WriteLine("Hello, world.");
return 0;
}
}
}

View file

@ -0,0 +1,12 @@
{
"dependencies": { },
"frameworks": {
"dnxcore50": {
"dependencies": {
"System.Runtime": "4.0.21-*",
"System.Console": "4.0.0-*"
}
},
"dnx451": { }
}
}

View file

@ -0,0 +1,8 @@
using System;
namespace Misc.DthTestProjects.EmptyLibrary
{
public class Class
{
}
}

View file

@ -0,0 +1,11 @@
{
"dependencies": { },
"frameworks": {
"dnxcore50": {
"dependencies":{
"System.Runtime": "4.0.21-beta-*",
"System.Console": "4.0.0-beta-*"
}
}
}
}

View file

@ -0,0 +1,10 @@
{
"dependencies": { },
"frameworks": {
"dnxcore50": {
"dependencies":{
"System.Runtime": "4.0.21-*"
}
}
}
}

View file

@ -0,0 +1,14 @@
namespace FailReleaseProject
{
public class Program
{
public int Main(string[] args)
{
#if RELEASE
// fail the compilation under Release configuration
i
#endif
return 0;
}
}
}

View file

@ -0,0 +1,10 @@
{
"frameworks": {
"dnxcore50": {
"dependencies": {
"System.Runtime": "4.0.21-*"
}
}
},
"dependencies": { }
}

View file

@ -0,0 +1,8 @@
{
"dependencies": {
"Newtonsoft.Json": "4.5.11"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,8 @@
{
"dependencies": {
"NoSuchPackage": "1.0.0"
},
"frameworks": {
"dnx451": { }
}
}

View file

@ -0,0 +1,8 @@
{
"dependencies": {
"EmptyLibrary": ""
},
"frameworks": {
"dnx451": { }
}
}

View file

@ -0,0 +1,8 @@
{
"version": "6.0.8",
"dependencies": {
},
"frameworks": {
"dnx451": { }
}
}

View file

@ -0,0 +1,6 @@
{
"projects": [
"src",
"../ext"
]
}

View file

@ -0,0 +1,8 @@
{
"dependencies": {
"Newtonsoft.Json": "6.0.8"
},
"frameworks": {
"dnx451": { }
}
}