static (ImmutableArray <IDependency> TopLevelDependencies, Dictionary <string, IDependency> topLevelDependencyByPath, bool hasVisibleUnresolvedDependency) Scan(ImmutableDictionary <string, IDependency> dependenciesWorld, ITargetFramework targetFramework) { // TODO use ToImmutableAndFree? ImmutableArray <IDependency> .Builder topLevelDependencies = ImmutableArray.CreateBuilder <IDependency>(); bool hasVisibleUnresolvedDependency = false; var topLevelDependencyByPath = new Dictionary <string, IDependency>(StringComparer.OrdinalIgnoreCase); foreach ((string id, IDependency dependency) in dependenciesWorld) { System.Diagnostics.Debug.Assert( string.Equals(id, dependency.Id), "dependenciesWorld dictionary entry keys must match their value's ids."); if (!dependency.Resolved && dependency.Visible) { hasVisibleUnresolvedDependency = true; } if (dependency.TopLevel) { topLevelDependencies.Add(dependency); if (!string.IsNullOrEmpty(dependency.Path)) { topLevelDependencyByPath.Add( Dependency.GetID(targetFramework, dependency.ProviderType, dependency.Path), dependency); } } } return(topLevelDependencies.ToImmutable(), topLevelDependencyByPath, hasVisibleUnresolvedDependency); }
public void GetID_ThrowsForInvalidArguments() { Assert.Throws <ArgumentNullException>(() => Dependency.GetID(null, "providerType", "modelId")); Assert.Throws <ArgumentNullException>(() => Dependency.GetID(TargetFramework.Any, null, "modelId")); Assert.Throws <ArgumentNullException>(() => Dependency.GetID(TargetFramework.Any, "providerType", null)); Assert.Throws <ArgumentException>(() => Dependency.GetID(TargetFramework.Any, "", "modelId")); Assert.Throws <ArgumentException>(() => Dependency.GetID(TargetFramework.Any, "providerType", "")); }
public void FromChanges_NoChangesAfterBeforeRemoveFilterDeclinedChange() { const string projectPath = @"c:\somefolder\someproject\a.csproj"; var targetFramework = new TargetFramework("tfm1"); var dependencyTop1 = new TestDependency { ProviderType = "Xxx", Id = Dependency.GetID(targetFramework, "Xxx", "topdependency1"), Name = "TopDependency1", Caption = "TopDependency1", SchemaItemType = "Xxx", Resolved = true, TopLevel = true }; var dependencyChild1 = new TestDependency { ProviderType = "Xxx", Id = Dependency.GetID(targetFramework, "Xxx", "childdependency1"), Name = "ChildDependency1", Caption = "ChildDependency1", SchemaItemType = "Xxx", Resolved = true, TopLevel = false }; var catalogs = IProjectCatalogSnapshotFactory.Create(); var previousSnapshot = new TargetedDependenciesSnapshot( projectPath, targetFramework, catalogs, new IDependency[] { dependencyTop1, dependencyChild1 }.ToImmutableDictionary(d => d.Id).WithComparers(StringComparer.OrdinalIgnoreCase)); var changes = new DependenciesChangesBuilder(); changes.Removed(dependencyTop1.ProviderType, dependencyTop1.Id); var snapshotFilter = new TestDependenciesSnapshotFilter(); var snapshot = TargetedDependenciesSnapshot.FromChanges( projectPath, previousSnapshot, changes.TryBuildChanges() !, catalogs, ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter), new Dictionary <string, IProjectDependenciesSubTreeProvider>(), null); Assert.Same(previousSnapshot, snapshot); Assert.True(snapshotFilter.Completed); }
public void FromChanges_NoChangesAfterBeforeRemoveFilterDeclinedChange() { const string projectPath = @"c:\somefolder\someproject\a.csproj"; var targetFramework = new TargetFramework("tfm1"); var dependencyTop1 = new TestDependency { ProviderType = "Xxx", Id = Dependency.GetID(targetFramework, "Xxx", "topdependency1"), Name = "TopDependency1", Caption = "TopDependency1", SchemaItemType = "Xxx", Resolved = true, TopLevel = true }; var dependencyChild1 = new TestDependency { ProviderType = "Xxx", Id = Dependency.GetID(targetFramework, "Xxx", "childdependency1"), Name = "ChildDependency1", Caption = "ChildDependency1", SchemaItemType = "Xxx", Resolved = true, TopLevel = false }; var catalogs = IProjectCatalogSnapshotFactory.Create(); var previousSnapshot = ITargetedDependenciesSnapshotFactory.Implement( projectPath: projectPath, targetFramework: targetFramework, catalogs: catalogs, dependenciesWorld: new [] { dependencyTop1, dependencyChild1 }, topLevelDependencies: new [] { dependencyTop1 }); var changes = new DependenciesChangesBuilder(); changes.Removed(dependencyTop1.ProviderType, dependencyTop1.Id); var snapshotFilter = new TestDependenciesSnapshotFilter(); var snapshot = TargetedDependenciesSnapshot.FromChanges( projectPath, previousSnapshot, changes.Build(), catalogs, ImmutableArray.Create <IDependenciesSnapshotFilter>(snapshotFilter), new Dictionary <string, IProjectDependenciesSubTreeProvider>(), null); Assert.Same(previousSnapshot, snapshot); }
private void ConstructTopLevelDependenciesByPathMap() { foreach (var topLevelDependency in TopLevelDependencies) { if (!string.IsNullOrEmpty(topLevelDependency.Path)) { _topLevelDependenciesByPathMap.Add( Dependency.GetID(TargetFramework, topLevelDependency.ProviderType, topLevelDependency.Path), topLevelDependency); } } }
public void BeforeAddOrUpdate_WhenSdk_ShouldFindMatchingPackageAndSetProperties() { var dependencyIDs = ImmutableArray.Create("id1", "id2"); var targetFramework = new TargetFramework("tfm"); const string sdkName = "sdkName"; var sdkDependency = new TestDependency { Id = "dependency1Id", Name = sdkName, TopLevel = true, Resolved = false, Flags = DependencyTreeFlags.SdkDependency }; var packageDependency = new TestDependency { Id = Dependency.GetID(targetFramework, PackageRuleHandler.ProviderTypeString, sdkName), Resolved = true, DependencyIDs = dependencyIDs, Flags = DependencyTreeFlags.NuGetPackageDependency }; var worldBuilder = new IDependency[] { sdkDependency, packageDependency }.ToImmutableDictionary(d => d.Id).ToBuilder(); var context = new AddDependencyContext(worldBuilder); var filter = new SdkAndPackagesDependenciesSnapshotFilter(); filter.BeforeAddOrUpdate( targetFramework, sdkDependency, null, null, context); var acceptedDependency = context.GetResult(filter); // Dependency should be accepted, but converted to resolved state Assert.NotNull(acceptedDependency); Assert.NotSame(sdkDependency, acceptedDependency); acceptedDependency.AssertEqualTo( sdkDependency.ToResolved( schemaName: ResolvedSdkReference.SchemaName, dependencyIDs: dependencyIDs)); // No changes other than the filtered dependency Assert.False(context.Changed); }
public void BeforeAddOrUpdate_WhenPackage_ShouldFindMatchingSdkAndSetProperties() { var dependencyIDs = ImmutableArray.Create("id1", "id2"); var targetFramework = new TargetFramework("tfm"); const string packageName = "packageName"; var sdkDependency = new TestDependency { Id = Dependency.GetID(targetFramework, SdkRuleHandler.ProviderTypeString, packageName), TopLevel = false, Resolved = true, Flags = DependencyTreeFlags.NuGetPackageDependency.Union(DependencyTreeFlags.Unresolved) // to see if unresolved is fixed }; var packageDependency = new TestDependency { Id = "packageId", Name = packageName, Flags = DependencyTreeFlags.NuGetPackageDependency, TopLevel = true, Resolved = true, DependencyIDs = dependencyIDs }; var worldBuilder = new IDependency[] { packageDependency, sdkDependency }.ToImmutableDictionary(d => d.Id).ToBuilder(); var context = new AddDependencyContext(worldBuilder); var filter = new SdkAndPackagesDependenciesSnapshotFilter(); filter.BeforeAddOrUpdate( targetFramework, packageDependency, null, null, context); // Accepts unchanged dependency Assert.Same(packageDependency, context.GetResult(filter)); // Other changes made Assert.True(context.Changed); Assert.True(context.TryGetDependency(sdkDependency.Id, out IDependency sdkDependencyAfter)); sdkDependencyAfter.AssertEqualTo( sdkDependency.ToResolved( schemaName: ResolvedSdkReference.SchemaName, dependencyIDs: dependencyIDs)); }
public void BeforeRemove_WhenPackageRemoving_ShouldCleanupSdk() { const string packageName = "packageName"; var targetFramework = new TargetFramework("tfm"); var sdkDependency = new TestDependency { Id = Dependency.GetID(targetFramework, SdkRuleHandler.ProviderTypeString, packageName), TopLevel = false, Resolved = true, Flags = DependencyTreeFlags.SdkDependency.Union(DependencyTreeFlags.Resolved) }; var packageDependency = new TestDependency { Id = "packageId", Name = packageName, Flags = DependencyTreeFlags.NuGetPackageDependency, TopLevel = true, Resolved = true }; var worldBuilder = new IDependency[] { packageDependency, sdkDependency }.ToImmutableDictionary(d => d.Id).ToBuilder(); var context = new RemoveDependencyContext(worldBuilder); var filter = new SdkAndPackagesDependenciesSnapshotFilter(); filter.BeforeRemove( targetFramework: targetFramework, dependency: packageDependency, context); // Accepts removal Assert.True(context.GetResult(filter)); // Makes other changes too Assert.True(context.Changed); Assert.True(worldBuilder.TryGetValue(packageDependency.Id, out var afterPackageDependency)); Assert.Same(packageDependency, afterPackageDependency); Assert.True(worldBuilder.TryGetValue(sdkDependency.Id, out var afterSdkDependency)); afterSdkDependency.AssertEqualTo( afterSdkDependency.ToUnresolved( SdkReference.SchemaName, dependencyIDs: ImmutableArray <string> .Empty)); }
// Internal, for test use -- normal code should use the factory methods internal TargetedDependenciesSnapshot( string projectPath, ITargetFramework targetFramework, IProjectCatalogSnapshot catalogs, ImmutableDictionary <string, IDependency> dependenciesWorld) { Requires.NotNullOrEmpty(projectPath, nameof(projectPath)); Requires.NotNull(targetFramework, nameof(targetFramework)); // catalogs can be null Requires.NotNull(dependenciesWorld, nameof(dependenciesWorld)); ProjectPath = projectPath; TargetFramework = targetFramework; Catalogs = catalogs; DependenciesWorld = dependenciesWorld; bool hasUnresolvedDependency = false; ImmutableHashSet <IDependency> .Builder topLevelDependencies = ImmutableHashSet.CreateBuilder <IDependency>(); foreach ((string id, IDependency dependency) in dependenciesWorld) { System.Diagnostics.Debug.Assert( string.Equals(id, dependency.Id), "dependenciesWorld dictionary entry keys must match their value's ids."); if (!dependency.Resolved) { hasUnresolvedDependency = true; } if (dependency.TopLevel) { bool added = topLevelDependencies.Add(dependency); System.Diagnostics.Debug.Assert(added, "Duplicate top level dependency found."); if (!string.IsNullOrEmpty(dependency.Path)) { _topLevelDependenciesByPathMap.Add( Dependency.GetID(TargetFramework, dependency.ProviderType, dependency.Path), dependency); } } } HasUnresolvedDependency = hasUnresolvedDependency; TopLevelDependencies = topLevelDependencies.ToImmutable(); }
public void BeforeAddOrUpdate_WhenSdkAndPackageUnresolved_ShouldDoNothing() { var targetFramework = new TargetFramework("tfm"); const string sdkName = "sdkName"; var sdkDependency = new TestDependency { Id = "dependency1", Name = sdkName, TopLevel = false, Resolved = true, Flags = DependencyTreeFlags.SdkDependency }; var packageDependency = new TestDependency { Id = Dependency.GetID(targetFramework, PackageRuleHandler.ProviderTypeString, sdkName), Resolved = false, Flags = DependencyTreeFlags.NuGetPackageDependency }; var worldBuilder = new IDependency[] { sdkDependency, packageDependency }.ToImmutableDictionary(d => d.Id).ToBuilder(); var context = new AddDependencyContext(worldBuilder); var filter = new SdkAndPackagesDependenciesSnapshotFilter(); filter.BeforeAddOrUpdate( targetFramework, sdkDependency, null, null, context); // Accepts unchanged dependency Assert.Same(sdkDependency, context.GetResult(filter)); // No other changes made Assert.False(context.Changed); }
public void GetID_CreatesCorrectString(string modelId, string expected) { Assert.Equal(expected, Dependency.GetID(TargetFramework.Any, "providerType", modelId)); }
/// <summary> /// Returns id having full path instead of OriginalItemSpec /// </summary> public static string GetTopLevelId(this IDependency self) { return(string.IsNullOrEmpty(self.Path) ? self.Id : Dependency.GetID(self.TargetFramework, self.ProviderType, self.Path)); }
/// <summary> /// Applies changes to <paramref name="previousSnapshot"/> and produces a new snapshot if required. /// If no changes are made, <paramref name="previousSnapshot"/> is returned unmodified. /// </summary> /// <returns>An updated snapshot, or <paramref name="previousSnapshot"/> if no changes occured.</returns> public static TargetedDependenciesSnapshot FromChanges( string projectPath, TargetedDependenciesSnapshot previousSnapshot, IDependenciesChanges changes, IProjectCatalogSnapshot?catalogs, ImmutableArray <IDependenciesSnapshotFilter> snapshotFilters, IReadOnlyDictionary <string, IProjectDependenciesSubTreeProvider> subTreeProviderByProviderType, IImmutableSet <string>?projectItemSpecs) { Requires.NotNullOrWhiteSpace(projectPath, nameof(projectPath)); Requires.NotNull(previousSnapshot, nameof(previousSnapshot)); Requires.NotNull(changes, nameof(changes)); Requires.Argument(!snapshotFilters.IsDefault, nameof(snapshotFilters), "Cannot be default."); Requires.NotNull(subTreeProviderByProviderType, nameof(subTreeProviderByProviderType)); bool anyChanges = false; ITargetFramework targetFramework = previousSnapshot.TargetFramework; var worldBuilder = previousSnapshot.DependenciesWorld.ToBuilder(); if (changes.RemovedNodes.Count != 0) { var context = new RemoveDependencyContext(worldBuilder); foreach (IDependencyModel removed in changes.RemovedNodes) { Remove(context, removed); } } if (changes.AddedNodes.Count != 0) { var context = new AddDependencyContext(worldBuilder); foreach (IDependencyModel added in changes.AddedNodes) { Add(context, added); } } // Also factor in any changes to path/framework/catalogs anyChanges = anyChanges || !StringComparers.Paths.Equals(projectPath, previousSnapshot.ProjectPath) || !targetFramework.Equals(previousSnapshot.TargetFramework) || !Equals(catalogs, previousSnapshot.Catalogs); if (anyChanges) { return(new TargetedDependenciesSnapshot( projectPath, targetFramework, catalogs, worldBuilder.ToImmutable())); } return(previousSnapshot); void Remove(RemoveDependencyContext context, IDependencyModel dependencyModel) { string dependencyId = Dependency.GetID( targetFramework, dependencyModel.ProviderType, dependencyModel.Id); if (!context.TryGetDependency(dependencyId, out IDependency dependency)) { return; } context.Reset(); foreach (IDependenciesSnapshotFilter filter in snapshotFilters) { filter.BeforeRemove( targetFramework, dependency, context); anyChanges |= context.Changed; if (!context.GetResult(filter)) { // TODO breaking here denies later filters the opportunity to modify builders return; } } worldBuilder.Remove(dependencyId); anyChanges = true; } void Add(AddDependencyContext context, IDependencyModel dependencyModel) { // Create the unfiltered dependency IDependency?dependency = new Dependency(dependencyModel, targetFramework, projectPath); context.Reset(); foreach (IDependenciesSnapshotFilter filter in snapshotFilters) { filter.BeforeAddOrUpdate( targetFramework, dependency, subTreeProviderByProviderType, projectItemSpecs, context); dependency = context.GetResult(filter); if (dependency == null) { break; } } if (dependency != null) { // A dependency was accepted worldBuilder.Remove(dependency.Id); worldBuilder.Add(dependency.Id, dependency); anyChanges = true; } else { // Even though the dependency was rejected, it's possible that filters made // changes to other dependencies. anyChanges |= context.Changed; } } }
private bool MergeChanges( IDependenciesChanges changes, IEnumerable <IDependenciesSnapshotFilter> snapshotFilters) { var worldBuilder = ImmutableDictionary.CreateBuilder <string, IDependency>( StringComparer.OrdinalIgnoreCase); worldBuilder.AddRange(DependenciesWorld); var topLevelBuilder = ImmutableHashSet.CreateBuilder <IDependency>(); topLevelBuilder.AddRange(TopLevelDependencies); var anyChanges = false; foreach (var removed in changes.RemovedNodes) { var targetedId = Dependency.GetID(TargetFramework, removed.ProviderType, removed.Id); if (!worldBuilder.TryGetValue(targetedId, out IDependency dependency)) { continue; } if (snapshotFilters != null) { foreach (var filter in snapshotFilters) { dependency = filter.BeforeRemove( ProjectPath, TargetFramework, dependency, worldBuilder, topLevelBuilder, out bool filterAnyChanges); anyChanges |= filterAnyChanges; if (dependency == null) { break; } } } if (dependency == null) { continue; } anyChanges = true; worldBuilder.Remove(targetedId); topLevelBuilder.Remove(dependency); } foreach (var added in changes.AddedNodes) { IDependency newDependency = new Dependency(added, TargetFramework); if (snapshotFilters != null) { foreach (var filter in snapshotFilters) { newDependency = filter.BeforeAdd( ProjectPath, TargetFramework, newDependency, worldBuilder, topLevelBuilder, out bool filterAnyChanges); anyChanges |= filterAnyChanges; if (newDependency == null) { break; } } } if (newDependency == null) { continue; } anyChanges = true; worldBuilder.Remove(newDependency.Id); worldBuilder.Add(newDependency.Id, newDependency); if (newDependency.TopLevel) { topLevelBuilder.Remove(newDependency); topLevelBuilder.Add(newDependency); } } DependenciesWorld = worldBuilder.ToImmutable(); TopLevelDependencies = topLevelBuilder.ToImmutable(); ConstructTopLevelDependenciesByPathMap(); return(anyChanges); }
private bool MergeChanges( IDependenciesChanges changes, IEnumerable <IDependenciesSnapshotFilter> snapshotFilters, IEnumerable <IProjectDependenciesSubTreeProvider> subTreeProviders, HashSet <string> projectItemSpecs) { var worldBuilder = DependenciesWorld.ToBuilder(); var topLevelBuilder = TopLevelDependencies.ToBuilder(); var anyChanges = false; foreach (var removed in changes.RemovedNodes) { var targetedId = Dependency.GetID(TargetFramework, removed.ProviderType, removed.Id); if (!worldBuilder.TryGetValue(targetedId, out IDependency dependency)) { continue; } if (snapshotFilters != null) { foreach (var filter in snapshotFilters) { dependency = filter.BeforeRemove( ProjectPath, TargetFramework, dependency, worldBuilder, topLevelBuilder, out bool filterAnyChanges); anyChanges |= filterAnyChanges; if (dependency == null) { break; } } } if (dependency == null) { continue; } anyChanges = true; worldBuilder.Remove(targetedId); topLevelBuilder.Remove(dependency); } var subTreeProvidersMap = GetSubTreeProviderMap(subTreeProviders); foreach (var added in changes.AddedNodes) { IDependency newDependency = new Dependency(added, TargetFramework, ProjectPath); if (snapshotFilters != null) { foreach (var filter in snapshotFilters) { newDependency = filter.BeforeAdd( ProjectPath, TargetFramework, newDependency, worldBuilder, topLevelBuilder, subTreeProvidersMap, projectItemSpecs, out bool filterAnyChanges); anyChanges |= filterAnyChanges; if (newDependency == null) { break; } } } if (newDependency == null) { continue; } anyChanges = true; worldBuilder.Remove(newDependency.Id); worldBuilder.Add(newDependency.Id, newDependency); if (newDependency.TopLevel) { topLevelBuilder.Remove(newDependency); topLevelBuilder.Add(newDependency); } } DependenciesWorld = worldBuilder.ToImmutable(); TopLevelDependencies = topLevelBuilder.ToImmutable(); ConstructTopLevelDependenciesByPathMap(); return(anyChanges); }
/// <summary> /// Applies changes to <paramref name="previousSnapshot"/> and produces a new snapshot if required. /// If no changes are made, <paramref name="previousSnapshot"/> is returned unmodified. /// </summary> /// <returns>An updated snapshot, or <paramref name="previousSnapshot"/> if no changes occured.</returns> public static ITargetedDependenciesSnapshot FromChanges( string projectPath, ITargetedDependenciesSnapshot previousSnapshot, IDependenciesChanges changes, IProjectCatalogSnapshot catalogs, IReadOnlyList <IDependenciesSnapshotFilter> snapshotFilters, IReadOnlyDictionary <string, IProjectDependenciesSubTreeProvider> subTreeProviderByProviderType, IImmutableSet <string> projectItemSpecs) { Requires.NotNullOrWhiteSpace(projectPath, nameof(projectPath)); Requires.NotNull(previousSnapshot, nameof(previousSnapshot)); // catalogs can be null Requires.NotNull(changes, nameof(changes)); Requires.NotNull(snapshotFilters, nameof(snapshotFilters)); Requires.NotNull(subTreeProviderByProviderType, nameof(subTreeProviderByProviderType)); // projectItemSpecs can be null bool anyChanges = false; ITargetFramework targetFramework = previousSnapshot.TargetFramework; var worldBuilder = previousSnapshot.DependenciesWorld.ToBuilder(); foreach (IDependencyModel removedNode in changes.RemovedNodes) { string targetedId = Dependency.GetID(targetFramework, removedNode.ProviderType, removedNode.Id); if (!worldBuilder.TryGetValue(targetedId, out IDependency dependency)) { continue; } bool canRemove = true; if (snapshotFilters != null) { foreach (IDependenciesSnapshotFilter filter in snapshotFilters) { canRemove = filter.BeforeRemove( projectPath, targetFramework, dependency, worldBuilder, out bool filterAnyChanges); anyChanges |= filterAnyChanges; if (!canRemove) { // TODO breaking here denies later filters the opportunity to modify builders break; } } } if (canRemove) { anyChanges = true; worldBuilder.Remove(targetedId); } } foreach (IDependencyModel added in changes.AddedNodes) { IDependency newDependency = new Dependency(added, targetFramework, projectPath); if (snapshotFilters != null) { foreach (IDependenciesSnapshotFilter filter in snapshotFilters) { newDependency = filter.BeforeAdd( projectPath, targetFramework, newDependency, worldBuilder, subTreeProviderByProviderType, projectItemSpecs, out bool filterAnyChanges); anyChanges |= filterAnyChanges; if (newDependency == null) { break; } } } if (newDependency == null) { continue; } anyChanges = true; worldBuilder.Remove(newDependency.Id); worldBuilder.Add(newDependency.Id, newDependency); } // Also factor in any changes to path/framework/catalogs anyChanges = anyChanges || !StringComparers.Paths.Equals(projectPath, previousSnapshot.ProjectPath) || !targetFramework.Equals(previousSnapshot.TargetFramework) || !Equals(catalogs, previousSnapshot.Catalogs); if (anyChanges) { return(new TargetedDependenciesSnapshot( projectPath, targetFramework, catalogs, worldBuilder.ToImmutable())); } return(previousSnapshot); }