|
// 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.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NuGet.Common;
using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
namespace NuGet.Protocol
{
internal static class ResolverMetadataClient
{
/// <summary>
/// Retrieve the <see cref="RemoteSourceDependencyInfo" /> for a registration.
/// </summary>
/// <returns>Returns an empty sequence if the package does not exist.</returns>
public static async Task<IEnumerable<RemoteSourceDependencyInfo>> GetDependencies(
HttpSource httpClient,
Uri registrationUri,
string packageId,
VersionRange range,
SourceCacheContext cacheContext,
ILogger log,
CancellationToken token)
{
var ranges = await RegistrationUtility.LoadRanges(httpClient, registrationUri, packageId, range, cacheContext, log, token);
var results = new HashSet<RemoteSourceDependencyInfo>();
foreach (var rangeObj in ranges)
{
if (rangeObj == null)
{
throw new InvalidDataException(registrationUri.AbsoluteUri);
}
foreach (JObject packageObj in rangeObj["items"]!)
{
var catalogEntry = (JObject)packageObj["catalogEntry"]!;
var version = NuGetVersion.Parse(catalogEntry["version"]!.ToString());
if (range.Satisfies(version))
{
results.Add(ProcessPackageVersion(packageObj, version));
}
}
}
return results;
}
/// <summary>
/// Process an individual package version entry
/// </summary>
/// <param name="packageObj"></param>
/// <param name="version"></param>
/// <returns>Returns the RemoteSourceDependencyInfo object corresponding to this package version</returns>
private static RemoteSourceDependencyInfo ProcessPackageVersion(JObject packageObj, NuGetVersion version)
{
var catalogEntry = (JObject)packageObj["catalogEntry"]!;
var listed = catalogEntry.GetBoolean("listed") ?? true;
var id = catalogEntry.Value<string>("id")!;
var identity = new PackageIdentity(id, version);
var dependencyGroups = new List<PackageDependencyGroup>();
var dependencyGroupsArray = (JArray?)catalogEntry["dependencyGroups"];
if (dependencyGroupsArray != null)
{
for (int i = 0; i < dependencyGroupsArray.Count; i++)
{
var dependencyGroupObj = (JObject)dependencyGroupsArray[i];
var currentFramework = GetFramework(dependencyGroupObj);
var groupDependencies = new List<PackageDependency>();
JToken? dependenciesObj;
// Packages with no dependencies have 'dependencyGroups' but no 'dependencies'
if (dependencyGroupObj.TryGetValue("dependencies", out dependenciesObj))
{
var dependencies = (JArray)dependenciesObj;
for (int j = 0; j < dependencies.Count; j++)
{
var dependencyObj = (JObject)dependencies[j];
var dependencyId = dependencyObj.Value<string>("id")!;
var dependencyRange = RegistrationUtility.CreateVersionRange(dependencyObj.Value<string>("range")!);
groupDependencies.Add(new PackageDependency(dependencyId, dependencyRange));
}
}
dependencyGroups.Add(new PackageDependencyGroup(currentFramework, groupDependencies));
}
}
var contentUri = packageObj.Value<string>("packageContent")!;
return new RemoteSourceDependencyInfo(identity, listed, dependencyGroups, contentUri);
}
/// <summary>
/// Retrieve a registration blob
/// </summary>
/// <returns>Returns null if the package does not exist.</returns>
public static async Task<RegistrationInfo?> GetRegistrationInfo(
HttpSource httpClient,
Uri registrationUri,
string packageId,
VersionRange range,
SourceCacheContext cacheContext,
NuGetFramework projectTargetFramework,
ILogger log,
CancellationToken token)
{
var frameworkComparer = NuGetFrameworkFullComparer.Instance;
var frameworkReducer = new FrameworkReducer();
var dependencies = await GetDependencies(httpClient, registrationUri, packageId, range, cacheContext, log, token);
if (!dependencies.Any())
{
return null;
}
var registrationInfo = new RegistrationInfo
{
Id = packageId,
IncludePrerelease = true
};
foreach (var item in dependencies)
{
var packageInfo = new PackageInfo
{
Listed = item.Listed,
Version = item.Identity.Version,
PackageContent = new Uri(item.ContentUri)
};
// only one target framework group will be used at install time, which means
// we can filter down to that group now by using the project target framework
var depFrameworks = item.DependencyGroups.Select(e => e.TargetFramework);
var targetFramework = frameworkReducer.GetNearest(projectTargetFramework, depFrameworks);
// If no frameworks are compatible we just ignore them - Should this be an exception?
if (targetFramework != null)
{
var dependencyGroup = item.DependencyGroups.FirstOrDefault(d => frameworkComparer.Equals(targetFramework, d.TargetFramework));
if (dependencyGroup != null)
{
foreach (var dependency in dependencyGroup.Packages)
{
var dependencyInfo = new DependencyInfo
{
Id = dependency.Id,
Range = dependency.VersionRange
};
packageInfo.Dependencies.Add(dependencyInfo);
}
}
}
registrationInfo.Add(packageInfo);
}
return registrationInfo;
}
/// <summary>
/// Retrieve the target framework from a dependency group obj
/// </summary>
private static NuGetFramework GetFramework(JObject dependencyGroupObj)
{
var framework = NuGetFramework.AnyFramework;
if (dependencyGroupObj["targetFramework"] != null)
{
framework = NuGetFramework.Parse(dependencyGroupObj["targetFramework"]!.ToString());
}
return framework;
}
}
}
|