using System; using System.IO; using System.Linq; using NuGet.Protocol.Core.Types; using PortingAssistant.Client.NuGet.InternalNuGet; using System.Collections.Generic; using System.Threading.Tasks; using System.Threading; using NuGet.Common; using NuGet.Versioning; using Microsoft.Extensions.Logging; using NuGet.Configuration; using PortingAssistant.Client.Model; using Settings = NuGet.Configuration.Settings; namespace PortingAssistant.Client.NuGet { public class InternalPackagesCompatibilityChecker : ICompatibilityChecker { private readonly IPortingAssistantInternalNuGetCompatibilityHandler _internalNuGetCompatibilityHandler; private readonly ILogger _logger; public PackageSourceType CompatibilityCheckerType => PackageSourceType.PRIVATE; public InternalPackagesCompatibilityChecker( IPortingAssistantInternalNuGetCompatibilityHandler internalNuGetCompatibilityHandler, ILogger logger) { _internalNuGetCompatibilityHandler = internalNuGetCompatibilityHandler; _logger = logger; } public Dictionary> Check( IEnumerable packageVersions, string pathToSolution, bool isIncremental = false, bool incrementalRefresh = false) { var internalRepositories = GetInternalRepositories(pathToSolution); var internalPackages = GetInternalPackagesAsync(packageVersions.ToList(), internalRepositories).Result; _logger.LogInformation("Checking internal source for compatibility of {0} package(s)", internalPackages.Count()); var packageVersionsGroupedByPackageId = internalPackages .GroupBy(pv => pv.PackageId) .ToDictionary(pvGroup => pvGroup.Key, pvGroup => pvGroup.ToList()); var processPackageVersionCompatibilityTasks = StartPackageVersionCompatibilityTasks(packageVersionsGroupedByPackageId, internalRepositories); var compatibilityResults = new Dictionary>(); processPackageVersionCompatibilityTasks.ForEach(packageVersionCompatibilityTaskPair => { var compatibilityTask = packageVersionCompatibilityTaskPair.Key; var packageVersionList = packageVersionCompatibilityTaskPair.Value; packageVersionList.ForEach(packageVersion => { compatibilityResults.Add(packageVersion, compatibilityTask); }); }); return compatibilityResults; } private List, List>> StartPackageVersionCompatibilityTasks( Dictionary> packageVersionsGroupedByPackageId, IEnumerable internalRepositories) { var processPackageVersionCompatibilityTasks = packageVersionsGroupedByPackageId .Select(groupedPackageVersions => { var packageId = groupedPackageVersions.Key; var packageVersions = groupedPackageVersions.Value; var compatibleVersionSet = new SortedSet(); var versionSet = new SortedSet(); var taskCompletionSource = new TaskCompletionSource(); var processCompatibilityTasks = packageVersions.Select(async packageVersionPair => { var version = packageVersionPair.Version; versionSet.Add(version); var compatibility = await ProcessCompatibility(packageVersionPair, internalRepositories); if (compatibility?.IsCompatible == true) { compatibleVersionSet.Add(version); } }).Where(task => task != null).ToList(); GetPackageDetailsAsync(processCompatibilityTasks, packageId, compatibleVersionSet, versionSet, taskCompletionSource); return new KeyValuePair, List>(taskCompletionSource.Task, packageVersions); }) .ToList(); return processPackageVersionCompatibilityTasks; } private async void GetPackageDetailsAsync(List processCompatibilityTasks, string packageId, SortedSet compatibleVersions, SortedSet versions, TaskCompletionSource taskCompletionSource) { await Task.WhenAll(processCompatibilityTasks.ToArray()); if (versions.Count == 0) { taskCompletionSource.SetException(new PortingAssistantClientException(ExceptionMessage.PackageNotFound(packageId), null)); } var packageDetails = new PackageDetails() { Name = packageId, Versions = versions, Targets = new Dictionary> { { "net6.0", compatibleVersions } }, Api = new List().ToArray() }; taskCompletionSource.SetResult(packageDetails); } private async Task ProcessCompatibility( PackageVersionPair packageVersion, IEnumerable internalRepositories) { try { return await _internalNuGetCompatibilityHandler.CheckCompatibilityAsync( packageVersion.PackageId, packageVersion.Version, "net6.0", internalRepositories); } catch (Exception ex) when (ex is PortingAssistantClientException) { _logger.LogInformation($"Could not check compatibility for package {packageVersion} " + $"using internal resources. Error: {ex.Message}"); } catch (Exception ex) { _logger.LogError($"Unexpected error encountered when checking compatibility of package {packageVersion} " + $"using internal source(s): {ex}"); } return null; } public virtual IEnumerable GetInternalRepositories(string pathToSolution) { string solutionDirectory = Path.GetDirectoryName(pathToSolution); var settings = Settings.LoadDefaultSettings(solutionDirectory); var sourceRepositoryProvider = new SourceRepositoryProvider( new PackageSourceProvider(settings), Repository.Provider.GetCoreV3()); var repositories = sourceRepositoryProvider .GetRepositories() .Where(r => !string.Equals(r.PackageSource.Name, "nuget.org", StringComparison.OrdinalIgnoreCase) && r.PackageSource.IsEnabled && !string.Equals(r.PackageSource.Name, "microsoft visual studio offline packages", StringComparison.OrdinalIgnoreCase)) .ToList(); return repositories; } public async Task> GetInternalPackagesAsync( IReadOnlyList packageVersions, IEnumerable internalRepositories) { var internalPackages = new List(); foreach (var repository in internalRepositories) { var source = await repository.GetResourceAsync(); var cacheContext = new SourceCacheContext(); foreach (var package in packageVersions) { try { var packageExists = await source.DoesPackageExistAsync( package.PackageId, new NuGetVersion(package.Version), cacheContext, NullLogger.Instance, CancellationToken.None); if (packageExists) { internalPackages.Add(package); } } catch (Exception error) { _logger.LogError($"Error encountered while accessing internal source repository {repository.PackageSource.Source}: " + $"Error type: {error.GetType()}\t Error Message: {error.Message}\t Stack trace: {error.StackTrace}"); } } } return internalPackages; } } }