Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ internal class VsReferenceItem(ReferenceItem referenceItem) : IVsReferenceItem2
{
public string Name => referenceItem.Name;

public IReadOnlyDictionary<string, string>? Metadata => referenceItem.Properties;
public IReadOnlyDictionary<string, string>? Metadata => referenceItem.Metadata;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks.Dataflow;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.IO;
using Microsoft.VisualStudio.ProjectSystem.Telemetry;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;

Expand Down Expand Up @@ -58,6 +59,7 @@ internal partial class PackageRestoreDataSource : ChainedProjectValueDataSourceB
private readonly IManagedProjectDiagnosticOutputService _logger;
private readonly INuGetRestoreService _nuGetRestoreService;
private Hash? _lastHash;
private ProjectRestoreInfo? _lastRestoreInfo;
private bool _enabled;
private bool _wasSourceBlockContinuationSet;

Expand Down Expand Up @@ -109,10 +111,7 @@ internal async Task<IEnumerable<IProjectVersionedValue<RestoreData>>> RestoreAsy

RestoreData restoreData = CreateRestoreData(e.Value.RestoreInfo, succeeded);

return new[]
{
new ProjectVersionedValue<RestoreData>(restoreData, e.DataSourceVersions)
};
return [new ProjectVersionedValue<RestoreData>(restoreData, e.DataSourceVersions)];
}

private async Task<bool> RestoreCoreAsync(PackageRestoreUnconfiguredInput value)
Expand All @@ -125,22 +124,43 @@ private async Task<bool> RestoreCoreAsync(PackageRestoreUnconfiguredInput value)

// Restore service always does work regardless of whether the value we pass
// them to actually contains changes, only nominate if there are any.
Hash hash = RestoreHasher.CalculateHash(restoreInfo);
Hash hash = CalculateHash();

if (await _cycleDetector.IsCycleDetectedAsync(hash, value.ActiveConfiguration, token))
{
_lastHash = hash;
_lastRestoreInfo = restoreInfo;
return false;
}

if (_lastHash?.Equals(hash) == true)
{
await _nuGetRestoreService.UpdateWithoutNominationAsync(value.ConfiguredInputs);
_lastRestoreInfo = restoreInfo;
return true;
}

_lastHash = hash;

if (DotNetProjectSystemEventSource.Instance.IsEnabled())
{
string changes;
if (_lastRestoreInfo is null)
{
changes = "Initial restore.";
}
else
{
RestoreStateComparisonBuilder builder = new();
_lastRestoreInfo.DescribeChanges(builder, restoreInfo);
changes = builder.ToString();
}

DotNetProjectSystemEventSource.Instance.NominateForRestore(projectFilePath: _project.FullPath, changes: changes);
}

_lastRestoreInfo = restoreInfo;

JoinableTask<bool> joinableTask = JoinableFactory.RunAsync(() =>
{
return NominateForRestoreAsync(restoreInfo, value.ConfiguredInputs, token);
Expand All @@ -152,6 +172,13 @@ private async Task<bool> RestoreCoreAsync(PackageRestoreUnconfiguredInput value)
registerFaultHandler: true);

return await joinableTask;

Hash CalculateHash()
{
using var hasher = new IncrementalHasher();
restoreInfo.AddToHash(hasher);
return hasher.GetHashAndReset();
}
}

private async Task<bool> NominateForRestoreAsync(ProjectRestoreInfo restoreInfo, IReadOnlyCollection<PackageRestoreConfiguredInput> versions, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using Microsoft.VisualStudio.Text;

namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;

/// <summary>
/// A data object at some level within the restore state snapshot, <see cref="ProjectRestoreInfo"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
internal interface IRestoreState<T> where T : class
{
/// <summary>
/// Adds state from this object to <paramref name="hasher"/>.
/// </summary>
/// <param name="hasher"></param>
void AddToHash(IncrementalHasher hasher);

/// <summary>
/// Compares all state between this instance and <paramref name="after"/>, and logs details of any changes.
/// </summary>
void DescribeChanges(RestoreStateComparisonBuilder builder, T after);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using Microsoft.VisualStudio.Text;

namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;

internal static class IncrementalHasherExtensions
{
public static void AppendProperty(this IncrementalHasher hasher, string name, string value)
{
hasher.Append(name);
hasher.Append("|");
hasher.Append(value);
}

public static void AppendArray<T>(this IncrementalHasher hasher, ImmutableArray<T> items) where T : class, IRestoreState<T>
{
foreach (T item in items)
{
item.AddToHash(hasher);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using Microsoft.VisualStudio.Text;

namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;

/// <summary>
/// A complete set of restore data for a project.
/// A complete set of restore data for a project.
/// </summary>
internal class ProjectRestoreInfo
internal sealed class ProjectRestoreInfo(
string msbuildProjectExtensionsPath,
string projectAssetsFilePath,
string originalTargetFrameworks,
ImmutableArray<TargetFrameworkInfo> targetFrameworks,
ImmutableArray<ReferenceItem> toolReferences)
: IRestoreState<ProjectRestoreInfo>
{
// If additional fields/properties are added to this class, please update RestoreHasher
// IMPORTANT: If additional state is added, update AddToHash and DescribeChanges below.

public ProjectRestoreInfo(string msbuildProjectExtensionsPath, string projectAssetsFilePath, string originalTargetFrameworks, ImmutableArray<TargetFrameworkInfo> targetFrameworks, ImmutableArray<ReferenceItem> toolReferences)
{
MSBuildProjectExtensionsPath = msbuildProjectExtensionsPath;
ProjectAssetsFilePath = projectAssetsFilePath;
OriginalTargetFrameworks = originalTargetFrameworks;
TargetFrameworks = targetFrameworks;
ToolReferences = toolReferences;
}
public string MSBuildProjectExtensionsPath { get; } = msbuildProjectExtensionsPath;

public string ProjectAssetsFilePath { get; } = projectAssetsFilePath;

public string OriginalTargetFrameworks { get; } = originalTargetFrameworks;

public string MSBuildProjectExtensionsPath { get; }
public ImmutableArray<TargetFrameworkInfo> TargetFrameworks { get; } = targetFrameworks;

public string ProjectAssetsFilePath { get; }
public ImmutableArray<ReferenceItem> ToolReferences { get; } = toolReferences;

public string OriginalTargetFrameworks { get; }
public void AddToHash(IncrementalHasher hasher)
{
hasher.AppendProperty(nameof(ProjectAssetsFilePath), ProjectAssetsFilePath);
hasher.AppendProperty(nameof(MSBuildProjectExtensionsPath), MSBuildProjectExtensionsPath);
hasher.AppendProperty(nameof(OriginalTargetFrameworks), OriginalTargetFrameworks);

public ImmutableArray<TargetFrameworkInfo> TargetFrameworks { get; }
hasher.AppendArray(TargetFrameworks);
hasher.AppendArray(ToolReferences);
}

public ImmutableArray<ReferenceItem> ToolReferences { get; }
public void DescribeChanges(RestoreStateComparisonBuilder builder, ProjectRestoreInfo after)
{
builder.CompareString(MSBuildProjectExtensionsPath, after.MSBuildProjectExtensionsPath, nameof(MSBuildProjectExtensionsPath));
builder.CompareString(ProjectAssetsFilePath, after.ProjectAssetsFilePath, nameof(ProjectAssetsFilePath));
builder.CompareString(OriginalTargetFrameworks, after.OriginalTargetFrameworks, nameof(OriginalTargetFrameworks));

builder.CompareArray(TargetFrameworks, after.TargetFrameworks, nameof(TargetFrameworks));
builder.CompareArray(ToolReferences, after.ToolReferences, nameof(ToolReferences));
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using System.Diagnostics;
using Microsoft.VisualStudio.Text;

namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;

/// <summary>
/// Represents a single package, tool or project reference.
/// Represents a reference item involved in package restore, with its associated metadata.
/// </summary>
[DebuggerDisplay("Name = {Name}")]
internal class ReferenceItem
internal sealed class ReferenceItem(
string name,
IImmutableDictionary<string, string> metadata)
: IRestoreState<ReferenceItem>
{
// If additional fields/properties are added to this class, please update RestoreHasher
// IMPORTANT: If additional state is added, update AddToHash and DescribeChanges below.

public ReferenceItem(string name, IImmutableDictionary<string, string> properties)
/// <summary>
/// Gets the name (item spec) of the reference.
/// </summary>
public string Name { get; } = name;

/// <summary>
/// Gets the name/value pair metadata associated with the reference.
/// </summary>
public IImmutableDictionary<string, string> Metadata { get; } = metadata;

public void AddToHash(IncrementalHasher hasher)
{
Requires.NotNullOrEmpty(name);
hasher.AppendProperty(nameof(Name), Name);

Name = name;
Properties = properties;
foreach ((string key, string value) in Metadata)
{
hasher.AppendProperty(key, value);
}
}

public string Name { get; }
public void DescribeChanges(RestoreStateComparisonBuilder builder, ReferenceItem after)
{
builder.PushScope(Name);

builder.CompareString(Name, after.Name, name: "%(Identity)");

builder.CompareDictionary(Metadata, after.Metadata);

public IImmutableDictionary<string, string> Properties { get; }
builder.PopScope();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,44 @@ namespace Microsoft.VisualStudio.ProjectSystem.PackageRestore;
/// </summary>
internal static class RestoreBuilder
{
public static readonly ImmutableArray<TargetFrameworkInfo> EmptyTargetFrameworks = [];
public static readonly ImmutableArray<ReferenceItem> EmptyReferences = [];

/// <summary>
/// Converts an immutable dictionary of rule snapshot data into an <see cref="ProjectRestoreInfo"/> instance.
/// </summary>
public static ProjectRestoreInfo ToProjectRestoreInfo(IImmutableDictionary<string, IProjectRuleSnapshot> update)
{
IImmutableDictionary<string, string> properties = update.GetSnapshotOrEmpty(NuGetRestore.SchemaName).Properties;
IProjectRuleSnapshot frameworkReferences = update.GetSnapshotOrEmpty(CollectedFrameworkReference.SchemaName);
IProjectRuleSnapshot packageDownloads = update.GetSnapshotOrEmpty(CollectedPackageDownload.SchemaName);
IProjectRuleSnapshot projectReferences = update.GetSnapshotOrEmpty(EvaluatedProjectReference.SchemaName);
IProjectRuleSnapshot packageReferences = update.GetSnapshotOrEmpty(CollectedPackageReference.SchemaName);
IProjectRuleSnapshot packageVersions = update.GetSnapshotOrEmpty(CollectedPackageVersion.SchemaName);
IProjectRuleSnapshot nuGetAuditSuppress = update.GetSnapshotOrEmpty(CollectedNuGetAuditSuppressions.SchemaName);
IProjectRuleSnapshot prunePackageReferences = update.GetSnapshotOrEmpty(CollectedPrunePackageReference.SchemaName);
IProjectRuleSnapshot toolReferences = update.GetSnapshotOrEmpty(DotNetCliToolReference.SchemaName);

// For certain project types such as UWP, "TargetFrameworkMoniker" != the moniker that restore uses
string targetMoniker = properties.GetPropertyOrEmpty(NuGetRestore.NuGetTargetMonikerProperty);
if (targetMoniker.Length == 0)
targetMoniker = properties.GetPropertyOrEmpty(NuGetRestore.TargetFrameworkMonikerProperty);

TargetFrameworkInfo frameworkInfo = new TargetFrameworkInfo(
TargetFrameworkInfo frameworkInfo = new(
targetMoniker,
ToReferenceItems(frameworkReferences.Items),
ToReferenceItems(packageDownloads.Items),
ToReferenceItems(projectReferences.Items),
ToReferenceItems(packageReferences.Items),
ToReferenceItems(packageVersions.Items),
ToReferenceItems(nuGetAuditSuppress.Items),
ToReferenceItems(prunePackageReferences.Items),
properties);
frameworkReferences: GetReferenceItems(CollectedFrameworkReference.SchemaName),
packageDownloads: GetReferenceItems(CollectedPackageDownload.SchemaName),
projectReferences: GetReferenceItems(EvaluatedProjectReference.SchemaName),
packageReferences: GetReferenceItems(CollectedPackageReference.SchemaName),
centralPackageVersions: GetReferenceItems(CollectedPackageVersion.SchemaName),
nuGetAuditSuppress: GetReferenceItems(CollectedNuGetAuditSuppressions.SchemaName),
prunePackageReferences: GetReferenceItems(CollectedPrunePackageReference.SchemaName),
properties: properties);

return new ProjectRestoreInfo(
properties.GetPropertyOrEmpty(NuGetRestore.MSBuildProjectExtensionsPathProperty),
properties.GetPropertyOrEmpty(NuGetRestore.ProjectAssetsFileProperty),
properties.GetPropertyOrEmpty(NuGetRestore.TargetFrameworksProperty),
EmptyTargetFrameworks.Add(frameworkInfo),
ToReferenceItems(toolReferences.Items));
msbuildProjectExtensionsPath: properties.GetPropertyOrEmpty(NuGetRestore.MSBuildProjectExtensionsPathProperty),
projectAssetsFilePath: properties.GetPropertyOrEmpty(NuGetRestore.ProjectAssetsFileProperty),
originalTargetFrameworks: properties.GetPropertyOrEmpty(NuGetRestore.TargetFrameworksProperty),
targetFrameworks: [frameworkInfo],
toolReferences: GetReferenceItems(DotNetCliToolReference.SchemaName));

static ImmutableArray<ReferenceItem> ToReferenceItems(IImmutableDictionary<string, IImmutableDictionary<string, string>> items)
ImmutableArray<ReferenceItem> GetReferenceItems(string schemaName)
{
return items.ToImmutableArray(static (name, metadata) => new ReferenceItem(name, metadata));
if (!update.TryGetValue(schemaName, out IProjectRuleSnapshot? result))
{
return [];
}

return result.Items.ToImmutableArray(static (name, metadata) => new ReferenceItem(name, metadata));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public override bool Equals(ReferenceItem? x, ReferenceItem? y)
if (!StringComparers.ItemNames.Equals(x.Name, y.Name))
return false;

if (!PropertiesAreEqual(x.Properties, y.Properties))
if (!PropertiesAreEqual(x.Metadata, y.Metadata))
return false;

return true;
Expand Down
Loading