Skip to content

Commit

Permalink
Get PackageDefinitions from Assets fileAdd output PackageDependencies…
Browse files Browse the repository at this point in the history
…DesignTime to cache file
  • Loading branch information
ocalles committed Oct 7, 2022
1 parent 9f28bbb commit 8a2df1e
Show file tree
Hide file tree
Showing 2 changed files with 297 additions and 3 deletions.
297 changes: 295 additions & 2 deletions src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NuGet.Common;
using NuGet.Frameworks;
using NuGet.ProjectModel;
using NuGet.Versioning;

Expand Down Expand Up @@ -162,6 +163,12 @@ public sealed class ResolvePackageAssets : TaskBase
/// </summary>
public bool DesignTimeBuild { get; set; }

/// <summary>
/// Eg: "Microsoft.NETCore.App;NETStandard.Library"
/// </summary>
[Required]
public string DefaultImplicitPackages { get; set; }

#endregion

#region Output Items
Expand Down Expand Up @@ -238,6 +245,17 @@ public sealed class ResolvePackageAssets : TaskBase
[Output]
public ITaskItem[] PackageDependencies { get; private set; }

/// <summary>
/// Filters and projects items produced by <see cref="ResolvePackageDependencies"/> for consumption by
/// the dependencies tree, via design-time builds.
/// </summary>
/// <remarks>
/// Changes to the implementation of output must be coordinated with <c>PackageRuleHandler</c>
/// in the dotnet/project-system repo.
/// </remarks>
[Output]
public ITaskItem[] PackageDependenciesDesignTime { get; private set; }

/// <summary>
/// List of symbol files (.pdb) related to NuGet packages.
/// </summary>
Expand Down Expand Up @@ -311,7 +329,7 @@ public sealed class ResolvePackageAssets : TaskBase
////////////////////////////////////////////////////////////////////////////////////////////////////

private const int CacheFormatSignature = ('P' << 0) | ('K' << 8) | ('G' << 16) | ('A' << 24);
private const int CacheFormatVersion = 11;
private const int CacheFormatVersion = 12;
private static readonly Encoding TextEncoding = Encoding.UTF8;
private const int SettingsHashLength = 256 / 8;
private HashAlgorithm CreateSettingsHash() => SHA256.Create();
Expand Down Expand Up @@ -343,6 +361,7 @@ private void ReadItemGroups()
FrameworkReferences = reader.ReadItemGroup();
NativeLibraries = reader.ReadItemGroup();
PackageDependencies = reader.ReadItemGroup();
PackageDependenciesDesignTime = reader.ReadItemGroup();
PackageFolders = reader.ReadItemGroup();
ReferenceDocumentationFiles = reader.ReadItemGroup();
ResourceAssemblies = reader.ReadItemGroup();
Expand Down Expand Up @@ -660,7 +679,10 @@ private ITaskItem ReadItem()
internal sealed class CacheWriter : IDisposable
{
private const int InitialStringTableCapacity = 32;
private const string ResolvedMetadata = "Resolved";

private HashSet<string> _projectFileDependencies;
private Dictionary<string, string> _targetNameToAliasMap;
private ResolvePackageAssets _task;
private BinaryWriter _writer;
private LockFile _lockFile;
Expand Down Expand Up @@ -691,7 +713,8 @@ public CacheWriter(ResolvePackageAssets task)
_task = task;
_lockFile = new LockFileCache(task).GetLockFile(task.ProjectAssetsFile);
_packageResolver = NuGetPackageResolver.CreateResolver(_lockFile);

_targetNameToAliasMap = CreateTargetNameToAlisMap();
ReadProjectFileDependencies(string.IsNullOrEmpty(_task.TargetFramework) || !_targetNameToAliasMap.ContainsKey(_task.TargetFramework) ? null : _targetNameToAliasMap[_task.TargetFramework]);

// If we are doing a design-time build, we do not want to fail the build if we can't find the
// target framework and/or runtime identifier in the assets file. This is because the design-time
Expand Down Expand Up @@ -733,6 +756,24 @@ public CacheWriter(ResolvePackageAssets task)
{
ComputePackageExclusions();
}

Dictionary<string, string> CreateTargetNameToAlisMap() => _lockFile.Targets.ToDictionary(t => t.Name, t =>
{
var alias = _lockFile.GetLockFileTargetAlias(t);
if (string.IsNullOrEmpty(t.RuntimeIdentifier))
{
return alias;
}
else
{
return alias + "/" + t.RuntimeIdentifier;
}
});

void ReadProjectFileDependencies(string frameworkAlias)
{
_projectFileDependencies = _lockFile.GetProjectFileDependencySet(frameworkAlias);
}
}

public void WriteToCacheFile()
Expand Down Expand Up @@ -814,6 +855,7 @@ private void WriteItemGroups()
WriteItemGroup(WriteFrameworkReferences);
WriteItemGroup(WriteNativeLibraries);
WriteItemGroup(WritePackageDependencies);
WriteItemGroup(WritePackageDependenciesDesignTime);
WriteItemGroup(WritePackageFolders);
WriteItemGroup(WriteReferenceDocumentationFiles);
WriteItemGroup(WriteResourceAssemblies);
Expand Down Expand Up @@ -1408,6 +1450,257 @@ private void WritePackageDependencies()
}
}

// This implementation was taken from PreprocessPackageDependenciesDesignTime.ExecuteCore
// with some minor modifications to use WriteItem instead of saving it into an array.
private void WritePackageDependenciesDesignTime()
{
List<ITaskItem> _packageDefinitions = new List<ITaskItem>();
List<ITaskItem> _packageDependencies = new List<ITaskItem>();
GetPackageDefinitions();
GetPackageDependencies();

var implicitPackageReferences = GetImplicitPackageReferences(_task.DefaultImplicitPackages);

// We have two types of data:
//
// 1) "PackageDependencies" which place a package in a given target/hierarchy
// 2) "PackageDefinitions" which provide general metadata about a package
//
// First, we scan PackageDependencies to build the set of packages in our target.

var allowItemSpecs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

foreach (var dependency in _packageDependencies)
{
if (dependency.HasMetadataValue(MetadataKeys.ParentPackage))
{
// ignore non-top-level packages (those with ParentPackage)
continue;
}

var target = dependency.GetMetadata(MetadataKeys.ParentTarget);

if (!StringComparer.OrdinalIgnoreCase.Equals(target, _task.TargetFramework))
{
// skip dependencies for other targets
continue;
}

allowItemSpecs.Add(dependency.ItemSpec);
}

// Second, find PackageDefinitions that match our allowed item specs

var outputItems = new List<ITaskItem>(allowItemSpecs.Count);

foreach (var packageDef in _packageDefinitions)
{
if (!allowItemSpecs.Contains(packageDef.ItemSpec))
{
// We are not interested in this definition (not top-level, or wrong target)
continue;
}

var dependencyType = GetDependencyType(packageDef.GetMetadata(MetadataKeys.Type));

if (dependencyType == DependencyType.Package ||
dependencyType == DependencyType.Unresolved)
{
var name = packageDef.GetMetadata(MetadataKeys.Name);

if (string.IsNullOrEmpty(name))
{
// Name is required
continue;
}

var version = packageDef.GetMetadata(MetadataKeys.Version) ?? string.Empty;
var resolvedPath = packageDef.GetMetadata(MetadataKeys.ResolvedPath);
var resolved = !string.IsNullOrEmpty(resolvedPath);
var path = (resolved
? resolvedPath
: packageDef.GetMetadata(MetadataKeys.Path)) ?? string.Empty;
var isImplicitlyDefined = implicitPackageReferences.Contains(name);
var diagnosticLevel = packageDef.GetMetadata(MetadataKeys.DiagnosticLevel) ?? string.Empty;

WriteItem(packageDef.ItemSpec);
WriteMetadata(MetadataKeys.Name, name);
WriteMetadata(MetadataKeys.Version, version);
WriteMetadata(MetadataKeys.Path, path);
WriteMetadata(MetadataKeys.IsImplicitlyDefined, isImplicitlyDefined.ToString());
WriteMetadata(MetadataKeys.DiagnosticLevel, diagnosticLevel);
WriteMetadata(ResolvedMetadata, resolved.ToString());
}
}

void GetPackageDependencies()
{
_packageDependencies = new List<ITaskItem>();
foreach (var target in _lockFile.Targets)
{
// raise each library in the target
GetPackageDependenciesPerTarget(target);
}
}

void GetPackageDependenciesPerTarget(LockFileTarget target)
{
var resolvedPackageVersions = target.Libraries
.ToDictionary(pkg => pkg.Name, pkg => pkg.Version.ToNormalizedString(), StringComparer.OrdinalIgnoreCase);

string frameworkAlias = _targetNameToAliasMap[target.Name];

var transitiveProjectRefs = new HashSet<string>(
target.Libraries
.Where(lib => lib.IsTransitiveProjectReference(_lockFile, ref _projectFileDependencies, frameworkAlias))
.Select(pkg => pkg.Name),
StringComparer.OrdinalIgnoreCase);

foreach (var package in target.Libraries)
{
string packageId = GetPackageId(package);

if (_projectFileDependencies.Contains(package.Name))
{
TaskItem item = new TaskItem(packageId);
item.SetMetadata(MetadataKeys.ParentTarget, frameworkAlias); // Foreign Key
item.SetMetadata(MetadataKeys.ParentPackage, string.Empty); // Foreign Key

_packageDependencies.Add(item);
}

// get sub package dependencies
GetDependencies(package, target.Name, resolvedPackageVersions, transitiveProjectRefs);
}
}

void GetDependencies(
LockFileTargetLibrary package,
string targetName,
Dictionary<string, string> resolvedPackageVersions,
HashSet<string> transitiveProjectRefs)
{
string packageId = GetPackageId(package);
string frameworkAlias = _targetNameToAliasMap[targetName];
foreach (var deps in package.Dependencies)
{
if (!resolvedPackageVersions.TryGetValue(deps.Id, out string version))
{
continue;
}

string depsName = $"{deps.Id}/{version}";

WriteItem(depsName);
WriteMetadata(MetadataKeys.ParentTarget, frameworkAlias); // Foreign Key
WriteMetadata(MetadataKeys.ParentPackage, packageId); // Foreign Key

if (transitiveProjectRefs.Contains(deps.Id))
{
WriteMetadata(MetadataKeys.TransitiveProjectReference, "true");
}
}
}

static string GetPackageId(LockFileTargetLibrary package) => $"{package.Name}/{package.Version.ToNormalizedString()}";

// get library and file definitions
void GetPackageDefinitions()
{
foreach (var package in _lockFile.Libraries)
{
var packageName = package.Name;
var packageVersion = package.Version.ToNormalizedString();
string packageId = $"{packageName}/{packageVersion}";
var item = new TaskItem(packageId);
item.SetMetadata(MetadataKeys.Name, packageName);
item.SetMetadata(MetadataKeys.Type, package.Type);
item.SetMetadata(MetadataKeys.Version, packageVersion);

item.SetMetadata(MetadataKeys.Path, package.Path ?? string.Empty);

string resolvedPackagePath = ResolvePackagePath(package);
item.SetMetadata(MetadataKeys.ResolvedPath, resolvedPackagePath ?? string.Empty);

item.SetMetadata(MetadataKeys.DiagnosticLevel, GetPackageDiagnosticLevel(package));

_packageDefinitions.Add(item);
}
}

string ResolvePackagePath(LockFileLibrary package)
{
if (package.IsProject())
{
var relativeMSBuildProjectPath = package.MSBuildProject;

if (string.IsNullOrEmpty(relativeMSBuildProjectPath))
{
throw new BuildErrorException(Strings.ProjectAssetsConsumedWithoutMSBuildProjectPath, package.Name, _task.ProjectAssetsFile);
}

return GetAbsolutePathFromProjectRelativePath(relativeMSBuildProjectPath);
}
else
{
return _packageResolver.GetPackageDirectory(package.Name, package.Version);
}
}

string GetAbsolutePathFromProjectRelativePath(string path)
{
return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(_task.ProjectPath), path));
}

string GetPackageDiagnosticLevel(LockFileLibrary package)
{
string target = _task.TargetFramework ?? "";

var messages = _lockFile.LogMessages.Where(log => log.LibraryId == package.Name && log.TargetGraphs
.Select(tg =>
{
var parsedTargetGraph = NuGetFramework.Parse(tg);
var alias = _lockFile.PackageSpec.TargetFrameworks.FirstOrDefault(tf => tf.FrameworkName == parsedTargetGraph)?.TargetAlias;
return alias ?? tg;
}).Contains(target));

if (messages.Count() == 0)
{
return string.Empty;
}

return messages.Max(log => log.Level).ToString();
}

static HashSet<string> GetImplicitPackageReferences(string defaultImplicitPackages)
{
var implicitPackageReferences = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrEmpty(defaultImplicitPackages))
{
return implicitPackageReferences;
}

var packageNames = defaultImplicitPackages.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (packageNames.Length == 0)
{
return implicitPackageReferences;
}

foreach (var packageReference in packageNames)
{
implicitPackageReferences.Add(packageReference);
}

return implicitPackageReferences;
}

static DependencyType GetDependencyType(string dependencyTypeString)
{
Enum.TryParse(dependencyTypeString, ignoreCase: true, out DependencyType dependencyType);
return dependencyType;
}
}

private void WriteResourceAssemblies()
{
WriteItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,8 @@ Copyright (c) .NET Foundation. All rights reserved.
SatelliteResourceLanguages="$(SatelliteResourceLanguages)"
DesignTimeBuild="$(DesignTimeBuild)"
ContinueOnError="$(ContinueOnError)"
PackageReferences="@(PackageReference)">
PackageReferences="@(PackageReference)"
DefaultImplicitPackages= "$(DefaultImplicitPackages)">

<!-- NOTE: items names here are inconsistent because they match prior implementation
(that was spread across different tasks/targets) for backwards compatibility. -->
Expand Down

0 comments on commit 8a2df1e

Please sign in to comment.