Add ProjectModel server
This commit is contained in:
parent
27794b89ae
commit
935cd4e281
70 changed files with 3602 additions and 78 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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/
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel.Server.Helpers
|
||||
{
|
||||
public static class JTokenExtensions
|
||||
{
|
||||
public static string GetValue(this JToken token, string name)
|
||||
{
|
||||
return GetValue<string>(token, name);
|
||||
}
|
||||
|
||||
public static TVal GetValue<TVal>(this JToken token, string name)
|
||||
{
|
||||
var value = token?[name];
|
||||
if (value != null)
|
||||
{
|
||||
return value.Value<TVal>();
|
||||
}
|
||||
|
||||
return default(TVal);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
29
src/Microsoft.DotNet.ProjectModel.Server/MessageTypes.cs
Normal file
29
src/Microsoft.DotNet.ProjectModel.Server/MessageTypes.cs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||
{
|
||||
public class DependencyItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Version { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as DependencyItem;
|
||||
return other != null &&
|
||||
string.Equals(Name, other.Name) &&
|
||||
object.Equals(Version, other.Version);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// These objects are currently POCOs and we're overriding equals
|
||||
// so that things like Enumerable.SequenceEqual just work.
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NuGet.Frameworks;
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||
{
|
||||
public class DiagnosticMessageGroup
|
||||
{
|
||||
public DiagnosticMessageGroup(IEnumerable<DiagnosticMessage> diagnostics)
|
||||
: this(framework: null, diagnostics: diagnostics)
|
||||
{ }
|
||||
|
||||
public DiagnosticMessageGroup(NuGetFramework framework, IEnumerable<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
Framework = framework;
|
||||
Diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
public IEnumerable<DiagnosticMessage> Diagnostics { get; }
|
||||
|
||||
public NuGetFramework Framework { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||
{
|
||||
public class DiagnosticMessageView
|
||||
{
|
||||
public DiagnosticMessageView(DiagnosticMessage data)
|
||||
{
|
||||
ErrorCode = data.ErrorCode;
|
||||
SourceFilePath = data.SourceFilePath;
|
||||
Message = data.Message;
|
||||
Severity = data.Severity;
|
||||
StartLine = data.StartLine;
|
||||
StartColumn = data.StartColumn;
|
||||
EndLine = data.EndLine;
|
||||
EndColumn = data.EndColumn;
|
||||
FormattedMessage = data.FormattedMessage;
|
||||
|
||||
var description = data.Source as LibraryDescription;
|
||||
if (description != null)
|
||||
{
|
||||
Source = new
|
||||
{
|
||||
Name = description.Identity.Name,
|
||||
Version = description.Identity.Version?.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string ErrorCode { get; }
|
||||
|
||||
public string SourceFilePath { get; }
|
||||
|
||||
public string Message { get; }
|
||||
|
||||
public DiagnosticMessageSeverity Severity { get; }
|
||||
|
||||
public int StartLine { get; }
|
||||
|
||||
public int StartColumn { get; }
|
||||
|
||||
public int EndLine { get; }
|
||||
|
||||
public int EndColumn { get; }
|
||||
|
||||
public string FormattedMessage { get; }
|
||||
|
||||
public object Source { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as DiagnosticMessageView;
|
||||
|
||||
return other != null &&
|
||||
Severity == other.Severity &&
|
||||
StartLine == other.StartLine &&
|
||||
StartColumn == other.StartColumn &&
|
||||
EndLine == other.EndLine &&
|
||||
EndColumn == other.EndColumn &&
|
||||
string.Equals(ErrorCode, other.ErrorCode, StringComparison.Ordinal) &&
|
||||
string.Equals(SourceFilePath, other.SourceFilePath, StringComparison.Ordinal) &&
|
||||
string.Equals(Message, other.Message, StringComparison.Ordinal) &&
|
||||
object.Equals(Source, other.Source);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||
{
|
||||
public class ErrorMessage
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Path { get; set; }
|
||||
public int Line { get; set; }
|
||||
public int Column { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var payload = obj as ErrorMessage;
|
||||
return payload != null &&
|
||||
string.Equals(Message, payload.Message, StringComparison.Ordinal) &&
|
||||
string.Equals(Path, payload.Path, StringComparison.OrdinalIgnoreCase) &&
|
||||
Line == payload.Line &&
|
||||
Column == payload.Column;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.DotNet.ProjectModel.Server.Models
|
||||
{
|
||||
public class FrameworkData
|
||||
{
|
||||
public string FrameworkName { get; set; }
|
||||
public string FriendlyName { get; set; }
|
||||
public string ShortName { get; set; }
|
||||
public string RedistListPath { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as FrameworkData;
|
||||
|
||||
return other != null &&
|
||||
string.Equals(FrameworkName, other.FrameworkName);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// These objects are currently POCOs and we're overriding equals
|
||||
// so that things like Enumerable.SequenceEqual just work.
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
39
src/Microsoft.DotNet.ProjectModel.Server/Models/Message.cs
Normal file
39
src/Microsoft.DotNet.ProjectModel.Server/Models/Message.cs
Normal 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)}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
90
src/Microsoft.DotNet.ProjectModel.Server/ProcessingQueue.cs
Normal file
90
src/Microsoft.DotNet.ProjectModel.Server/ProcessingQueue.cs
Normal 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}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
164
src/Microsoft.DotNet.ProjectModel.Server/Program.cs
Normal file
164
src/Microsoft.DotNet.ProjectModel.Server/Program.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
111
src/Microsoft.DotNet.ProjectModel.Server/ProtocolManager.cs
Normal file
111
src/Microsoft.DotNet.ProjectModel.Server/ProtocolManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
34
src/Microsoft.DotNet.ProjectModel.Server/project.json
Normal file
34
src/Microsoft.DotNet.ProjectModel.Server/project.json
Normal 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\""
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
273
test/Microsoft.DotNet.ProjectModel.Server.Tests/DthTestClient.cs
Normal file
273
test/Microsoft.DotNet.ProjectModel.Server.Tests/DthTestClient.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
15
test/Microsoft.DotNet.ProjectModel.Server.Tests/project.json
Normal file
15
test/Microsoft.DotNet.ProjectModel.Server.Tests/project.json
Normal 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"
|
||||
}
|
||||
}
|
3
testapp/DthTestProjects/global.json
Normal file
3
testapp/DthTestProjects/global.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"projects": ["src"]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"EmptyLibrary": ""
|
||||
},
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
13
testapp/DthTestProjects/src/EmptyConsoleApp/Program.cs
Normal file
13
testapp/DthTestProjects/src/EmptyConsoleApp/Program.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
12
testapp/DthTestProjects/src/EmptyConsoleApp/project.json
Normal file
12
testapp/DthTestProjects/src/EmptyConsoleApp/project.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"dependencies": { },
|
||||
"frameworks": {
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.0.21-*",
|
||||
"System.Console": "4.0.0-*"
|
||||
}
|
||||
},
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
8
testapp/DthTestProjects/src/EmptyLibrary/Class.cs
Normal file
8
testapp/DthTestProjects/src/EmptyLibrary/Class.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace Misc.DthTestProjects.EmptyLibrary
|
||||
{
|
||||
public class Class
|
||||
{
|
||||
}
|
||||
}
|
11
testapp/DthTestProjects/src/EmptyLibrary/project-update.json
Normal file
11
testapp/DthTestProjects/src/EmptyLibrary/project-update.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"dependencies": { },
|
||||
"frameworks": {
|
||||
"dnxcore50": {
|
||||
"dependencies":{
|
||||
"System.Runtime": "4.0.21-beta-*",
|
||||
"System.Console": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
testapp/DthTestProjects/src/EmptyLibrary/project.json
Normal file
10
testapp/DthTestProjects/src/EmptyLibrary/project.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"dependencies": { },
|
||||
"frameworks": {
|
||||
"dnxcore50": {
|
||||
"dependencies":{
|
||||
"System.Runtime": "4.0.21-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
testapp/DthTestProjects/src/FailReleaseProject/Program.cs
Normal file
14
testapp/DthTestProjects/src/FailReleaseProject/Program.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
10
testapp/DthTestProjects/src/FailReleaseProject/project.json
Normal file
10
testapp/DthTestProjects/src/FailReleaseProject/project.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"frameworks": {
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"System.Runtime": "4.0.21-*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": { }
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "4.5.11"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnxcore50": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"NoSuchPackage": "1.0.0"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"EmptyLibrary": ""
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"version": "6.0.8",
|
||||
"dependencies": {
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
6
testapp/DthUpdateSearchPathSample/home/global.json
Normal file
6
testapp/DthUpdateSearchPathSample/home/global.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"projects": [
|
||||
"src",
|
||||
"../ext"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Newtonsoft.Json": "6.0.8"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue