refactor WorkspaceContext (#2736)

this removes a regression in ProjectModelServer behavior

also added a test to cover target framework change in DTH
This commit is contained in:
Troy Dai 2016-05-02 11:32:24 -07:00 committed by Andrew Stanton-Nurse
parent 2e22c3ac4b
commit f32683cbab
35 changed files with 821 additions and 635 deletions

View file

@ -230,11 +230,11 @@ namespace Microsoft.DotNet.Cli.Build
var projectFile = Path.Combine(Dirs.RepoRoot, "src", projectName, "project.json");
dotnet.Pack(
projectFile,
"--no-build",
"--build-base-path", packagingBuildBasePath,
"--output", Dirs.PackagesIntermediate,
"--configuration", configuration,
projectFile,
"--no-build",
"--build-base-path", packagingBuildBasePath,
"--output", Dirs.PackagesIntermediate,
"--configuration", configuration,
"--version-suffix", versionSuffix)
.Execute()
.EnsureSuccessful();

View file

@ -19,7 +19,7 @@ namespace Microsoft.DotNet.Cli.Utils
}
}
private static void WaitForDebugger()
public static void WaitForDebugger()
{
Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue");
Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");

View file

@ -17,9 +17,11 @@ using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Files;
using NuGet.Frameworks;
using RoslynWorkspace = Microsoft.CodeAnalysis.Workspace;
namespace Microsoft.DotNet.ProjectModel.Workspaces
{
public class ProjectJsonWorkspace : Workspace
public class ProjectJsonWorkspace : RoslynWorkspace
{
private Dictionary<string, AssemblyMetadata> _cache = new Dictionary<string, AssemblyMetadata>();

View file

@ -1,12 +1,9 @@
using Microsoft.CodeAnalysis;
using RoslynWorkspace = Microsoft.CodeAnalysis.Workspace;
namespace Microsoft.DotNet.ProjectModel.Workspaces
{
public static class WorkspaceProjectContextExtensions
{
public static Workspace CreateWorkspace(this ProjectContext context)
{
return new ProjectJsonWorkspace(context);
}
public static RoslynWorkspace CreateRoslynWorkspace(this ProjectContext context) => new ProjectJsonWorkspace(context);
}
}

View file

@ -0,0 +1,81 @@
// 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;
namespace Microsoft.DotNet.ProjectModel
{
public class BuildWorkspace : Workspace
{
public BuildWorkspace(ProjectReaderSettings settings) : base(settings) { }
/// <summary>
/// Create an empty <see cref="WorkspaceContext" /> using the default <see cref="ProjectReaderSettings" />
/// </summary>
/// <returns></returns>
public static BuildWorkspace Create() => Create(versionSuffix: string.Empty);
/// <summary>
/// Create an empty <see cref="WorkspaceContext" /> using the default <see cref="ProjectReaderSettings" />, with the specified Version Suffix
/// </summary>
/// <param name="versionSuffix">The suffix to use to replace any '-*' snapshot tokens in Project versions.</param>
/// <returns></returns>
public static BuildWorkspace Create(string versionSuffix)
{
var settings = ProjectReaderSettings.ReadFromEnvironment();
if (!string.IsNullOrEmpty(versionSuffix))
{
settings.VersionSuffix = versionSuffix;
}
return new BuildWorkspace(settings);
}
public ProjectContext GetRuntimeContext(ProjectContext context, IEnumerable<string> runtimeIdentifiers)
{
if (!runtimeIdentifiers.Any())
{
return context;
}
var contexts = GetProjectContextCollection(context.ProjectDirectory);
if (contexts == null)
{
return null;
}
var runtimeContext = runtimeIdentifiers
.Select(r => contexts.GetTarget(context.TargetFramework, r))
.FirstOrDefault(c => c != null);
if (runtimeContext == null)
{
if (context.IsPortable)
{
// We're specializing a portable target, so synthesize a runtime target manually
// We don't cache this project context, but we'll still use the cached Project and LockFile
return CreateBaseProjectBuilder(context.ProjectFile)
.WithTargetFramework(context.TargetFramework)
.WithRuntimeIdentifiers(runtimeIdentifiers)
.AsDesignTime()
.Build();
}
// We are standalone, but don't support this runtime
var rids = string.Join(", ", runtimeIdentifiers);
throw new InvalidOperationException($"Can not find runtime target for framework '{context.TargetFramework}' compatible with one of the target runtimes: '{rids}'. " +
"Possible causes:" + Environment.NewLine +
"1. The project has not been restored or restore failed - run `dotnet restore`" + Environment.NewLine +
$"2. The project does not list one of '{rids}' in the 'runtimes' section.");
}
return runtimeContext;
}
protected override IEnumerable<ProjectContext> BuildProjectContexts(Project project)
{
return CreateBaseProjectBuilder(project).BuildAllTargets();
}
}
}

View file

@ -0,0 +1,148 @@
// 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;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Graph;
namespace Microsoft.DotNet.ProjectModel
{
public class DesignTimeWorkspace : Workspace
{
private readonly HashSet<string> _projects = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private bool _needRefresh;
public DesignTimeWorkspace(ProjectReaderSettings settings) : base(settings) { }
public void AddProject(string path)
{
var projectPath = NormalizeProjectPath(path);
if (projectPath != null)
{
_needRefresh = _projects.Add(path);
}
}
public void RemoveProject(string path)
{
_needRefresh = _projects.Remove(path);
}
/// <summary>
/// Refresh all cached projects in the Workspace
/// </summary>
public void Refresh()
{
if (!_needRefresh)
{
return;
}
var basePaths = new List<string>(_projects);
_projects.Clear();
foreach (var projectDirectory in basePaths)
{
var project = GetProject(projectDirectory);
if (project == null)
{
continue;
}
_projects.Add(project.ProjectDirectory);
foreach (var projectContext in GetProjectContextCollection(project.ProjectDirectory).ProjectContexts)
{
foreach (var reference in GetProjectReferences(projectContext))
{
var referencedProject = GetProject(reference.Path);
if (referencedProject != null)
{
_projects.Add(referencedProject.ProjectDirectory);
}
}
}
}
_needRefresh = false;
}
protected override IEnumerable<ProjectContext> BuildProjectContexts(Project project)
{
foreach (var framework in project.GetTargetFrameworks())
{
yield return CreateBaseProjectBuilder(project)
.AsDesignTime()
.WithTargetFramework(framework.FrameworkName)
.Build();
}
}
private static List<string> ResolveProjectPath(string projectPath)
{
if (File.Exists(projectPath))
{
var filename = Path.GetFileName(projectPath);
if (!Project.FileName.Equals(filename, StringComparison.OrdinalIgnoreCase) &&
!GlobalSettings.FileName.Equals(filename, StringComparison.OrdinalIgnoreCase))
{
return null;
}
projectPath = Path.GetDirectoryName(projectPath);
}
if (File.Exists(Path.Combine(projectPath, Project.FileName)))
{
return new List<string> { projectPath };
}
if (File.Exists(Path.Combine(projectPath, GlobalSettings.FileName)))
{
var root = ProjectRootResolver.ResolveRootDirectory(projectPath);
GlobalSettings globalSettings;
if (GlobalSettings.TryGetGlobalSettings(projectPath, out globalSettings))
{
return globalSettings.ProjectSearchPaths
.Select(searchPath => Path.Combine(globalSettings.DirectoryPath, searchPath))
.Where(actualPath => Directory.Exists(actualPath))
.SelectMany(actualPath => Directory.GetDirectories(actualPath))
.Where(actualPath => File.Exists(Path.Combine(actualPath, Project.FileName)))
.Select(path => Path.GetFullPath(path))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
}
return null;
}
private static IEnumerable<ProjectDescription> GetProjectReferences(ProjectContext context)
{
var projectDescriptions = context.LibraryManager
.GetLibraries()
.Where(lib => lib.Identity.Type == LibraryType.Project)
.OfType<ProjectDescription>();
foreach (var description in projectDescriptions)
{
if (description.Identity.Name == context.ProjectFile.Name)
{
continue;
}
// if this is an assembly reference then don't threat it as project reference
if (!string.IsNullOrEmpty(description.TargetFrameworkInfo?.AssemblyPath))
{
continue;
}
yield return description;
}
}
}
}

View file

@ -135,7 +135,7 @@ namespace Microsoft.DotNet.ProjectModel
yield return new ProjectContextBuilder()
.WithProject(project)
.WithTargetFramework(framework.FrameworkName)
.WithReaderSettings(settings)
.WithProjectReaderSettings(settings)
.WithRuntimeIdentifiers(runtimeIdentifiers ?? Enumerable.Empty<string>())
.Build();
}
@ -149,7 +149,7 @@ namespace Microsoft.DotNet.ProjectModel
var project = ProjectReader.GetProject(projectPath);
return new ProjectContextBuilder()
.WithReaderSettings(settings)
.WithProjectReaderSettings(settings)
.WithProject(project)
.BuildAllTargets();
}

View file

@ -3,8 +3,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.DotNet.InternalAbstractions;
using Microsoft.DotNet.ProjectModel.Graph;
@ -15,12 +17,13 @@ namespace Microsoft.DotNet.ProjectModel
{
public class ProjectContextBuilder
{
// Note: When adding a property, make sure to add it to Clone below. You'll also need to update the CloneTest in
// Microsoft.DotNet.ProjectModel.Tests.ProjectContextBuilderTests
private Project Project { get; set; }
private LockFile LockFile { get; set; }
private GlobalSettings GlobalSettings { get; set; }
private NuGetFramework TargetFramework { get; set; }
private IEnumerable<string> RuntimeIdentifiers { get; set; } = Enumerable.Empty<string>();
@ -39,7 +42,7 @@ namespace Microsoft.DotNet.ProjectModel
private Func<string, LockFile> LockFileResolver { get; set; }
private ProjectReaderSettings Settings { get; set; } = ProjectReaderSettings.ReadFromEnvironment();
private ProjectReaderSettings ProjectReaderSettings { get; set; } = ProjectReaderSettings.ReadFromEnvironment();
public ProjectContextBuilder()
{
@ -47,6 +50,28 @@ namespace Microsoft.DotNet.ProjectModel
LockFileResolver = ResolveLockFile;
}
public ProjectContextBuilder Clone()
{
var builder = new ProjectContextBuilder()
.WithLockFile(LockFile)
.WithProject(Project)
.WithProjectDirectory(ProjectDirectory)
.WithTargetFramework(TargetFramework)
.WithRuntimeIdentifiers(RuntimeIdentifiers)
.WithReferenceAssembliesPath(ReferenceAssembliesPath)
.WithPackagesDirectory(PackagesDirectory)
.WithRootDirectory(RootDirectory)
.WithProjectResolver(ProjectResolver)
.WithLockFileResolver(LockFileResolver)
.WithProjectReaderSettings(ProjectReaderSettings);
if(IsDesignTime)
{
builder.AsDesignTime();
}
return builder;
}
public ProjectContextBuilder WithLockFile(LockFile lockFile)
{
LockFile = lockFile;
@ -113,9 +138,9 @@ namespace Microsoft.DotNet.ProjectModel
return this;
}
public ProjectContextBuilder WithReaderSettings(ProjectReaderSettings settings)
public ProjectContextBuilder WithProjectReaderSettings(ProjectReaderSettings projectReaderSettings)
{
Settings = settings;
ProjectReaderSettings = projectReaderSettings;
return this;
}
@ -125,13 +150,20 @@ namespace Microsoft.DotNet.ProjectModel
return this;
}
/// <summary>
/// Produce all targets found in the lock file associated with this builder.
/// Returns an empty enumerable if there is no lock file
/// (making this unsuitable for scenarios where the lock file may not be present,
/// such as at design-time)
/// </summary>
/// <returns></returns>
public IEnumerable<ProjectContext> BuildAllTargets()
{
ProjectDirectory = Project?.ProjectDirectory ?? ProjectDirectory;
EnsureProjectLoaded();
LockFile = LockFile ?? LockFileResolver(ProjectDirectory);
if (LockFile != null && LockFile.Targets.Any())
if (LockFile != null)
{
var deduper = new HashSet<string>();
foreach (var target in LockFile.Targets)
@ -139,15 +171,9 @@ namespace Microsoft.DotNet.ProjectModel
var id = $"{target.TargetFramework}/{target.RuntimeIdentifier}";
if (deduper.Add(id))
{
var builder = new ProjectContextBuilder()
.WithProject(Project)
.WithLockFile(LockFile)
var builder = Clone()
.WithTargetFramework(target.TargetFramework)
.WithRuntimeIdentifiers(new[] { target.RuntimeIdentifier });
if (IsDesignTime)
{
builder.AsDesignTime();
}
yield return builder.Build();
}
@ -155,8 +181,7 @@ namespace Microsoft.DotNet.ProjectModel
}
else
{
// Build a context for each framework. It won't be fully valid, since it won't have resolved data or runtime data, but the diagnostics will show that
// (Project Model Server needs this)
// Build a context for each framework. It won't be fully valid, since it won't have resolved data or runtime data, but the diagnostics will show that.
foreach (var framework in Project.GetTargetFrameworks())
{
var builder = new ProjectContextBuilder()
@ -177,19 +202,15 @@ namespace Microsoft.DotNet.ProjectModel
ProjectDirectory = Project?.ProjectDirectory ?? ProjectDirectory;
if (GlobalSettings == null && ProjectDirectory != null)
GlobalSettings globalSettings = null;
if (ProjectDirectory != null)
{
RootDirectory = ProjectRootResolver.ResolveRootDirectory(ProjectDirectory);
GlobalSettings globalSettings;
if (GlobalSettings.TryGetGlobalSettings(RootDirectory, out globalSettings))
{
GlobalSettings = globalSettings;
}
GlobalSettings.TryGetGlobalSettings(RootDirectory, out globalSettings);
}
RootDirectory = GlobalSettings?.DirectoryPath ?? RootDirectory;
PackagesDirectory = PackagesDirectory ?? PackageDependencyProvider.ResolvePackagesPath(RootDirectory, GlobalSettings);
RootDirectory = globalSettings?.DirectoryPath ?? RootDirectory;
PackagesDirectory = PackagesDirectory ?? PackageDependencyProvider.ResolvePackagesPath(RootDirectory, globalSettings);
FrameworkReferenceResolver frameworkReferenceResolver;
if (string.IsNullOrEmpty(ReferenceAssembliesPath))
@ -343,7 +364,7 @@ namespace Microsoft.DotNet.ProjectModel
var libraryManager = new LibraryManager(libraries.Values.ToList(), diagnostics, Project?.ProjectFilePath);
return new ProjectContext(
GlobalSettings,
globalSettings,
mainProject,
platformLibrary,
TargetFramework,
@ -570,7 +591,7 @@ namespace Microsoft.DotNet.ProjectModel
private Project ResolveProject(string projectDirectory)
{
Project project;
if (ProjectReader.TryGetProject(projectDirectory, out project, settings: Settings))
if (ProjectReader.TryGetProject(projectDirectory, out project, settings: ProjectReaderSettings))
{
return project;
}

View file

@ -13,8 +13,14 @@ namespace Microsoft.DotNet.ProjectModel
{
public Project Project { get; set; }
/// <summary>
/// Gets all the ProjectContexts in this collection
/// </summary>
public List<ProjectContext> ProjectContexts { get; } = new List<ProjectContext>();
/// <summary>
/// Gets the ProjectContexts in this collection which are not runtime-specific (i.e. the ones used for compilation)
/// </summary>
public IEnumerable<ProjectContext> FrameworkOnlyContexts => ProjectContexts.Where(c => string.IsNullOrEmpty(c.RuntimeIdentifier));
public List<DiagnosticMessage> ProjectDiagnostics { get; } = new List<DiagnosticMessage>();

View file

@ -0,0 +1,300 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel
{
/// <summary>
/// Represents a cache of Projects, LockFiles, and ProjectContexts
/// </summary>
public abstract class Workspace
{
// key: project directory
private readonly ConcurrentDictionary<string, FileModelEntry<Project>> _projectsCache
= new ConcurrentDictionary<string, FileModelEntry<Project>>();
// key: project directory
private readonly ConcurrentDictionary<string, FileModelEntry<LockFile>> _lockFileCache
= new ConcurrentDictionary<string, FileModelEntry<LockFile>>();
// key: project directory, target framework
private readonly ConcurrentDictionary<string, ProjectContextCollection> _projectContextsCache
= new ConcurrentDictionary<string, ProjectContextCollection>();
private readonly ProjectReaderSettings _settings;
private readonly LockFileReader _lockFileReader;
protected Workspace(ProjectReaderSettings settings)
{
_settings = settings;
_lockFileReader = new LockFileReader();
}
public ProjectContext GetProjectContext(string projectPath, NuGetFramework framework)
{
var contexts = GetProjectContextCollection(projectPath);
if (contexts == null)
{
return null;
}
return contexts
.ProjectContexts
.FirstOrDefault(c => Equals(c.TargetFramework, framework) && string.IsNullOrEmpty(c.RuntimeIdentifier));
}
public ProjectContextCollection GetProjectContextCollection(string projectPath)
{
var normalizedPath = NormalizeProjectPath(projectPath);
if (normalizedPath == null)
{
return null;
}
return _projectContextsCache.AddOrUpdate(
normalizedPath,
key => AddProjectContextEntry(key, null),
(key, oldEntry) => AddProjectContextEntry(key, oldEntry));
}
public Project GetProject(string projectDirectory) => GetProjectCore(projectDirectory)?.Model;
private LockFile GetLockFile(string projectDirectory)
{
var normalizedPath = NormalizeProjectPath(projectDirectory);
if (normalizedPath == null)
{
return null;
}
return _lockFileCache.AddOrUpdate(
normalizedPath,
key => AddLockFileEntry(key, null),
(key, oldEntry) => AddLockFileEntry(key, oldEntry)).Model;
}
private FileModelEntry<Project> GetProjectCore(string projectDirectory)
{
var normalizedPath = NormalizeProjectPath(projectDirectory);
if (normalizedPath == null)
{
return null;
}
return _projectsCache.AddOrUpdate(
normalizedPath,
key => AddProjectEntry(key, null),
(key, oldEntry) => AddProjectEntry(key, oldEntry));
}
protected static string NormalizeProjectPath(string path)
{
if (File.Exists(path) &&
string.Equals(Path.GetFileName(path), Project.FileName, StringComparison.OrdinalIgnoreCase))
{
return Path.GetDirectoryName(Path.GetFullPath(path));
}
else if (Directory.Exists(path) &&
File.Exists(Path.Combine(path, Project.FileName)))
{
return Path.GetFullPath(path);
}
return null;
}
private FileModelEntry<Project> AddProjectEntry(string projectDirectory, FileModelEntry<Project> currentEntry)
{
if (currentEntry == null)
{
currentEntry = new FileModelEntry<Project>();
}
else if (!File.Exists(Path.Combine(projectDirectory, Project.FileName)))
{
// project was deleted
currentEntry.Reset();
return currentEntry;
}
if (currentEntry.IsInvalid)
{
Project project;
if (!ProjectReader.TryGetProject(projectDirectory, out project, _settings))
{
currentEntry.Reset();
}
else
{
currentEntry.Diagnostics.AddRange(project.Diagnostics);
currentEntry.Model = project;
currentEntry.FilePath = project.ProjectFilePath;
currentEntry.UpdateLastWriteTimeUtc();
}
}
return currentEntry;
}
private FileModelEntry<LockFile> AddLockFileEntry(string projectDirectory, FileModelEntry<LockFile> currentEntry)
{
if (currentEntry == null)
{
currentEntry = new FileModelEntry<LockFile>();
}
if (currentEntry.IsInvalid)
{
currentEntry.Reset();
if (!File.Exists(Path.Combine(projectDirectory, LockFile.FileName)))
{
return currentEntry;
}
else
{
currentEntry.FilePath = Path.Combine(projectDirectory, LockFile.FileName);
using (var fs = ResilientFileStreamOpener.OpenFile(currentEntry.FilePath, retry: 2))
{
try
{
currentEntry.Model = _lockFileReader.ReadLockFile(currentEntry.FilePath, fs, designTime: true);
currentEntry.UpdateLastWriteTimeUtc();
}
catch (FileFormatException ex)
{
throw ex.WithFilePath(currentEntry.FilePath);
}
catch (Exception ex)
{
throw FileFormatException.Create(ex, currentEntry.FilePath);
}
}
}
}
return currentEntry;
}
private ProjectContextCollection AddProjectContextEntry(string projectDirectory,
ProjectContextCollection currentEntry)
{
if (currentEntry == null)
{
// new entry required
currentEntry = new ProjectContextCollection();
}
var projectEntry = GetProjectCore(projectDirectory);
if (projectEntry?.Model == null)
{
// project doesn't exist anymore
currentEntry.Reset();
return currentEntry;
}
var project = projectEntry.Model;
if (currentEntry.HasChanged)
{
currentEntry.Reset();
var contexts = BuildProjectContexts(project);
currentEntry.ProjectContexts.AddRange(contexts);
currentEntry.Project = project;
currentEntry.ProjectFilePath = project.ProjectFilePath;
currentEntry.LastProjectFileWriteTimeUtc = File.GetLastWriteTimeUtc(currentEntry.ProjectFilePath);
var lockFilePath = Path.Combine(project.ProjectDirectory, LockFile.FileName);
if (File.Exists(lockFilePath))
{
currentEntry.LockFilePath = lockFilePath;
currentEntry.LastLockFileWriteTimeUtc = File.GetLastWriteTimeUtc(lockFilePath);
}
currentEntry.ProjectDiagnostics.AddRange(projectEntry.Diagnostics);
}
return currentEntry;
}
protected abstract IEnumerable<ProjectContext> BuildProjectContexts(Project project);
/// <summary>
/// Creates a ProjectContextBuilder configured to use the Workspace caches.
/// </summary>
/// <returns></returns>
protected ProjectContextBuilder CreateBaseProjectBuilder()
{
return new ProjectContextBuilder()
.WithProjectReaderSettings(_settings)
.WithProjectResolver(path => GetProjectCore(path)?.Model)
.WithLockFileResolver(path => GetLockFile(path));
}
/// <summary>
/// Creates a ProjectContextBuilder configured to use the Workspace caches, and the specified root project.
/// </summary>
/// <param name="root">The root project</param>
/// <returns></returns>
protected ProjectContextBuilder CreateBaseProjectBuilder(Project root)
{
return CreateBaseProjectBuilder().WithProject(root);
}
protected class FileModelEntry<TModel> where TModel : class
{
private DateTime _lastWriteTimeUtc;
public TModel Model { get; set; }
public string FilePath { get; set; }
public List<DiagnosticMessage> Diagnostics { get; } = new List<DiagnosticMessage>();
public void UpdateLastWriteTimeUtc()
{
_lastWriteTimeUtc = File.GetLastWriteTimeUtc(FilePath);
}
public bool IsInvalid
{
get
{
if (Model == null)
{
return true;
}
if (!File.Exists(FilePath))
{
return true;
}
return _lastWriteTimeUtc < File.GetLastWriteTimeUtc(FilePath);
}
}
public void Reset()
{
Model = null;
FilePath = null;
Diagnostics.Clear();
_lastWriteTimeUtc = DateTime.MinValue;
}
}
}
}

View file

@ -1,507 +0,0 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel
{
public class WorkspaceContext
{
// key: project directory
private readonly ConcurrentDictionary<string, FileModelEntry<Project>> _projectsCache
= new ConcurrentDictionary<string, FileModelEntry<Project>>();
// key: project directory
private readonly ConcurrentDictionary<string, FileModelEntry<LockFile>> _lockFileCache
= new ConcurrentDictionary<string, FileModelEntry<LockFile>>();
// key: project directory, target framework
private readonly ConcurrentDictionary<string, ProjectContextCollection> _projectContextsCache
= new ConcurrentDictionary<string, ProjectContextCollection>();
private readonly HashSet<string> _projects = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private bool _needRefresh;
private readonly ProjectReaderSettings _settings;
private readonly bool _designTime;
private readonly LockFileReader _lockFileReader;
private WorkspaceContext(IEnumerable<string> projectPaths, ProjectReaderSettings settings, bool designTime)
{
_settings = settings;
_designTime = designTime;
_lockFileReader = new LockFileReader();
foreach (var path in projectPaths)
{
AddProject(path);
}
Refresh();
}
/// <summary>
/// Create a WorkspaceContext from a given path.
///
/// If the given path points to a global.json, all the projects found under the search paths
/// are added to the WorkspaceContext.
///
/// If the given path points to a project.json, all the projects it referenced as well as itself
/// are added to the WorkspaceContext.
///
/// If no path is provided, the workspace context is created empty and projects must be manually added
/// to it using <see cref="AddProject(string)"/>.
/// </summary>
public static WorkspaceContext CreateFrom(string projectPath, bool designTime)
{
var projectPaths = ResolveProjectPath(projectPath);
if (projectPaths == null || !projectPaths.Any())
{
return new WorkspaceContext(Enumerable.Empty<string>(), ProjectReaderSettings.ReadFromEnvironment(), designTime);
}
var context = new WorkspaceContext(projectPaths, ProjectReaderSettings.ReadFromEnvironment(), designTime);
return context;
}
/// <summary>
/// Create an empty <see cref="WorkspaceContext" /> using the default <see cref="ProjectReaderSettings" />
/// </summary>
/// <param name="designTime">A boolean indicating if the workspace should be created in Design-Time mode</param>
/// <returns></returns>
public static WorkspaceContext Create(bool designTime) => Create(ProjectReaderSettings.ReadFromEnvironment(), designTime);
/// <summary>
/// Create an empty <see cref="WorkspaceContext" /> using the default <see cref="ProjectReaderSettings" />, with the specified Version Suffix
/// </summary>
/// <param name="versionSuffix">The suffix to use to replace any '-*' snapshot tokens in Project versions.</param>
/// <param name="designTime">A boolean indicating if the workspace should be created in Design-Time mode</param>
/// <returns></returns>
public static WorkspaceContext Create(string versionSuffix, bool designTime)
{
var settings = ProjectReaderSettings.ReadFromEnvironment();
if (!string.IsNullOrEmpty(versionSuffix))
{
settings.VersionSuffix = versionSuffix;
}
return Create(settings, designTime);
}
/// <summary>
/// Create an empty <see cref="WorkspaceContext" /> using the provided <see cref="ProjectReaderSettings" />.
/// </summary>
/// <param name="settings">The settings to use when reading projects</param>
/// <param name="designTime">A boolean indicating if the workspace should be created in Design-Time mode</param>
/// <returns></returns>
public static WorkspaceContext Create(ProjectReaderSettings settings, bool designTime)
{
return new WorkspaceContext(Enumerable.Empty<string>(), settings, designTime);
}
public void AddProject(string path)
{
var projectPath = ProjectPathHelper.NormalizeProjectDirectoryPath(path);
if (projectPath != null)
{
_needRefresh = _projects.Add(path);
}
}
public void RemoveProject(string path)
{
_needRefresh = _projects.Remove(path);
}
public IReadOnlyList<string> GetAllProjects()
{
Refresh();
return _projects.ToList().AsReadOnly();
}
/// <summary>
/// Refresh the WorkspaceContext to update projects collection
/// </summary>
public void Refresh()
{
if (!_needRefresh)
{
return;
}
var basePaths = new List<string>(_projects);
_projects.Clear();
foreach (var projectDirectory in basePaths)
{
var project = GetProjectCore(projectDirectory).Model;
if (project == null)
{
continue;
}
_projects.Add(project.ProjectDirectory);
foreach (var projectContext in GetProjectContexts(project.ProjectDirectory))
{
foreach (var reference in GetProjectReferences(projectContext))
{
var referencedProject = GetProjectCore(reference.Path).Model;
if (referencedProject != null)
{
_projects.Add(referencedProject.ProjectDirectory);
}
}
}
}
_needRefresh = false;
}
public IReadOnlyList<ProjectContext> GetProjectContexts(string projectPath)
{
return (IReadOnlyList<ProjectContext>)GetProjectContextCollection(projectPath)?.ProjectContexts.AsReadOnly() ??
EmptyArray<ProjectContext>.Value;
}
public ProjectContext GetProjectContext(string projectPath, NuGetFramework framework)
{
var contexts = GetProjectContextCollection(projectPath);
if (contexts == null)
{
return null;
}
return contexts
.ProjectContexts
.FirstOrDefault(c => Equals(c.TargetFramework, framework) && string.IsNullOrEmpty(c.RuntimeIdentifier));
}
public ProjectContext GetRuntimeContext(ProjectContext context, IEnumerable<string> runtimeIdentifiers)
{
if(!runtimeIdentifiers.Any())
{
return context;
}
var contexts = GetProjectContextCollection(context.ProjectDirectory);
if (contexts == null)
{
return null;
}
var runtimeContext = runtimeIdentifiers
.Select(r => contexts.GetTarget(context.TargetFramework, r))
.FirstOrDefault(c => c != null);
if (runtimeContext == null)
{
if (context.IsPortable)
{
// We're specializing a portable target, so synthesize a runtime target manually
// We don't cache this project context, but we'll still use the cached Project and LockFile
return InitializeProjectContextBuilder(context.ProjectFile)
.WithTargetFramework(context.TargetFramework)
.WithRuntimeIdentifiers(runtimeIdentifiers)
.Build();
}
// We are standalone, but don't support this runtime
var rids = string.Join(", ", runtimeIdentifiers);
throw new InvalidOperationException($"Can not find runtime target for framework '{context.TargetFramework}' compatible with one of the target runtimes: '{rids}'. " +
"Possible causes:" + Environment.NewLine +
"1. The project has not been restored or restore failed - run `dotnet restore`" + Environment.NewLine +
$"2. The project does not list one of '{rids}' in the 'runtimes' section.");
}
return runtimeContext;
}
public Project GetProject(string projectDirectory) => GetProjectCore(projectDirectory)?.Model;
public ProjectContextCollection GetProjectContextCollection(string projectPath)
{
var normalizedPath = ProjectPathHelper.NormalizeProjectDirectoryPath(projectPath);
if (normalizedPath == null)
{
return null;
}
return _projectContextsCache.AddOrUpdate(
normalizedPath,
key => AddProjectContextEntry(key, null),
(key, oldEntry) => AddProjectContextEntry(key, oldEntry));
}
private FileModelEntry<Project> GetProjectCore(string projectDirectory)
{
var normalizedPath = ProjectPathHelper.NormalizeProjectDirectoryPath(projectDirectory);
if (normalizedPath == null)
{
return null;
}
return _projectsCache.AddOrUpdate(
normalizedPath,
key => AddProjectEntry(key, null),
(key, oldEntry) => AddProjectEntry(key, oldEntry));
}
private LockFile GetLockFile(string projectDirectory)
{
var normalizedPath = ProjectPathHelper.NormalizeProjectDirectoryPath(projectDirectory);
if (normalizedPath == null)
{
return null;
}
return _lockFileCache.AddOrUpdate(
normalizedPath,
key => AddLockFileEntry(key, null),
(key, oldEntry) => AddLockFileEntry(key, oldEntry)).Model;
}
private FileModelEntry<Project> AddProjectEntry(string projectDirectory, FileModelEntry<Project> currentEntry)
{
if (currentEntry == null)
{
currentEntry = new FileModelEntry<Project>();
}
else if (!File.Exists(Path.Combine(projectDirectory, Project.FileName)))
{
// project was deleted
currentEntry.Reset();
return currentEntry;
}
if (currentEntry.IsInvalid)
{
Project project;
if (!ProjectReader.TryGetProject(projectDirectory, out project, _settings))
{
currentEntry.Reset();
}
else
{
currentEntry.Model = project;
currentEntry.Diagnostics.AddRange(project.Diagnostics);
currentEntry.FilePath = project.ProjectFilePath;
currentEntry.UpdateLastWriteTimeUtc();
}
}
return currentEntry;
}
private FileModelEntry<LockFile> AddLockFileEntry(string projectDirectory, FileModelEntry<LockFile> currentEntry)
{
if (currentEntry == null)
{
currentEntry = new FileModelEntry<LockFile>();
}
if (currentEntry.IsInvalid)
{
currentEntry.Reset();
if (!File.Exists(Path.Combine(projectDirectory, LockFile.FileName)))
{
return currentEntry;
}
else
{
currentEntry.FilePath = Path.Combine(projectDirectory, LockFile.FileName);
using (var fs = ResilientFileStreamOpener.OpenFile(currentEntry.FilePath, retry: 2))
{
try
{
currentEntry.Model = _lockFileReader.ReadLockFile(currentEntry.FilePath, fs, designTime: true);
currentEntry.UpdateLastWriteTimeUtc();
}
catch (FileFormatException ex)
{
throw ex.WithFilePath(currentEntry.FilePath);
}
catch (Exception ex)
{
throw FileFormatException.Create(ex, currentEntry.FilePath);
}
}
}
}
return currentEntry;
}
private ProjectContextCollection AddProjectContextEntry(string projectDirectory,
ProjectContextCollection currentEntry)
{
if (currentEntry == null)
{
// new entry required
currentEntry = new ProjectContextCollection();
}
var projectEntry = GetProjectCore(projectDirectory);
if (projectEntry?.Model == null)
{
// project doesn't exist anymore
currentEntry.Reset();
return currentEntry;
}
var project = projectEntry.Model;
if (currentEntry.HasChanged)
{
currentEntry.Reset();
var builder = InitializeProjectContextBuilder(project);
currentEntry.ProjectContexts.AddRange(builder.BuildAllTargets());
currentEntry.Project = project;
currentEntry.ProjectFilePath = project.ProjectFilePath;
currentEntry.LastProjectFileWriteTimeUtc = File.GetLastWriteTimeUtc(currentEntry.ProjectFilePath);
var lockFilePath = Path.Combine(project.ProjectDirectory, LockFile.FileName);
if (File.Exists(lockFilePath))
{
currentEntry.LockFilePath = lockFilePath;
currentEntry.LastLockFileWriteTimeUtc = File.GetLastWriteTimeUtc(lockFilePath);
}
currentEntry.ProjectDiagnostics.AddRange(projectEntry.Diagnostics);
}
return currentEntry;
}
private ProjectContextBuilder InitializeProjectContextBuilder(Project project)
{
var builder = new ProjectContextBuilder()
.WithProjectResolver(path => GetProjectCore(path)?.Model)
.WithLockFileResolver(path => GetLockFile(path))
.WithProject(project);
if (_designTime)
{
builder.AsDesignTime();
}
return builder;
}
private class FileModelEntry<TModel> where TModel : class
{
private DateTime _lastWriteTimeUtc;
public TModel Model { get; set; }
public string FilePath { get; set; }
public List<DiagnosticMessage> Diagnostics { get; } = new List<DiagnosticMessage>();
public void UpdateLastWriteTimeUtc()
{
_lastWriteTimeUtc = File.GetLastWriteTimeUtc(FilePath);
}
public bool IsInvalid
{
get
{
if (Model == null)
{
return true;
}
if (!File.Exists(FilePath))
{
return true;
}
return _lastWriteTimeUtc < File.GetLastWriteTimeUtc(FilePath);
}
}
public void Reset()
{
Model = null;
FilePath = null;
Diagnostics.Clear();
_lastWriteTimeUtc = DateTime.MinValue;
}
}
private static List<string> ResolveProjectPath(string projectPath)
{
if (File.Exists(projectPath))
{
var filename = Path.GetFileName(projectPath);
if (!Project.FileName.Equals(filename, StringComparison.OrdinalIgnoreCase) &&
!GlobalSettings.FileName.Equals(filename, StringComparison.OrdinalIgnoreCase))
{
return null;
}
projectPath = Path.GetDirectoryName(projectPath);
}
if (File.Exists(Path.Combine(projectPath, Project.FileName)))
{
return new List<string> { projectPath };
}
if (File.Exists(Path.Combine(projectPath, GlobalSettings.FileName)))
{
var root = ProjectRootResolver.ResolveRootDirectory(projectPath);
GlobalSettings globalSettings;
if (GlobalSettings.TryGetGlobalSettings(projectPath, out globalSettings))
{
return globalSettings.ProjectSearchPaths
.Select(searchPath => Path.Combine(globalSettings.DirectoryPath, searchPath))
.Where(actualPath => Directory.Exists(actualPath))
.SelectMany(actualPath => Directory.GetDirectories(actualPath))
.Where(actualPath => File.Exists(Path.Combine(actualPath, Project.FileName)))
.Select(path => Path.GetFullPath(path))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
}
return null;
}
private static IEnumerable<ProjectDescription> GetProjectReferences(ProjectContext context)
{
var projectDescriptions = context.LibraryManager
.GetLibraries()
.Where(lib => lib.Identity.Type == LibraryType.Project)
.OfType<ProjectDescription>();
foreach (var description in projectDescriptions)
{
if (description.Identity.Name == context.ProjectFile.Name)
{
continue;
}
// if this is an assembly reference then don't threat it as project reference
if (!string.IsNullOrEmpty(description.TargetFrameworkInfo?.AssemblyPath))
{
continue;
}
yield return description;
}
}
}
}

View file

@ -45,14 +45,14 @@ namespace Microsoft.DotNet.Tools.Compiler
public bool ShouldNotUseIncrementality { get; set; }
public bool ShouldSkipDependencies { get; set; }
public WorkspaceContext Workspace { get; private set; }
public BuildWorkspace Workspace { get; private set; }
// workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params
private readonly Dictionary<string, CommandOption> baseClassOptions;
public BuildCommandApp(string name, string fullName, string description) : this(name, fullName, description, workspace: null) { }
public BuildCommandApp(string name, string fullName, string description, WorkspaceContext workspace)
public BuildCommandApp(string name, string fullName, string description, BuildWorkspace workspace)
{
Workspace = workspace;
_app = new CommandLineApplication
@ -108,13 +108,7 @@ namespace Microsoft.DotNet.Tools.Compiler
// Set defaults based on the environment
if (Workspace == null)
{
var settings = ProjectReaderSettings.ReadFromEnvironment();
if (!string.IsNullOrEmpty(VersionSuffixValue))
{
settings.VersionSuffix = VersionSuffixValue;
}
Workspace = WorkspaceContext.Create(settings, designTime: false);
Workspace = BuildWorkspace.Create(VersionSuffixValue);
}
var files = new ProjectGlobbingResolver().Resolve(_projectArgument.Values);

View file

@ -4,12 +4,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.DotNet.Tools.Compiler;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Compiler;
namespace Microsoft.DotNet.Tools.Build
{
@ -19,21 +18,21 @@ namespace Microsoft.DotNet.Tools.Build
private readonly string _outputPath;
private readonly string _buildBasePath;
private readonly IList<string> _runtimes;
private readonly WorkspaceContext _workspace;
private readonly BuildWorkspace _workspace;
private readonly ConcurrentDictionary<ProjectContextIdentity, CompilerIO> _cache;
public CompilerIOManager(string configuration,
string outputPath,
string buildBasePath,
IEnumerable<string> runtimes,
WorkspaceContext workspace)
BuildWorkspace workspace)
{
_configuration = configuration;
_outputPath = outputPath;
_buildBasePath = buildBasePath;
_runtimes = runtimes.ToList();
_workspace = workspace;
_cache = new ConcurrentDictionary<ProjectContextIdentity, CompilerIO>();
}

View file

@ -4,11 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Compiler;
using Microsoft.DotNet.Cli.Utils;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Build
@ -17,7 +16,7 @@ namespace Microsoft.DotNet.Tools.Build
{
public static int Run(string[] args) => Run(args, null);
public static int Run(string[] args, WorkspaceContext workspace)
public static int Run(string[] args, BuildWorkspace workspace)
{
DebugHelper.HandleDebugSwitch(ref args);

View file

@ -15,18 +15,18 @@ namespace Microsoft.DotNet.Tools.Pack
private readonly string _buildBasePath;
private readonly string _configuration;
private readonly string _versionSuffix;
private readonly BuildWorkspace _workspace;
public BuildProjectCommand(
Project project,
string buildBasePath,
string configuration,
string versionSuffix)
BuildWorkspace workspace)
{
_project = project;
_buildBasePath = buildBasePath;
_configuration = configuration;
_versionSuffix = versionSuffix;
_workspace = workspace;
}
public int Execute()
@ -37,11 +37,8 @@ namespace Microsoft.DotNet.Tools.Pack
argsBuilder.Add("--configuration");
argsBuilder.Add($"{_configuration}");
if (!string.IsNullOrEmpty(_versionSuffix))
{
argsBuilder.Add("--version-suffix");
argsBuilder.Add(_versionSuffix);
}
// Passing the Workspace along will flow the version suffix,
// so we don't need to pass it as an argument.
if (!string.IsNullOrEmpty(_buildBasePath))
{
@ -51,7 +48,7 @@ namespace Microsoft.DotNet.Tools.Pack
argsBuilder.Add($"{_project.ProjectFilePath}");
var result = Build.BuildCommand.Run(argsBuilder.ToArray());
var result = Build.BuildCommand.Run(argsBuilder.ToArray(), _workspace);
return result;
}

View file

@ -31,7 +31,7 @@ namespace Microsoft.DotNet.Tools.Pack
var packDiagnostics = new List<DiagnosticMessage>();
var mainPackageGenerator = new PackageGenerator(project, _configuration, _artifactPathsCalculator);
var symbolsPackageGenerator =
var symbolsPackageGenerator =
new SymbolPackageGenerator(project, _configuration, _artifactPathsCalculator);
return mainPackageGenerator.BuildPackage(_contexts, packDiagnostics) &&

View file

@ -51,19 +51,13 @@ namespace Microsoft.DotNet.Tools.Compiler
}
// Set defaults based on the environment
var settings = ProjectReaderSettings.ReadFromEnvironment();
var versionSuffixValue = versionSuffix.Value();
if (!string.IsNullOrEmpty(versionSuffixValue))
{
settings.VersionSuffix = versionSuffixValue;
}
var workspace = BuildWorkspace.Create(versionSuffix.Value());
var configValue = configuration.Value() ?? Cli.Utils.Constants.DefaultConfiguration;
var outputValue = output.Value();
var buildBasePathValue = buildBasePath.Value();
var contexts = ProjectContext.CreateContextForEachFramework(pathValue, settings);
var contexts = workspace.GetProjectContextCollection(pathValue).FrameworkOnlyContexts;
var project = contexts.First().ProjectFile;
var artifactPathsCalculator = new ArtifactPathsCalculator(project, buildBasePathValue, outputValue, configValue);
@ -72,7 +66,7 @@ namespace Microsoft.DotNet.Tools.Compiler
int buildResult = 0;
if (!noBuild.HasValue())
{
var buildProjectCommand = new BuildProjectCommand(project, buildBasePathValue, configValue, versionSuffixValue);
var buildProjectCommand = new BuildProjectCommand(project, buildBasePathValue, configValue, workspace);
buildResult = buildProjectCommand.Execute();
}

View file

@ -16,7 +16,7 @@ namespace Microsoft.DotNet.ProjectModel.Server
public ConnectionContext(Socket acceptedSocket,
string hostName,
ProtocolManager protocolManager,
WorkspaceContext workspaceContext,
DesignTimeWorkspace workspaceContext,
IDictionary<int, ProjectManager> projects)
{
_hostName = hostName;
@ -59,4 +59,4 @@ namespace Microsoft.DotNet.ProjectModel.Server
return _queue.Send(message);
}
}
}
}

View file

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.DotNet.ProjectModel.Server.Helpers;
using Microsoft.DotNet.ProjectModel.Server.Models;
@ -20,7 +19,7 @@ namespace Microsoft.DotNet.ProjectModel.Server
public ErrorMessage GlobalErrorMessage { get; set; }
public Dictionary<NuGetFramework, ProjectContextSnapshot> ProjectContexts { get; } = new Dictionary<NuGetFramework, ProjectContextSnapshot>();
public static ProjectSnapshot Create(string projectDirectory, string configuration, WorkspaceContext workspaceContext, IReadOnlyList<string> projectSearchPaths)
public static ProjectSnapshot Create(string projectDirectory, string configuration, DesignTimeWorkspace workspaceContext, IReadOnlyList<string> projectSearchPaths)
{
var projectContextsCollection = workspaceContext.GetProjectContextCollection(projectDirectory);
if (!projectContextsCollection.ProjectContexts.Any())

View file

@ -14,7 +14,7 @@ namespace Microsoft.DotNet.ProjectModel.Server
public class ProjectModelServerCommand
{
private readonly Dictionary<int, ProjectManager> _projects;
private readonly WorkspaceContext _workspaceContext;
private readonly DesignTimeWorkspace _workspaceContext;
private readonly ProtocolManager _protocolManager;
private readonly string _hostName;
private readonly int _port;
@ -25,7 +25,7 @@ namespace Microsoft.DotNet.ProjectModel.Server
_port = port;
_hostName = hostName;
_protocolManager = new ProtocolManager(maxVersion: 4);
_workspaceContext = WorkspaceContext.Create(designTime: true);
_workspaceContext = new DesignTimeWorkspace(ProjectReaderSettings.ReadFromEnvironment());
_projects = new Dictionary<int, ProjectManager>();
}

View file

@ -31,7 +31,7 @@ namespace Microsoft.DotNet.ProjectModel.Server
private ProjectSnapshot _local = new ProjectSnapshot();
private ProjectSnapshot _remote = new ProjectSnapshot();
private readonly WorkspaceContext _workspaceContext;
private readonly DesignTimeWorkspace _workspaceContext;
private int? _contextProtocolVersion;
private readonly List<Messenger<ProjectContextSnapshot>> _messengers;
@ -42,7 +42,7 @@ namespace Microsoft.DotNet.ProjectModel.Server
public ProjectManager(
int contextId,
WorkspaceContext workspaceContext,
DesignTimeWorkspace workspaceContext,
ProtocolManager protocolManager)
{
Id = contextId;

View file

@ -45,7 +45,7 @@ namespace Microsoft.DotNet.Tools.Publish
publish.VersionSuffix = versionSuffix.Value();
publish.ShouldBuild = !noBuild.HasValue();
publish.Workspace = WorkspaceContext.Create(versionSuffix.Value(), designTime: false);
publish.Workspace = BuildWorkspace.Create(versionSuffix.Value());
if (string.IsNullOrEmpty(publish.ProjectPath))
{

View file

@ -31,7 +31,7 @@ namespace Microsoft.DotNet.Tools.Publish
public string Runtime { get; set; }
public bool NativeSubdirectories { get; set; }
public NuGetFramework NugetFramework { get; set; }
public WorkspaceContext Workspace { get; set; }
public BuildWorkspace Workspace { get; set; }
public IList<ProjectContext> ProjectContexts { get; set; }
public string VersionSuffix { get; set; }
public int NumberOfProjects { get; private set; }
@ -253,7 +253,7 @@ namespace Microsoft.DotNet.Tools.Publish
args.Add(buildBasePath);
}
var result = Build.BuildCommand.Run(args.ToArray());
var result = Build.BuildCommand.Run(args.ToArray(), Workspace);
return result == 0;
}

View file

@ -29,7 +29,7 @@ namespace Microsoft.DotNet.Tools.Run
ProjectContext _context;
List<string> _args;
private WorkspaceContext _workspace;
private BuildWorkspace _workspace;
public int Start()
{
@ -113,7 +113,7 @@ namespace Microsoft.DotNet.Tools.Run
private int RunExecutable()
{
// Set up the workspace
_workspace = WorkspaceContext.Create(ProjectReaderSettings.ReadFromEnvironment(), designTime: false);
_workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment());
CalculateDefaultsForNonAssigned();
@ -125,7 +125,7 @@ namespace Microsoft.DotNet.Tools.Run
$"--configuration",
Configuration,
$"{_context.ProjectFile.ProjectDirectory}"
});
}, _workspace);
if (result != 0)
{

View file

@ -8,26 +8,26 @@ namespace Microsoft.DotNet.Tools.Test
{
public abstract class BaseDotnetTestRunner : IDotnetTestRunner
{
public int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
public int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
var result = BuildTestProject(projectContext, dotnetTestParams);
var result = BuildTestProject(projectContext, dotnetTestParams, workspace);
return result == 0 ? DoRunTests(projectContext, dotnetTestParams) : result;
}
internal abstract int DoRunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams);
private int BuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
private int BuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
if (dotnetTestParams.NoBuild)
{
return 0;
}
return DoBuildTestProject(projectContext, dotnetTestParams);
return DoBuildTestProject(projectContext, dotnetTestParams, workspace);
}
private int DoBuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
private int DoBuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
var strings = new List<string>
{
@ -59,7 +59,7 @@ namespace Microsoft.DotNet.Tools.Test
strings.Add(projectContext.RuntimeIdentifier);
}
var result = Build.BuildCommand.Run(strings.ToArray());
var result = Build.BuildCommand.Run(strings.ToArray(), workspace);
return result;
}

View file

@ -41,6 +41,8 @@ namespace Microsoft.DotNet.Tools.Test
public NuGetFramework Framework { get; set; }
public string UnparsedFramework { get; set; }
public List<string> RemainingArguments { get; set; }
public bool NoBuild { get; set; }
@ -97,6 +99,7 @@ namespace Microsoft.DotNet.Tools.Test
Port = port;
}
UnparsedFramework = _frameworkOption.Value();
if (_frameworkOption.HasValue())
{
Framework = NuGetFramework.Parse(_frameworkOption.Value());

View file

@ -8,6 +8,6 @@ namespace Microsoft.DotNet.Tools.Test
{
public interface IDotnetTestRunner
{
int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams);
int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace);
}
}

View file

@ -48,12 +48,19 @@ namespace Microsoft.DotNet.Tools.Test
var exitCode = 0;
// Create a workspace
var workspace = WorkspaceContext.Create(ProjectReaderSettings.ReadFromEnvironment(), designTime: false);
var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment());
if (dotnetTestParams.Framework != null)
{
var projectContext = ProjectContext.Create(projectPath, dotnetTestParams.Framework, runtimeIdentifiers);
exitCode = RunTest(projectContext, dotnetTestParams);
var projectContext = workspace.GetProjectContext(projectPath, dotnetTestParams.Framework);
if (projectContext == null)
{
Reporter.Error.WriteLine($"Project '{projectPath}' does not support framework: {dotnetTestParams.UnparsedFramework}");
return 1;
}
projectContext = workspace.GetRuntimeContext(projectContext, runtimeIdentifiers);
exitCode = RunTest(projectContext, dotnetTestParams, workspace);
}
else
{
@ -66,7 +73,7 @@ namespace Microsoft.DotNet.Tools.Test
// Execute for all TFMs the project targets.
foreach (var projectContext in projectContexts)
{
var result = RunTest(projectContext, dotnetTestParams);
var result = RunTest(projectContext, dotnetTestParams, workspace);
if (result == 0)
{
summary.Passed++;
@ -134,11 +141,11 @@ namespace Microsoft.DotNet.Tools.Test
}
}
private int RunTest(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
private int RunTest(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
var testRunner = projectContext.ProjectFile.TestRunner;
var dotnetTestRunner = _dotnetTestRunnerFactory.Create(dotnetTestParams.Port);
return dotnetTestRunner.RunTests(projectContext, dotnetTestParams);
return dotnetTestRunner.RunTests(projectContext, dotnetTestParams, workspace);
}
private static string GetProjectPath(string projectPath)

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using FluentAssertions;
using Microsoft.DotNet.ProjectModel.Graph;
using NuGet.Frameworks;
using Xunit;
namespace Microsoft.DotNet.ProjectModel.Tests
{
public class ProjectContextBuilderTests
{
private static readonly HashSet<string> KnownProperties = new HashSet<string>(StringComparer.Ordinal) {
"Project",
"LockFile",
"TargetFramework",
"RuntimeIdentifiers",
"RootDirectory",
"ProjectDirectory",
"PackagesDirectory",
"ReferenceAssembliesPath",
"IsDesignTime",
"ProjectResolver",
"LockFileResolver",
"ProjectReaderSettings"
};
// This test ensures that Clone is always kept up-to-date to avoid hard-to-debug errors
// because someone added a property but didn't put it in Clone.
[Fact]
public void CloneTest()
{
// Initialize a test instance that we're going to clone. Make sure all properties are initialized here.
var initialBuilder = new ProjectContextBuilder()
.WithProject(new Project())
.WithLockFile(new LockFile("abc"))
.WithTargetFramework(FrameworkConstants.CommonFrameworks.NetStandard10)
.WithRuntimeIdentifiers(new[] { "win7-x64", "osx.10.10-x64" })
.WithRootDirectory("C:\\The\\Root")
.WithProjectDirectory("/where/the/project/at")
.WithPackagesDirectory("D:\\My\\Awesome\\NuGet\\Packages")
.WithReferenceAssembliesPath("/these/are/the/reference/assemblies")
.WithProjectResolver(_ => new Project())
.WithLockFileResolver(_ => new LockFile("def"))
.WithProjectReaderSettings(new ProjectReaderSettings());
// Clone the builder
var clonedBuilder = initialBuilder.Clone();
// Compare all the properties. This is a shallow clone, so they should all be exactly ReferenceEqual
foreach(var prop in typeof(ProjectContextBuilder).GetTypeInfo().DeclaredProperties)
{
KnownProperties.Remove(prop.Name).Should().BeTrue(because: $"{prop.Name} should be listed in the known properties to ensure it is properly tested.");
if (prop.PropertyType.GetTypeInfo().IsValueType)
{
// Can't use reference equality on value types
prop.GetValue(clonedBuilder).Should().Be(prop.GetValue(initialBuilder), because: $"clone should have duplicated the {prop.Name} property");
}
else
{
prop.GetValue(clonedBuilder).Should().BeSameAs(prop.GetValue(initialBuilder), because: $"clone should have duplicated the {prop.Name} property");
}
}
KnownProperties.Should().BeEmpty(because: "all properties should have been checked by the CloneTest");
}
}
}

View file

@ -9,6 +9,7 @@ using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
using Microsoft.DotNet.TestFramework;
using System.Diagnostics;
namespace Microsoft.DotNet.Tools.Builder.Tests
{

View file

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO;
using FluentAssertions;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Compiler;
using Moq;
using NuGet.Frameworks;
using Xunit;
@ -20,7 +19,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
private Mock<ICompiler> _nativeCompilerMock;
private List<ProjectContext> _contexts;
private BuildCommandApp _args;
private readonly WorkspaceContext _workspace;
private readonly BuildWorkspace _workspace;
public GivenACompilationDriverController()
{
@ -35,13 +34,13 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()))
.Returns(true);
_workspace = WorkspaceContext.Create(ProjectReaderSettings.ReadFromEnvironment(), designTime: false);
_workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment());
_contexts = new List<ProjectContext>
{
_workspace.GetProjectContext(_projectJson, NuGetFramework.Parse("netcoreapp1.0"))
};
_args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform", WorkspaceContext.Create(designTime: false));
_args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform", _workspace);
}
[Fact]

View file

@ -187,7 +187,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
It.IsAny<string>()))
.Returns(command.Object);
var _args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform", WorkspaceContext.Create(designTime: false));
var _args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform", new BuildWorkspace(new ProjectReaderSettings()));
_args.ConfigValue = ConfigValue;
PreCompileScriptVariables = new Dictionary<string, string>();
@ -218,7 +218,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
rids.Add(rid);
}
var workspace = WorkspaceContext.Create(ProjectReaderSettings.ReadFromEnvironment(), designTime: false);
var workspace = new BuildWorkspace(new ProjectReaderSettings());
var context = workspace.GetRuntimeContext(workspace.GetProjectContext(projectJson, TestAssetFramework), rids);
context = workspace.GetRuntimeContext(context, rids);
managedCompiler.Compile(context, _args);

View file

@ -53,12 +53,12 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
Task.Run(() => ReadMessage(_readCancellationToken.Token), _readCancellationToken.Token);
}
public void SendPayLoad(Project project, string messageType)
public void SendPayload(Project project, string messageType)
{
SendPayLoad(project.ProjectDirectory, messageType);
SendPayload(project.ProjectDirectory, messageType);
}
public void SendPayLoad(string projectPath, string messageType)
public void SendPayload(string projectPath, string messageType)
{
int contextId;
if (!_projectContexts.TryGetValue(projectPath, out contextId))
@ -66,15 +66,15 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
Assert.True(false, $"Unable to resolve context for {projectPath}");
}
SendPayLoad(contextId, messageType);
SendPayload(contextId, messageType);
}
public void SendPayLoad(int contextId, string messageType)
public void SendPayload(int contextId, string messageType)
{
SendPayLoad(contextId, messageType, new { });
SendPayload(contextId, messageType, new { });
}
public void SendPayLoad(int contextId, string messageType, object payload)
public void SendPayload(int contextId, string messageType, object payload)
{
lock (_writer)
{
@ -94,7 +94,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
var contextId = _nextContextId++;
_projectContexts[projectPath] = contextId;
SendPayLoad(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath });
SendPayload(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath });
return contextId;
}
@ -104,7 +104,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
var contextId = _nextContextId++;
_projectContexts[projectPath] = contextId;
SendPayLoad(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion });
SendPayload(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion });
return contextId;
}
@ -114,14 +114,14 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
var contextId = _nextContextId++;
_projectContexts[projectPath] = contextId;
SendPayLoad(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion, Configuration = configuration });
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 });
SendPayload(0, MessageTypes.ProtocolVersion, new { Version = version });
}
public List<DthMessage> DrainMessage(int count)

View file

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.TestFramework;
using Microsoft.DotNet.Tools.Test.Utilities;
@ -260,7 +261,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
Path.Combine(projectPath, "home", GlobalSettings.FileName),
JsonConvert.SerializeObject(new { project = new string[] { "src" } }));
client.SendPayLoad(testProject, "RefreshDependencies");
client.SendPayload(testProject, "RefreshDependencies");
client.DrainTillFirst("ProjectInformation")
.RetrievePayloadAs<JObject>()
@ -379,7 +380,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
messages.ContainsMessage(MessageTypes.Error);
File.WriteAllText(projectFile, content);
client.SendPayLoad(testProject, MessageTypes.FilesChanged);
client.SendPayload(testProject, MessageTypes.FilesChanged);
var clearError = client.DrainTillFirst(MessageTypes.Error);
clearError.Payload.AsJObject().AssertProperty("Message", null as string);
}
@ -548,7 +549,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
File.WriteAllText(projectFilePath, JsonConvert.SerializeObject(projectJson));
client.SendPayLoad(projectPath, MessageTypes.RefreshDependencies);
client.SendPayload(projectPath, MessageTypes.RefreshDependencies);
var afterDependencies = client.DrainTillFirst(MessageTypes.Dependencies);
afterDependencies.RetrieveDependency(appName)
@ -597,7 +598,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
File.WriteAllText(projectFilePath, JsonConvert.SerializeObject(projectJson));
client.SendPayLoad(projectPath, MessageTypes.RefreshDependencies);
client.SendPayload(projectPath, MessageTypes.RefreshDependencies);
var afterDependencies = client.DrainTillFirst(MessageTypes.Dependencies);
afterDependencies.RetrieveDependency("MainApp")
@ -625,6 +626,82 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
}
}
[Fact]
public void TestTargetFrameworkChange()
{
using (var server = new DthTestServer())
using (var client = new DthTestClient(server))
{
var testProject = _testAssetsManager.CreateTestInstance("EmptyLibrary")
.WithLockFiles()
.TestRoot;
// initialize the project and drain all messages (7 message for project with one framework)
client.Initialize(testProject);
client.DrainAllMessages();
// update the target framework from netstandard1.3 to netstandard 1.5 so as to invalidate all
// dependencies
var projectJsonPath = Path.Combine(testProject, "project.json");
File.WriteAllText(projectJsonPath,
File.ReadAllText(projectJsonPath).Replace("netstandard1.3", "netstandard1.5"));
// send files change request to server to prompt update
client.SendPayload(testProject, MessageTypes.FilesChanged);
// assert project information is updated
client.DrainTillFirst(MessageTypes.ProjectInformation)
.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("Frameworks")
.AssertJArrayCount(1)
.RetrieveArraryElementAs<JObject>(0)
.AssertProperty("ShortName", "netstandard1.5");
// the NETStandard.Library dependency should turn unresolved
var dependencies = client.DrainTillFirst(MessageTypes.Dependencies);
dependencies.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JObject>("Framework")
.AssertProperty("ShortName", "netstandard1.5");
dependencies.RetrieveDependency("NETStandard.Library")
.RetrievePropertyAs<JArray>("Errors")
.AssertJArrayCount(1)
.RetrieveArraryElementAs<JObject>(0)
.AssertProperty("ErrorCode", "NU1001");
// warning for project.json and project.lock.json out of sync
var diagnostics = client.DrainTillFirst(MessageTypes.DependencyDiagnostics);
diagnostics.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JObject>("Framework")
.AssertProperty("ShortName", "netstandard1.5");
diagnostics.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("Warnings")
.AssertJArrayCount(1)
.RetrieveArraryElementAs<JObject>(0)
.AssertProperty("ErrorCode", "NU1006");
// restore again
var restoreCommand = new RestoreCommand();
restoreCommand.WorkingDirectory = testProject;
restoreCommand.Execute().Should().Pass();
client.SendPayload(testProject, MessageTypes.RefreshDependencies);
client.DrainTillFirst(MessageTypes.Dependencies)
.RetrieveDependency("NETStandard.Library")
.RetrievePropertyAs<JArray>("Errors")
.AssertJArrayCount(0);
client.DrainTillFirst(MessageTypes.DependencyDiagnostics)
.RetrievePayloadAs<JObject>()
.RetrievePropertyAs<JArray>("Warnings")
.AssertJArrayCount(0);
}
}
private static string NormalizePathString(string original)
{
return original.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);

View file

@ -29,7 +29,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
{
_dotnetTestRunnerMock = new Mock<IDotnetTestRunner>();
_dotnetTestRunnerMock
.Setup(d => d.RunTests(It.IsAny<ProjectContext>(), It.IsAny<DotnetTestParams>()))
.Setup(d => d.RunTests(It.IsAny<ProjectContext>(), It.IsAny<DotnetTestParams>(), It.IsAny<BuildWorkspace>()))
.Returns(0);
_dotnetTestRunnerFactoryMock = new Mock<IDotnetTestRunnerFactory>();
@ -62,7 +62,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
var result = _testCommand.DoRun(new[] { ProjectJsonPath, "-f", "netcoreapp1.0" });
_dotnetTestRunnerMock.Verify(
d => d.RunTests(It.IsAny<ProjectContext>(), It.IsAny<DotnetTestParams>()),
d => d.RunTests(It.IsAny<ProjectContext>(), It.IsAny<DotnetTestParams>(), It.IsAny<BuildWorkspace>()),
Times.Once);
}
}