提交 abd7654e 编写于 作者: M mattwar

This changes removes the ProjectDependencyService and replaces the Solution's...

This changes removes the ProjectDependencyService and replaces the Solution's itnernal transitive dependency tracking with the ProjectDependencyGraph.  After the change removing async projects from solutions, the service was no longer necessary, as dependency graphs (mostly on demand) can be computed and reused for most solution branching.  The solution's internal transitive dependency tracking then became redundant. (changeset 1210210)
上级 30c925ba
......@@ -21,7 +21,7 @@ internal partial class FindReferencesSearchEngine
private readonly ImmutableList<IReferenceFinder> finders;
private readonly IFindReferencesProgress progress;
private readonly CancellationToken cancellationToken;
private readonly AsyncLazy<ProjectDependencyGraph> lazyDependencyGraph;
private readonly ProjectDependencyGraph dependencyGraph;
/// <summary>
/// Mapping from a document to the list of reference locations found in it. Kept around so
......@@ -49,7 +49,7 @@ internal partial class FindReferencesSearchEngine
this.finders = finders;
this.progress = progress;
this.cancellationToken = cancellationToken;
this.lazyDependencyGraph = new AsyncLazy<ProjectDependencyGraph>(c => solution.GetProjectDependencyGraphAsync(c), cacheResult: true);
this.dependencyGraph = solution.GetProjectDependencyGraph();
}
public async Task<IEnumerable<ReferencedSymbol>> FindReferencesAsync(ISymbol symbol)
......@@ -89,8 +89,7 @@ public async Task<IEnumerable<ReferencedSymbol>> FindReferencesAsync(ISymbol sym
// Get the connected components of the dependency graph and process each individually.
// That way once a component is done we can throw away all the memory associated with
// it.
var graph = await this.lazyDependencyGraph.GetValueAsync(cancellationToken).ConfigureAwait(false);
var connectedProjects = graph.GetConnectedProjects(cancellationToken);
var connectedProjects = this.dependencyGraph.GetDependencySets(cancellationToken);
var projectMap = CreateProjectMap(documentMap);
foreach (var projectSet in connectedProjects)
......
......@@ -553,7 +553,7 @@ private async Task FindDocumentsAndPossibleNameConflicts()
var symbol = renameLocationSet.Symbol;
var newSolution = renameLocationSet.Solution;
var dependencyGraph = await newSolution.GetProjectDependencyGraphAsync(cancellationToken).ConfigureAwait(false);
var dependencyGraph = newSolution.GetProjectDependencyGraph();
this.topologicallySortedProjects = dependencyGraph.GetTopologicallySortedProjects(cancellationToken).ToList();
if (symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Label || symbol.Kind == SymbolKind.RangeVariable)
......
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.WorkspaceServices;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
internal static class ProjectDependencyService
{
private const string PersistenceName = "<GRAPH>";
private static readonly ConditionalWeakTable<Workspace, WorkspaceTracker> workspaceTrackers =
new ConditionalWeakTable<Workspace, WorkspaceTracker>();
public static async Task<ProjectDependencyGraph> GetDependencyGraphAsync(Solution solution, CancellationToken cancellationToken)
{
var tracker = TrackWorkspace(solution.Workspace);
var oldGraph = await tracker.GetGraphAsync(cancellationToken).ConfigureAwait(false);
return ProjectDependencyGraph.From(solution, oldGraph, cancellationToken);
}
private static WorkspaceTracker TrackWorkspace(Workspace workspace)
{
// only add if the workspace is not currently being tracked.
return workspaceTrackers.GetValue(workspace, ws => new WorkspaceTracker(workspace));
}
private static void StopTracking(Workspace workspace)
{
workspaceTrackers.Remove(workspace);
}
/// <summary>
/// The workspace tracker tracks workspaces changes and resets the lazily created graph if
/// project references change
/// </summary>
private class WorkspaceTracker
{
private readonly WeakReference<Workspace> weakWorkspace;
private readonly NonReentrantLock lazyGate = new NonReentrantLock();
private SolutionId solutionId;
private AsyncLazy<ProjectDependencyGraph> lazyGraph;
internal WorkspaceTracker(Workspace workspace)
{
this.weakWorkspace = new WeakReference<Workspace>(workspace);
var solutionToLoad = workspace.CurrentSolution;
this.solutionId = solutionToLoad.Id;
this.lazyGraph = new AsyncLazy<ProjectDependencyGraph>(c => LoadOrComputeGraphAsync(solutionToLoad, c), cacheResult: true);
workspace.WorkspaceChanged += this.OnWorkspaceChanged;
}
private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args)
{
if (args.Kind == WorkspaceChangeKind.SolutionAdded)
{
// no lock needed for solutionId. it will be only mutated in this
// method and it is already serialized.
this.solutionId = args.NewSolution.Id;
this.RecomputeGraphLazily(args.NewSolution);
return;
}
if (args.Kind == WorkspaceChangeKind.SolutionRemoved)
{
if (args.OldSolution.Id == this.solutionId)
{
this.StopTracking();
}
return;
}
if (args.NewSolution.Id == this.solutionId)
{
switch (args.Kind)
{
case WorkspaceChangeKind.SolutionCleared:
this.RecomputeGraphLazily(args.NewSolution);
break;
case WorkspaceChangeKind.SolutionChanged:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectAdded:
case WorkspaceChangeKind.ProjectRemoved:
case WorkspaceChangeKind.ProjectReloaded:
this.RecomputeLazilyOrUpdateGraph(args.NewSolution, args.ProjectId);
break;
default:
this.RecomputeLazilyOrMoveToLatestSolution(args.NewSolution, args.ProjectId);
break;
}
}
}
public void StopTracking()
{
// time to save?
var lazyGraph = this.GetLazyGraph(CancellationToken.None);
ProjectDependencyGraph graph;
if (lazyGraph.TryGetValue(out graph))
{
this.SaveGraphAsync(graph, CancellationToken.None).Wait();
}
Workspace workspace;
if (this.weakWorkspace.TryGetTarget(out workspace))
{
workspace.WorkspaceChanged -= this.OnWorkspaceChanged;
ProjectDependencyService.StopTracking(workspace);
}
}
public Task<ProjectDependencyGraph> GetGraphAsync(CancellationToken cancellationToken)
{
return this.GetLazyGraph(cancellationToken).GetValueAsync(cancellationToken);
}
private AsyncLazy<ProjectDependencyGraph> GetLazyGraph(CancellationToken cancellationToken)
{
using (this.lazyGate.DisposableWait(cancellationToken))
{
return this.lazyGraph;
}
}
private void SetLazyGraph(AsyncLazy<ProjectDependencyGraph> lazyGraph)
{
using (this.lazyGate.DisposableWait(CancellationToken.None))
{
this.lazyGraph = lazyGraph;
}
}
private async Task<ProjectDependencyGraph> LoadOrComputeGraphAsync(Solution solution, CancellationToken cancellationToken)
{
Contract.ThrowIfFalse(solution.BranchId == solution.Workspace.PrimaryBranchId);
// TODO: make this async too
var persistenceService = WorkspaceService.GetService<IPersistentStorageService>(solution.Workspace);
ProjectDependencyGraph graph;
using (var storage = persistenceService.GetStorage(solution))
using (var stream = await storage.ReadStreamAsync(PersistenceName, cancellationToken).ConfigureAwait(false))
{
if (stream != null)
{
using (var reader = new ObjectReader(stream))
{
graph = ProjectDependencyGraph.From(solution, reader, cancellationToken);
if (graph != null)
{
return graph;
}
}
}
}
cancellationToken.ThrowIfCancellationRequested();
// do it the hard way!
graph = ProjectDependencyGraph.From(solution, cancellationToken);
// since we built it, we may as well save it for next time
await SaveGraphAsync(graph, cancellationToken).ConfigureAwait(false);
return graph;
}
private async Task SaveGraphAsync(ProjectDependencyGraph graph, CancellationToken cancellationToken)
{
Contract.ThrowIfFalse(graph.Solution.BranchId == graph.Solution.Workspace.PrimaryBranchId);
using (var stream = SerializableBytes.CreateWritableStream())
using (var writer = new ObjectWriter(stream))
{
graph.WriteTo(writer);
stream.Position = 0;
var persistenceService = WorkspaceService.GetService<IPersistentStorageService>(graph.Solution.Workspace);
using (var storage = persistenceService.GetStorage(graph.Solution))
{
await storage.WriteStreamAsync(PersistenceName, stream, cancellationToken).ConfigureAwait(false);
}
}
}
public void RecomputeGraphLazily(Solution solution)
{
SetLazyGraph(new AsyncLazy<ProjectDependencyGraph>(c => Task.FromResult(ProjectDependencyGraph.From(solution, c)), cacheResult: true));
}
private static readonly Func<Solution, ProjectDependencyGraph, CancellationToken, Task<ProjectDependencyGraph>> updateReferences =
(s, g, c) => Task.FromResult(ProjectDependencyGraph.From(s, g, c));
private void RecomputeLazilyOrUpdateGraph(Solution solution, ProjectId projectID)
{
RecomputeLazilyOrMakeGraphUpToDate(solution, projectID, updateReferences);
}
private static readonly Func<Solution, ProjectDependencyGraph, CancellationToken, Task<ProjectDependencyGraph>> withNewSolution =
(s, g, c) => Task.FromResult(g.WithNewSolution(s));
private void RecomputeLazilyOrMoveToLatestSolution(Solution solution, ProjectId projectID)
{
RecomputeLazilyOrMakeGraphUpToDate(solution, projectID, withNewSolution);
}
private void RecomputeLazilyOrMakeGraphUpToDate(Solution solution, ProjectId projectID, Func<Solution, ProjectDependencyGraph, CancellationToken, Task<ProjectDependencyGraph>> newGraphGetter)
{
var lazyGraph = this.GetLazyGraph(CancellationToken.None);
ProjectDependencyGraph graph;
if (!lazyGraph.TryGetValue(out graph) ||
(projectID != null && !graph.Solution.ProjectIds.Contains(projectID)))
{
RecomputeGraphLazily(solution);
return;
}
this.SetLazyGraph(new AsyncLazy<ProjectDependencyGraph>(c => newGraphGetter(solution, graph, c), cacheResult: true));
}
}
}
}
\ No newline at end of file
......@@ -36,7 +36,7 @@ public partial class Solution
private readonly ImmutableDictionary<ProjectId, ProjectState> projectIdToProjectStateMap;
private readonly VersionStamp version;
private readonly Lazy<VersionStamp> lazyLatestProjectVersion;
private readonly ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> transitiveDependencyMap;
private readonly ProjectDependencyGraph dependencyGraph;
// Values for all these are created on demand.
private ImmutableHashMap<ProjectId, Project> projectIdToProjectMap;
......@@ -51,7 +51,7 @@ public partial class Solution
ImmutableList<ProjectId> projectIds,
ImmutableDictionary<ProjectId, ProjectState> idToProjectStateMap,
ImmutableDictionary<ProjectId, CompilationTracker> projectIdToTrackerMap,
ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> transitiveDependencyMap,
ProjectDependencyGraph dependencyGraph,
VersionStamp version,
Lazy<VersionStamp> lazyLatestProjectVersion)
{
......@@ -63,7 +63,7 @@ public partial class Solution
this.projectIds = projectIds;
this.projectIdToProjectStateMap = idToProjectStateMap;
this.projectIdToTrackerMap = projectIdToTrackerMap;
this.transitiveDependencyMap = transitiveDependencyMap;
this.dependencyGraph = dependencyGraph;
this.projectIdToProjectMap = ImmutableHashMap<ProjectId, Project>.Empty;
this.version = version;
this.lazyLatestProjectVersion = lazyLatestProjectVersion;
......@@ -85,7 +85,7 @@ public partial class Solution
projectIds: ImmutableList.Create<ProjectId>(),
idToProjectStateMap: ImmutableDictionary<ProjectId, ProjectState>.Empty,
projectIdToTrackerMap: ImmutableDictionary<ProjectId, CompilationTracker>.Empty,
transitiveDependencyMap: ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>>.Empty,
dependencyGraph: ProjectDependencyGraph.Empty,
lazyLatestProjectVersion: null)
{
this.lazyLatestProjectVersion = new Lazy<VersionStamp>(() => ComputeLatestProjectVersion());
......@@ -210,7 +210,7 @@ private void CheckInvariants()
ImmutableList<ProjectId> projectIds = null,
ImmutableDictionary<ProjectId, ProjectState> idToProjectStateMap = null,
ImmutableDictionary<ProjectId, CompilationTracker> projectIdToTrackerMap = null,
ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> transitiveDependencyMap = null,
ProjectDependencyGraph dependencyGraph = null,
VersionStamp? version = default(VersionStamp?),
Lazy<VersionStamp> lazyLatestProjectVersion = null)
{
......@@ -219,7 +219,7 @@ private void CheckInvariants()
projectIds = projectIds ?? this.projectIds;
idToProjectStateMap = idToProjectStateMap ?? this.projectIdToProjectStateMap;
projectIdToTrackerMap = projectIdToTrackerMap ?? this.projectIdToTrackerMap;
transitiveDependencyMap = transitiveDependencyMap ?? this.transitiveDependencyMap;
dependencyGraph = dependencyGraph ?? this.dependencyGraph;
version = version.HasValue ? version.Value : this.version;
lazyLatestProjectVersion = lazyLatestProjectVersion ?? this.lazyLatestProjectVersion;
......@@ -227,7 +227,7 @@ private void CheckInvariants()
projectIds == this.projectIds &&
idToProjectStateMap == this.projectIdToProjectStateMap &&
projectIdToTrackerMap == this.projectIdToTrackerMap &&
transitiveDependencyMap == this.transitiveDependencyMap &&
dependencyGraph == this.dependencyGraph &&
version == this.version &&
lazyLatestProjectVersion == this.lazyLatestProjectVersion)
{
......@@ -243,7 +243,7 @@ private void CheckInvariants()
projectIds,
idToProjectStateMap,
projectIdToTrackerMap,
transitiveDependencyMap,
dependencyGraph,
version.Value,
lazyLatestProjectVersion);
}
......@@ -269,7 +269,7 @@ private void CheckInvariants()
this.projectIds,
this.projectIdToProjectStateMap,
this.projectIdToTrackerMap,
this.transitiveDependencyMap,
this.dependencyGraph,
this.version,
this.lazyLatestProjectVersion);
}
......@@ -502,14 +502,16 @@ private CompilationTracker GetCompilationTracker(ProjectId projectId)
private Solution AddProject(ProjectId projectId, ProjectState projectState)
{
var newTransitiveMap = ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>>.Empty;
var newTrackerMap = CreateCompilationTrackerMap(projectId, withProjectReferenceChange: true, newTransitiveMap: newTransitiveMap);
var newProjectIds = this.projectIds.Add(projectId);
var newStateMap = this.projectIdToProjectStateMap.Add(projectId, projectState);
var newDependencyGraph = CreateDependencyGraph(newProjectIds, newStateMap);
var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph);
return this.Branch(
projectIds: this.projectIds.Add(projectId),
idToProjectStateMap: this.projectIdToProjectStateMap.Add(projectId, projectState),
projectIds: newProjectIds,
idToProjectStateMap: newStateMap,
projectIdToTrackerMap: newTrackerMap,
transitiveDependencyMap: newTransitiveMap,
dependencyGraph: newDependencyGraph,
version: this.Version.GetNewerVersion(), // changed project list so, increment version.
lazyLatestProjectVersion: new Lazy<VersionStamp>(() => projectState.Version)); // this is the newest!
}
......@@ -597,14 +599,16 @@ public Solution RemoveProject(ProjectId projectId)
CheckContainsProject(projectId);
var newTransitiveMap = ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>>.Empty;
var newTrackerMap = CreateCompilationTrackerMap(projectId, withProjectReferenceChange: true, newTransitiveMap: newTransitiveMap);
var newProjectIds = this.projectIds.Remove(projectId);
var newStateMap = this.projectIdToProjectStateMap.Remove(projectId);
var newDependencyGraph = CreateDependencyGraph(newProjectIds, newStateMap);
var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph);
return this.Branch(
projectIds: this.projectIds.Remove(projectId),
idToProjectStateMap: this.projectIdToProjectStateMap.Remove(projectId),
projectIds: newProjectIds,
idToProjectStateMap: newStateMap,
projectIdToTrackerMap: newTrackerMap.Remove(projectId),
transitiveDependencyMap: newTransitiveMap,
dependencyGraph: newDependencyGraph,
version: this.Version.GetNewerVersion()); // changed project list, so increment version
}
......@@ -1306,8 +1310,8 @@ private Solution TouchDocument(DocumentId documentId, Func<ProjectState, Project
var projectId = newProjectState.Id;
var newStateMap = this.projectIdToProjectStateMap.SetItem(projectId, newProjectState);
var newTransitiveMap = GetTransitiveDependencyMap(projectId, withProjectReferenceChange);
var newTrackerMap = CreateCompilationTrackerMap(projectId, withProjectReferenceChange, newTransitiveMap);
var newDependencyGraph = withProjectReferenceChange ? CreateDependencyGraph(this.projectIds, newStateMap) : this.dependencyGraph;
var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph);
// If we have a tracker for this project, then fork it as well (along with the
// translation action and store it in the tracker map.
......@@ -1323,48 +1327,27 @@ private Solution TouchDocument(DocumentId documentId, Func<ProjectState, Project
return this.Branch(
idToProjectStateMap: newStateMap,
projectIdToTrackerMap: newTrackerMap,
transitiveDependencyMap: newTransitiveMap,
dependencyGraph: newDependencyGraph,
lazyLatestProjectVersion: newLatestProjectVersion);
}
private ImmutableDictionary<ProjectId, CompilationTracker> CreateCompilationTrackerMap(
ProjectId projectId, bool withProjectReferenceChange, ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> newTransitiveMap)
private static ProjectDependencyGraph CreateDependencyGraph(
ImmutableList<ProjectId> projectIds,
ImmutableDictionary<ProjectId, ProjectState> projectStates)
{
// Create a copy of the tracker map.
//
// 1) If we have no compilation computed at all in the tracker, then we can just ignore
// this tracker object.
// 2) we can reuse trackers for projects not dependent on this newProject. Otherwise, we
// have to fork them.
// first check whether this fork is due to reference changes
ImmutableHashSet<ProjectId> transitiveSet;
if (!withProjectReferenceChange)
{
// okay, it is not due to a reference change, current or new transitivie map should contain logically same information.
// let's just use the new transitive map
// (new map could have more information since the map will be filled up lazily)
transitiveSet = newTransitiveMap[projectId];
ValidateTransitiveDependencyMap(projectId, transitiveSet);
return CreateCompilationTrackerMap(projectId, id => transitiveSet.Contains(id));
}
// we have reference changes, let's see whether we can use current transitive map
if (this.transitiveDependencyMap.TryGetValue(projectId, out transitiveSet))
{
// Yes, it is already calculated, we can reuse existing one
ValidateTransitiveDependencyMap(projectId, transitiveSet);
return CreateCompilationTrackerMap(projectId, id => transitiveSet.Contains(id));
}
var map = projectStates.Values.Select(state => new KeyValuePair<ProjectId, ImmutableHashSet<ProjectId>>(
state.Id,
state.ProjectReferences.Where(pr => projectStates.ContainsKey(pr.ProjectId)).Select(pr => pr.ProjectId).ToImmutableHashSet()))
.ToImmutableDictionary();
// we never calcualted this information. do it here
return CreateCompilationTrackerMap(projectId, id => this.HasTransitiveDependency(id, projectId));
return new ProjectDependencyGraph(projectIds, map);
}
private ImmutableDictionary<ProjectId, CompilationTracker> CreateCompilationTrackerMap(ProjectId projectId, Func<ProjectId, bool> hasTransitiveDependency)
private ImmutableDictionary<ProjectId, CompilationTracker> CreateCompilationTrackerMap(ProjectId projectId, ProjectDependencyGraph dependencyGraph)
{
var builder = ImmutableDictionary.CreateBuilder<ProjectId, CompilationTracker>();
var dependencies = dependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(projectId);
foreach (var projectIdAndTracker in this.projectIdToTrackerMap)
{
var id = projectIdAndTracker.Key;
......@@ -1375,87 +1358,13 @@ private Solution TouchDocument(DocumentId documentId, Func<ProjectState, Project
continue;
}
var canReuse = id == projectId || !hasTransitiveDependency(id);
var canReuse = id == projectId || !dependencies.Contains(id);
builder.Add(id, canReuse ? tracker : tracker.Fork(tracker.ProjectState));
}
return builder.ToImmutable();
}
[Conditional("DEBUG")]
private void ValidateTransitiveDependencyMap(ProjectId projectId, ImmutableHashSet<ProjectId> transitiveMap)
{
var projectsDependOnMe = GetProjectsDependOnMe(projectId);
Contract.ThrowIfFalse(transitiveMap.SetEquals(projectsDependOnMe));
}
private ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>> GetTransitiveDependencyMap(ProjectId projectId, bool withProjectReferenceChange)
{
if (withProjectReferenceChange)
{
// we have project reference change, we need to re-create whole map again
return ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>>.Empty;
}
// we are having none reference related changes and we already have a transitive map for given project.
// re-use the map as it is
if (this.transitiveDependencyMap.ContainsKey(projectId))
{
// we can reuse existing transitive dependency map as it is
return this.transitiveDependencyMap;
}
// we didn't calculate the map yet. do it right here. this map is for the forked project,
// but since there is no project reference change, it should be okay to calculate the map using current p2p information.
var projectsDependOnMe = GetProjectsDependOnMe(projectId);
return this.transitiveDependencyMap.Add(projectId, projectsDependOnMe);
}
private ImmutableHashSet<ProjectId> GetProjectsDependOnMe(ProjectId projectId)
{
return ImmutableHashSet.CreateRange<ProjectId>(
from id in this.projectIds
where id != projectId && this.HasTransitiveDependency(id, projectId)
select id);
}
private bool HasTransitiveDependency(ProjectId fromProject, ProjectId toProject)
{
var seenProjects = SharedPools.Default<HashSet<ProjectId>>().AllocateAndClear();
var result = HasTransitiveDependency(fromProject, toProject, seenProjects);
SharedPools.Default<HashSet<ProjectId>>().ClearAndFree(seenProjects);
return result;
}
private bool HasTransitiveDependency(ProjectId fromProject, ProjectId toProject, HashSet<ProjectId> seenProjects)
{
if (fromProject == toProject)
{
return true;
}
// Don't go down projects multiple times.
if (!seenProjects.Add(fromProject))
{
return false;
}
var project = this.GetProjectState(fromProject);
if (project != null)
{
foreach (var projectReference in project.ProjectReferences)
{
if (HasTransitiveDependency(projectReference.ProjectId, toProject, seenProjects))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Gets a copy of the solution isolated from the original so that they do not share computed state.
///
......@@ -1530,7 +1439,7 @@ internal async Task<Solution> WithFrozenPartialCompilationIncludingSpecificDocum
currentPartialSolution = this.Branch(
idToProjectStateMap: newIdToProjectStateMap,
projectIdToTrackerMap: newIdToTrackerMap,
transitiveDependencyMap: ImmutableDictionary<ProjectId, ImmutableHashSet<ProjectId>>.Empty);
dependencyGraph: CreateDependencyGraph(this.projectIds, newIdToProjectStateMap));
this.latestSolutionWithPartialCompilation = new WeakReference<Solution>(currentPartialSolution);
this.timeOfLatestSolutionWithPartialCompilation = DateTime.UtcNow;
......@@ -1685,9 +1594,9 @@ public SolutionChanges GetChanges(Solution oldSolution)
/// <summary>
/// Gets an ProjectDependencyGraph that details the dependencies between projects for this solution.
/// </summary>
public Task<ProjectDependencyGraph> GetProjectDependencyGraphAsync(CancellationToken cancellationToken = default(CancellationToken))
public ProjectDependencyGraph GetProjectDependencyGraph()
{
return ProjectDependencyService.GetDependencyGraphAsync(this, cancellationToken);
return this.dependencyGraph;
}
private void CheckNotContainsProject(ProjectId projectId)
......@@ -1716,7 +1625,8 @@ private void CheckNotContainsProjectReference(ProjectId projectId, ProjectRefere
private void CheckNotContainsTransitiveReference(ProjectId fromProjectId, ProjectId toProjectId)
{
if (HasTransitiveDependency(fromProjectId, toProjectId))
var dependents = this.dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(fromProjectId);
if (dependents.Contains(toProjectId))
{
throw new InvalidOperationException(WorkspacesResources.ProjectTransitivelyReferencesTargetProject);
}
......
......@@ -854,7 +854,6 @@
<Compile Include="Workspace\Solution\Project.EquivalenceResult.cs" />
<Compile Include="Workspace\Solution\ProjectChanges.cs" />
<Compile Include="Workspace\Solution\ProjectDependencyGraph.cs" />
<Compile Include="Workspace\Solution\ProjectDependencyService.cs" />
<Compile Include="Workspace\Solution\ProjectDiagnostic.cs" />
<Compile Include="Workspace\Solution\ProjectId.cs" />
<Compile Include="Workspace\Solution\ProjectInfo.cs" />
......
......@@ -30,7 +30,7 @@ public void ProjectDependencyGraph_GetTopologicallySortedProjects()
private void VerifyTopologicalSort(string projectReferences, params string[] expectedResults)
{
Solution solution = CreateSolutionFromReferenceMap(projectReferences);
var projectDependencyGraph = ProjectDependencyGraph.From(solution, CancellationToken.None);
var projectDependencyGraph = solution.GetProjectDependencyGraph();
var projectIds = projectDependencyGraph.GetTopologicallySortedProjects(CancellationToken.None);
var actualResult = string.Concat(projectIds.Select(id => solution.GetProject(id).AssemblyName));
......@@ -39,24 +39,24 @@ private void VerifyTopologicalSort(string projectReferences, params string[] exp
#endregion
#region GetConnectedProjects
#region Dependency Sets
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
[WorkItem(542438)]
public void ProjectDependencyGraph_GetConnectedProjects()
public void ProjectDependencyGraph_GetDependencySets()
{
VerifyConnectedProjects("A B:A C:A D E:D F:D", "ABC DEF");
VerifyConnectedProjects("A B:A,C C", "ABC");
VerifyConnectedProjects("A B", "A B");
VerifyConnectedProjects("A B C:B", "A BC");
VerifyConnectedProjects("A B:A C:A D:B,C", "ABCD");
VerifyDependencySets("A B:A C:A D E:D F:D", "ABC DEF");
VerifyDependencySets("A B:A,C C", "ABC");
VerifyDependencySets("A B", "A B");
VerifyDependencySets("A B C:B", "A BC");
VerifyDependencySets("A B:A C:A D:B,C", "ABCD");
}
private void VerifyConnectedProjects(string projectReferences, string expectedResult)
private void VerifyDependencySets(string projectReferences, string expectedResult)
{
Solution solution = CreateSolutionFromReferenceMap(projectReferences);
var projectDependencyGraph = ProjectDependencyGraph.From(solution, CancellationToken.None);
var projectIds = projectDependencyGraph.GetConnectedProjects(CancellationToken.None);
var projectDependencyGraph = solution.GetProjectDependencyGraph();
var projectIds = projectDependencyGraph.GetDependencySets(CancellationToken.None);
var actualResult = string.Join(" ",
projectIds.Select(
group => string.Concat(
......@@ -66,89 +66,29 @@ private void VerifyConnectedProjects(string projectReferences, string expectedRe
#endregion
#region Serialization
#region GetProjectsThatThisProjectTransitivelyDependsOn
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void ProjectDependencyGraph_RoundTripToText()
public void ProjectDependencyGraph_GetProjectsThatThisProjectTransitivelyDependsOn()
{
string projectReferences = "B:A A C:A D:C,B";
// due to the way version is serialized and deserialized, we need to make sure there is no version collision during
// building solution. otherwise, original version will have higher version than deserialized version and make
// creating graph from persistance source fail.
var solution = CreateSolutionFromReferenceMap(projectReferences);
var graph = ProjectDependencyGraph.From(solution, CancellationToken.None);
var text = GetGraphText(graph);
// write graph to stream
using (var stream = new MemoryStream())
{
using (var writer = new ObjectWriter(stream))
{
graph.WriteTo(writer);
}
stream.Position = 0;
ProjectDependencyGraph newGraph;
using (var reader = new ObjectReader(stream))
{
// read graph back from stream
newGraph = ProjectDependencyGraph.ReadGraph(solution, reader, CancellationToken.None);
}
var newText = GetGraphText(newGraph);
Assert.Equal(text, newText);
}
}
private string GetGraphText(string projectReferences = "B:A A C:A D:C,B")
{
var graph = CreateGraph(projectReferences);
return GetGraphText(graph);
VerifyTransitiveReferences("A", "A", new string[] { });
VerifyTransitiveReferences("B:A A", "B", new string[] { "A" });
VerifyTransitiveReferences("C:B B:A A", "C", new string[] { "B", "A" });
VerifyTransitiveReferences("C:B B:A A", "A", new string[] { });
}
private string GetGraphText(ProjectDependencyGraph graph)
private void VerifyTransitiveReferences(string projectReferences, string project, string[] expectedResults)
{
using (var stream = new MemoryStream())
using (var writer = new ObjectWriter(stream))
{
graph.WriteTo(writer);
stream.Position = 0;
return Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length);
}
}
#endregion
Solution solution = CreateSolutionFromReferenceMap(projectReferences);
var projectDependencyGraph = solution.GetProjectDependencyGraph();
var projectId = solution.GetProjectsByName(project).Single().Id;
var projectIds = projectDependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(projectId);
#region From
var actualResults = projectIds.Select(id => solution.GetProject(id).Name);
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void ProjectDependencyGraph_FromExistingGraph()
{
var staleSolution = CreateSolution("stale");
var staleGraph = ProjectDependencyGraph.From(staleSolution, CancellationToken.None);
var solution = CreateSolutionFromReferenceMap("A B C");
var projectA = solution.GetProjectsByName("A").FirstOrDefault().Id;
var projectB = solution.GetProjectsByName("B").FirstOrDefault().Id;
var projectC = solution.GetProjectsByName("C").FirstOrDefault().Id;
var graph = ProjectDependencyGraph.From(solution, staleGraph, CancellationToken.None);
solution = solution.AddProjectReference(projectB, new ProjectReference(projectA));
solution = solution.RemoveProject(projectC);
AddProject(ref solution, 4, "D");
var projectD = solution.GetProjectsByName("D").FirstOrDefault().Id;
solution = solution.AddProjectReference(projectD, new ProjectReference(projectA));
graph = ProjectDependencyGraph.From(solution, graph, CancellationToken.None);
// we don't have an ordering guarantee. So consider both cases;
var test1 = new[] { projectB, projectD }.SequenceEqual(graph.GetProjectsThatDirectlyDependOnThisProject(projectA));
var test2 = new[] { projectD, projectB }.SequenceEqual(graph.GetProjectsThatDirectlyDependOnThisProject(projectA));
Assert.True(test1 || test2, "test1 == " + test1 + ", test2 == " + test2);
AssertEx.Equal(new[] { projectA }, graph.GetProjectsThatThisProjectDirectlyDependsOn(projectB));
Assert.Equal<string>(
expectedResults.OrderBy(n => n),
actualResults.OrderBy(n => n));
}
#endregion
......@@ -158,21 +98,25 @@ public void ProjectDependencyGraph_FromExistingGraph()
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void ProjectDependencyGraph_GetProjectsThatTransitivelyDependOnThisProject()
{
VerifyTransitiveReferences("A", "A", "");
VerifyTransitiveReferences("B:A A", "B", "A");
VerifyTransitiveReferences("C:B B:A A", "C", "BA");
VerifyTransitiveReferences("C:B B:A A", "A", "");
VerifyReverseTransitiveReferences("A", "A", new string[] { });
VerifyReverseTransitiveReferences("B:A A", "A", new string[] { "B" });
VerifyReverseTransitiveReferences("C:B B:A A", "A", new string[] { "B", "C" });
VerifyReverseTransitiveReferences("C:B B:A A", "C", new string[] { });
VerifyReverseTransitiveReferences("D:C,B B:A C A", "A", new string[] { "D", "B" });
}
private void VerifyTransitiveReferences(string projectReferences, string project, params string[] expectedResults)
private void VerifyReverseTransitiveReferences(string projectReferences, string project, string[] expectedResults)
{
Solution solution = CreateSolutionFromReferenceMap(projectReferences);
var projectDependencyGraph = ProjectDependencyGraph.From(solution, CancellationToken.None);
var projectDependencyGraph = solution.GetProjectDependencyGraph();
var projectId = solution.GetProjectsByName(project).Single().Id;
var projectIds = projectDependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(projectId);
var projectIds = projectDependencyGraph.GetProjectsThatTransitivelyDependOnThisProject(projectId);
var actualResult = string.Concat(projectIds.Select(id => solution.GetProject(id).AssemblyName));
Assert.Contains<string>(actualResult, expectedResults);
var actualResults = projectIds.Select(id => solution.GetProject(id).Name);
Assert.Equal<string>(
expectedResults.OrderBy(n => n),
actualResults.OrderBy(n => n));
}
#endregion
......@@ -182,7 +126,7 @@ private void VerifyTransitiveReferences(string projectReferences, string project
private ProjectDependencyGraph CreateGraph(string projectReferences)
{
var solution = CreateSolutionFromReferenceMap(projectReferences);
return ProjectDependencyGraph.From(solution, CancellationToken.None);
return solution.GetProjectDependencyGraph();
}
private Solution CreateSolutionFromReferenceMap(string projectReferences)
......
......@@ -137,8 +137,7 @@
<Compile Include="TestWorkspace.cs" />
<Compile Include="WorkspaceExtensions.cs" />
<Compile Include="WorkspaceServices\OptionServiceTests.cs" />
<Compile Include="WorkspaceServices\ProjectDependencyGraphTests.cs" />
<Compile Include="WorkspaceServices\ProjectDependencyServiceTests.cs" />
<Compile Include="ProjectDependencyGraphTests.cs" />
<Compile Include="WorkspaceServices\TestPersistenceService.cs" />
<Compile Include="WorkspaceServices\TestWorkspaceServiceProvider.cs" />
<EmbeddedResource Include="TestFiles\CSharpProject_App.xaml">
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;
using CS = Microsoft.CodeAnalysis.CSharp;
namespace Microsoft.CodeAnalysis.Host.UnitTests
{
public class ProjectDependencyServiceTests : TestBase
{
[WorkItem(8683, "DevDiv_Projects/Roslyn"), WorkItem(542393)]
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestMoveToLatestSolution()
{
var workspace = new TestWorkspace();
var solution = workspace.CurrentSolution;
var project1 = workspace.AddProject("P1");
var graph = ProjectDependencyService.GetDependencyGraphAsync(workspace.CurrentSolution, CancellationToken.None).Result;
var project2 = workspace.AddProject("P2");
graph = ProjectDependencyService.GetDependencyGraphAsync(workspace.CurrentSolution, CancellationToken.None).Result;
var sortedProjects = graph.GetTopologicallySortedProjects();
AssertEx.SetEqual(sortedProjects, project1, project2);
workspace.OnAssemblyNameChanged(project1, "ChangedP1");
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void Test_WorkspaceChanges()
{
var workspace = new TestWorkspace();
var solution = workspace.CurrentSolution;
var project1 = workspace.AddProject("P1");
var graph = ProjectDependencyService.GetDependencyGraphAsync(workspace.CurrentSolution, CancellationToken.None).Result;
var project2 = workspace.AddProject("P2");
graph = ProjectDependencyService.GetDependencyGraphAsync(workspace.CurrentSolution, CancellationToken.None).Result;
var sortedProjects = graph.GetTopologicallySortedProjects();
AssertEx.SetEqual(sortedProjects, project1, project2);
Project ps = workspace.CurrentSolution.GetProject(project1);
int startCount = ps.MetadataReferences.Count;
var source2 = @"
using System;
public class X
{
}
";
MetadataReference comp1 = CreateCSharpCompilation(source2).ToMetadataReference();
workspace.OnMetadataReferenceAdded(project1, comp1);
workspace.OnAssemblyNameChanged(project1, "ChangedP1");
Assert.False(ps.CompilationOptions.CheckOverflow);
CompilationOptions co = new CSharp.CSharpCompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary, checkOverflow: true);
workspace.OnCompilationOptionsChanged(project1, co);
ps = workspace.CurrentSolution.GetProject(project1);
Assert.Equal(startCount + 1, ps.MetadataReferences.Count);
Assert.Equal(ps.AssemblyName, "ChangedP1");
Assert.True(ps.CompilationOptions.CheckOverflow);
}
[WorkItem(705220)]
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void Test_WorkspaceOutputFilePathChanges()
{
var workspace = new TestWorkspace();
var solution = workspace.CurrentSolution;
var project = workspace.AddProject("P1");
Project ps = workspace.CurrentSolution.GetProject(project);
Assert.Equal(null, ps.OutputFilePath);
workspace.OnOutputFilePathChanged(project, "NewPath");
ps = workspace.CurrentSolution.GetProject(project);
Assert.Equal("NewPath", ps.OutputFilePath);
}
private CS.CSharpCompilation CreateCSharpCompilation(string sourceText)
{
MetadataReference mscorlib = new MetadataFileReference(typeof(int).Assembly.Location);
var syntaxTree = CS.SyntaxFactory.ParseSyntaxTree(sourceText);
return (CS.CSharpCompilation)CS.CSharpCompilation.Create("foo.exe").AddReferences(mscorlib).AddSyntaxTrees(syntaxTree);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册