|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.ProjectModel;
using NuGet.Versioning;
namespace NuGet.Commands
{
public static class ToolRestoreUtility
{
/// <summary>
/// Build a package spec in memory to execute the tool restore as if it were
/// its own project. For now, we always restore for a null runtime and a single
/// constant framework.
/// </summary>
public static PackageSpec GetSpec(string projectFilePath, string id, VersionRange versionRange, NuGetFramework framework, string packagesPath, IList<string> fallbackFolders, IList<PackageSource> sources, WarningProperties projectWideWarningProperties)
{
var frameworkShortFolderName = framework.GetShortFolderName();
var name = GetUniqueName(id, frameworkShortFolderName, versionRange);
return new PackageSpec()
{
Name = name, // make sure this package never collides with a dependency
FilePath = projectFilePath,
TargetFrameworks =
{
new TargetFrameworkInformation
{
TargetAlias = frameworkShortFolderName,
FrameworkName = framework,
Dependencies =
[
new LibraryDependency()
{
LibraryRange = new LibraryRange(id, versionRange, LibraryDependencyTarget.Package)
}
]
}
},
RestoreMetadata = new ProjectRestoreMetadata()
{
ProjectStyle = ProjectStyle.DotnetCliTool,
ProjectName = name,
ProjectUniqueName = name,
ProjectPath = projectFilePath,
PackagesPath = packagesPath,
FallbackFolders = fallbackFolders,
Sources = sources,
OriginalTargetFrameworks = {
frameworkShortFolderName
},
TargetFrameworks =
{
new ProjectRestoreMetadataFrameworkInfo
{
TargetAlias = frameworkShortFolderName,
FrameworkName = framework,
ProjectReferences = { }
}
},
ProjectWideWarningProperties = projectWideWarningProperties ?? new WarningProperties()
}
};
}
public static string GetUniqueName(string id, string framework, VersionRange versionRange)
{
return $"{id}-{framework}-{versionRange.ToNormalizedString()}".ToLowerInvariant();
}
/// <summary>
/// Only one output can win per packages folder/version range. Between colliding requests take
/// the intersection of the inputs used.
/// </summary>
/// <returns></returns>
public static IReadOnlyList<RestoreSummaryRequest> GetSubSetRequests(IEnumerable<RestoreSummaryRequest> requestSummaries)
{
var results = new List<RestoreSummaryRequest>();
var tools = new List<RestoreSummaryRequest>();
foreach (var requestSummary in requestSummaries)
{
if (requestSummary.Request.Project.RestoreMetadata?.ProjectStyle == ProjectStyle.DotnetCliTool)
{
tools.Add(requestSummary);
}
else
{
// Pass non-tools to the output
results.Add(requestSummary);
}
}
foreach (var toolIdGroup in tools.GroupBy(e => GetToolIdOrNullFromSpec(e.Request.Project), StringComparer.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(toolIdGroup.Key))
{
// Pass problem requests on to fail with a better error message
results.AddRange(toolIdGroup);
}
else
{
// Actually narrow down the requests now
results.AddRange(GetSubSetRequestsForSingleId(toolIdGroup));
}
}
return results;
}
public static IReadOnlyList<RestoreSummaryRequest> GetSubSetRequestsForSingleId(IEnumerable<RestoreSummaryRequest> requests)
{
var results = new List<RestoreSummaryRequest>();
// Unique by packages folder
foreach (var packagesFolderGroup in requests.GroupBy(e => e.Request.PackagesDirectory, StringComparer.Ordinal))
{
// Unique by version range
foreach (var versionRangeGroup in packagesFolderGroup.GroupBy(e =>
GetToolDependencyOrNullFromSpec(e.Request.Project)?.LibraryRange?.VersionRange))
{
// This could be improved in the future, for now take the request with the least sources
// to ensure that if this is going to fail anywhere it will *probably* consistently fail.
// Take requests with no imports over requests that do need imports to increase the chance
// of failing.
var bestRequest = versionRangeGroup
.OrderBy(e => e.Request.Project.TargetFrameworks.Any(f => f.FrameworkName is FallbackFramework) ? 1 : 0)
.ThenBy(e => e.Request.DependencyProviders.RemoteProviders.Count)
.First();
results.Add(bestRequest);
}
}
return results;
}
/// <summary>
/// Returns the name of the single dependency in the spec or null.
/// </summary>
public static string GetToolIdOrNullFromSpec(PackageSpec spec)
{
return GetToolDependencyOrNullFromSpec(spec)?.Name;
}
/// <summary>
/// Returns the name of the single dependency in the spec or null.
/// </summary>
public static LibraryDependency GetToolDependencyOrNullFromSpec(PackageSpec spec)
{
if (spec == null)
{
return null;
}
return spec.TargetFrameworks.SelectMany(e => e.Dependencies).SingleOrDefault();
}
public static LockFileTargetLibrary GetToolTargetLibrary(LockFile toolLockFile, string toolId)
{
var target = toolLockFile.Targets.Single();
return target
.Libraries
.FirstOrDefault(l => StringComparer.OrdinalIgnoreCase.Equals(toolId, l.Name));
}
}
}
|