From f25eca3c837edd6d512356666af36e06d20d39db Mon Sep 17 00:00:00 2001 From: Mikko Uuksulainen Date: Tue, 15 Sep 2020 14:07:57 +0300 Subject: [PATCH 1/4] Move NugetDownloader to the own solution from PluginFramework --- Directory.Build.props | 2 +- README.md | 23 +- docs/Adafy_logo_256.png | Bin 0 -> 12815 bytes src/Weikio.NugetDownloader.sln | 25 ++ src/Weikio.NugetDownloader/ConsoleLogger.cs | 46 +++ .../FolderProjectContext.cs | 78 ++++ .../MachineWideSettings.cs | 22 ++ src/Weikio.NugetDownloader/NuGetFeed.cs | 20 + src/Weikio.NugetDownloader/NugetDownloader.cs | 350 ++++++++++++++++++ .../PackageNotFoundException.cs | 12 + .../PluginFolderNugetProject.cs | 103 ++++++ .../Weikio.NugetDownloader.csproj | 32 ++ 12 files changed, 696 insertions(+), 17 deletions(-) create mode 100644 docs/Adafy_logo_256.png create mode 100644 src/Weikio.NugetDownloader.sln create mode 100644 src/Weikio.NugetDownloader/ConsoleLogger.cs create mode 100644 src/Weikio.NugetDownloader/FolderProjectContext.cs create mode 100644 src/Weikio.NugetDownloader/MachineWideSettings.cs create mode 100644 src/Weikio.NugetDownloader/NuGetFeed.cs create mode 100644 src/Weikio.NugetDownloader/NugetDownloader.cs create mode 100644 src/Weikio.NugetDownloader/PackageNotFoundException.cs create mode 100644 src/Weikio.NugetDownloader/PluginFolderNugetProject.cs create mode 100644 src/Weikio.NugetDownloader/Weikio.NugetDownloader.csproj diff --git a/Directory.Build.props b/Directory.Build.props index 0a32d42..05bb22b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ logo_256.png 1.0 MIT - https://github.com/weikio/Weikio.EventFramework + https://github.com/weikio/NugetDownloader https://www.weik.io ©2020 Weik.io 8.0 diff --git a/README.md b/README.md index b763381..e6796d2 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,16 @@ -# Weik.io Template +# NugetDownloader [![NuGet Version](https://img.shields.io/nuget/v/Weikio.NugetDownloader.svg?style=flat&label=Weikio.NugetDownloader)](https://www.nuget.org/packages/Weikio.NugetDownloader/) -Template for Weik.io's .NET Core projects. Contains the following constructs: - -* Coding conventions in the format of .editorconfig. -* Folder structure -* Global.json -* Git ignore - -This template can be used as a starting point for all the Weik.io projects. +Tool for downloading and installing Nuget packages from .NET. ## Usage -The template can be used when creating a new repository in GitHub: - -![Creating a new repository using the template](2020-08-10-13-30-31.png) - -Select Weikio/Template from the Repository teamplte dropdown. +NugetDownloader is available from Nuget as a .NET Core 3.1 package: -Alternatively a new repository with this template can be created from https://github.com/weikio/Template: +[![NuGet Version](https://img.shields.io/nuget/v/Weikio.NugetDownloader.svg?style=flat&label=Weikio.NugetDownloader)](https://www.nuget.org/packages/Weikio.NugetDownloader/) -![Creating a new repository from the template's repository](2020-08-10-13-34-18.png) +``` +Install-Package Weikio.NugetDownloader +``` ## License diff --git a/docs/Adafy_logo_256.png b/docs/Adafy_logo_256.png new file mode 100644 index 0000000000000000000000000000000000000000..736795e522344ca9b4c44fc555b829f69a574929 GIT binary patch literal 12815 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 literal 0 HcmV?d00001 diff --git a/src/Weikio.NugetDownloader.sln b/src/Weikio.NugetDownloader.sln new file mode 100644 index 0000000..3d8f298 --- /dev/null +++ b/src/Weikio.NugetDownloader.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weikio.NugetDownloader", "Weikio.NugetDownloader\Weikio.NugetDownloader.csproj", "{4BCDAB59-F231-4241-B6BE-93BDC047C86F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {69466E23-050E-41B5-945E-367A476EF6CB} + EndGlobalSection +EndGlobal diff --git a/src/Weikio.NugetDownloader/ConsoleLogger.cs b/src/Weikio.NugetDownloader/ConsoleLogger.cs new file mode 100644 index 0000000..95ce8f2 --- /dev/null +++ b/src/Weikio.NugetDownloader/ConsoleLogger.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using NuGet.Common; + +namespace Weikio.NugetDownloader +{ + public class ConsoleLogger : LoggerBase + { + public override void Log(ILogMessage message) + { + switch (message.Level) + { + case LogLevel.Debug: + Console.WriteLine($"DEBUG - {message}"); + break; + + case LogLevel.Verbose: + Console.WriteLine($"VERBOSE - {message}"); + break; + + case LogLevel.Information: + Console.WriteLine($"INFORMATION - {message}"); + break; + + case LogLevel.Minimal: + Console.WriteLine($"MINIMAL - {message}"); + break; + + case LogLevel.Warning: + Console.WriteLine($"WARNING - {message}"); + break; + + case LogLevel.Error: + Console.WriteLine($"ERROR - {message}"); + break; + } + } + + public override Task LogAsync(ILogMessage message) + { + Log(message); + + return Task.CompletedTask; + } + } +} diff --git a/src/Weikio.NugetDownloader/FolderProjectContext.cs b/src/Weikio.NugetDownloader/FolderProjectContext.cs new file mode 100644 index 0000000..a6a5c6a --- /dev/null +++ b/src/Weikio.NugetDownloader/FolderProjectContext.cs @@ -0,0 +1,78 @@ +using System; +using System.Globalization; +using System.Xml.Linq; +using NuGet.Common; +using NuGet.Packaging; +using NuGet.ProjectManagement; + +namespace Weikio.NugetDownloader +{ + class FolderProjectContext : INuGetProjectContext + { + private readonly ILogger _logger; + + public FolderProjectContext(ILogger logger) + { + _logger = logger; + } + + public ExecutionContext ExecutionContext => null; + + public PackageExtractionContext PackageExtractionContext { get; set; } + + public XDocument OriginalPackagesConfig { get; set; } + + public ISourceControlManagerProvider SourceControlManagerProvider => null; + + public void Log(MessageLevel level, string message, params object[] args) + { + if (args.Length > 0) + { + message = string.Format(CultureInfo.CurrentCulture, message, args); + } + + switch (level) + { + case MessageLevel.Debug: + _logger.LogDebug(message); + break; + + case MessageLevel.Info: + _logger.LogMinimal(message); + break; + + case MessageLevel.Warning: + _logger.LogWarning(message); + break; + + case MessageLevel.Error: + _logger.LogError(message); + break; + } + } + + public void Log(ILogMessage message) + { + _logger.Log(message); + } + + public void ReportError(string message) + { + _logger.LogError(message); + } + + public void ReportError(ILogMessage message) + { + _logger.Log(message); + } + + public virtual FileConflictAction ResolveFileConflict(string message) + { + return FileConflictAction.IgnoreAll; + } + + public NuGetActionType ActionType { get; set; } + + public Guid OperationId { get; set; } + } +} diff --git a/src/Weikio.NugetDownloader/MachineWideSettings.cs b/src/Weikio.NugetDownloader/MachineWideSettings.cs new file mode 100644 index 0000000..21df22e --- /dev/null +++ b/src/Weikio.NugetDownloader/MachineWideSettings.cs @@ -0,0 +1,22 @@ +using System; +using NuGet.Common; +using NuGet.Configuration; + +namespace Weikio.NugetDownloader +{ + public class MachineWideSettings : IMachineWideSettings + { + private readonly Lazy _settings; + + public MachineWideSettings() + { + _settings = new Lazy(() => + { + var baseDirectory = NuGetEnvironment.GetFolderPath(NuGetFolderPath.MachineWideConfigDirectory); + return global::NuGet.Configuration.Settings.LoadMachineWideSettings(baseDirectory); + }); + } + + public ISettings Settings => _settings.Value; + } +} diff --git a/src/Weikio.NugetDownloader/NuGetFeed.cs b/src/Weikio.NugetDownloader/NuGetFeed.cs new file mode 100644 index 0000000..0e956eb --- /dev/null +++ b/src/Weikio.NugetDownloader/NuGetFeed.cs @@ -0,0 +1,20 @@ +// ReSharper disable once CheckNamespace +namespace Weikio.NugetDownloader +{ + public class NuGetFeed + { + public string Name { get; } + + public string Feed { get; } + + public NuGetFeed(string name, string feed = null) + { + Name = name; + Feed = feed; + } + + public string Username { get; set; } + + public string Password { get; set; } + } +} diff --git a/src/Weikio.NugetDownloader/NugetDownloader.cs b/src/Weikio.NugetDownloader/NugetDownloader.cs new file mode 100644 index 0000000..56024cf --- /dev/null +++ b/src/Weikio.NugetDownloader/NugetDownloader.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Frameworks; +using NuGet.PackageManagement; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Packaging.PackageExtraction; +using NuGet.Packaging.Signing; +using NuGet.Protocol.Core.Types; +using NuGet.Resolver; +using NuGet.Versioning; + +namespace Weikio.NugetDownloader +{ + public class NuGetDownloader + { + private readonly ILogger _logger; + + public NuGetDownloader(ILogger logger = null) + { + _logger = logger ?? new ConsoleLogger(); + } + + public async Task DownloadAsync(string packageFolder, string packageName, string packageVersion = null, bool includePrerelease = false, + NuGetFeed packageFeed = null, bool onlyDownload = false) + { + if (!Directory.Exists(packageFolder)) + { + Directory.CreateDirectory(packageFolder); + } + + var providers = GetNugetResourceProviders(); + var settings = Settings.LoadDefaultSettings(packageFolder, null, new MachineWideSettings()); + var packageSourceProvider = new PackageSourceProvider(settings); + var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, providers); + + var dotNetFramework = Assembly + .GetEntryAssembly() + .GetCustomAttribute()? + .FrameworkName; + + var frameworkNameProvider = new FrameworkNameProvider( + new[] { DefaultFrameworkMappings.Instance }, + new[] { DefaultPortableFrameworkMappings.Instance }); + + var nuGetFramework = NuGetFramework.ParseFrameworkName(dotNetFramework, frameworkNameProvider); + + IPackageSearchMetadata package = null; + SourceRepository sourceRepo = null; + + if (!string.IsNullOrWhiteSpace(packageFeed?.Feed)) + { + sourceRepo = GetSourceRepo(packageFeed, providers); + + package = await SearchPackageAsync(packageName, packageVersion, includePrerelease, sourceRepo); + } + else + { + foreach (var repo in sourceRepositoryProvider.GetRepositories()) + { + if (packageFeed != null && repo.PackageSource.Name != packageFeed.Name) + { + continue; + } + + package = await SearchPackageAsync(packageName, packageVersion, includePrerelease, repo); + + if (package != null) + { + sourceRepo = repo; + + break; + } + } + } + + if (package == null) + { + throw new PackageNotFoundException($"Couldn't find package '{packageVersion}'.{packageVersion}."); + } + + var project = new PluginFolderNugetProject(packageFolder, package, nuGetFramework, onlyDownload); + var packageManager = new NuGetPackageManager(sourceRepositoryProvider, settings, packageFolder) { PackagesFolderNuGetProject = project }; + + var clientPolicyContext = ClientPolicyContext.GetClientPolicy(settings, _logger); + + var projectContext = new FolderProjectContext(_logger) + { + PackageExtractionContext = new PackageExtractionContext( + PackageSaveMode.Defaultv2, + PackageExtractionBehavior.XmlDocFileSaveMode, + clientPolicyContext, + _logger) + }; + + var resolutionContext = new ResolutionContext( + DependencyBehavior.Lowest, + includePrerelease, + includeUnlisted: false, + VersionConstraints.None); + + var downloadContext = new PackageDownloadContext( + resolutionContext.SourceCacheContext, + packageFolder, + resolutionContext.SourceCacheContext.DirectDownload); + + // We are waiting here instead of await as await actually doesn't seem to work correctly. + packageManager.InstallPackageAsync( + project, + package.Identity, + resolutionContext, + projectContext, + downloadContext, + sourceRepo, + new List(), + CancellationToken.None).Wait(); + + if (onlyDownload) + { + var versionFolder = Path.Combine(packageFolder, package.Identity.ToString()); + + return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); + } + + return await project.GetPluginAssemblyFilesAsync(); + } + + public async Task DownloadAsync(IPackageSearchMetadata packageIdentity, SourceRepository repository, + string downloadFolder, bool onlyDownload = false) + { + if (packageIdentity == null) + { + throw new ArgumentNullException(nameof(packageIdentity)); + } + + var providers = GetNugetResourceProviders(); + var settings = Settings.LoadDefaultSettings(downloadFolder, null, new MachineWideSettings()); + var packageSourceProvider = new PackageSourceProvider(settings); + var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, providers); + + var dotNetFramework = Assembly + .GetEntryAssembly() + .GetCustomAttribute()? + .FrameworkName; + + var frameworkNameProvider = new FrameworkNameProvider( + new[] { DefaultFrameworkMappings.Instance }, + new[] { DefaultPortableFrameworkMappings.Instance }); + + var nuGetFramework = NuGetFramework.ParseFrameworkName(dotNetFramework, frameworkNameProvider); + + var project = new PluginFolderNugetProject(downloadFolder, packageIdentity, nuGetFramework, onlyDownload); + + var packageManager = new NuGetPackageManager(sourceRepositoryProvider, settings, downloadFolder) { PackagesFolderNuGetProject = project }; + + var clientPolicyContext = ClientPolicyContext.GetClientPolicy(settings, _logger); + + var projectContext = new FolderProjectContext(_logger) + { + PackageExtractionContext = new PackageExtractionContext( + PackageSaveMode.Defaultv2, + PackageExtractionBehavior.XmlDocFileSaveMode, + clientPolicyContext, + _logger) + }; + + var resolutionContext = new ResolutionContext( + DependencyBehavior.Lowest, + true, + includeUnlisted: false, + VersionConstraints.None); + + var downloadContext = new PackageDownloadContext( + resolutionContext.SourceCacheContext, + downloadFolder, + resolutionContext.SourceCacheContext.DirectDownload); + + await packageManager.InstallPackageAsync( + project, + packageIdentity.Identity, + resolutionContext, + projectContext, + downloadContext, + repository, + new List(), + CancellationToken.None); + + var versionFolder = Path.Combine(downloadFolder, packageIdentity.Identity.ToString()); + + return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); + } + + private static List> GetNugetResourceProviders() + { + var providers = new List>(); + providers.AddRange(Repository.Provider.GetCoreV3()); // Add v3 API s + + return providers; + } + + private static SourceRepository GetSourceRepo(NuGetFeed packageFeed, List> providers) + { + SourceRepository sourceRepo; + var packageSource = new PackageSource(packageFeed.Feed); + + if (!string.IsNullOrWhiteSpace(packageFeed.Username)) + { + packageSource.Credentials = new PackageSourceCredential(packageFeed.Name, packageFeed.Username, packageFeed.Password, true, null); + } + + sourceRepo = new SourceRepository(packageSource, providers); + + return sourceRepo; + } + + public async IAsyncEnumerable<(SourceRepository Repository, IPackageSearchMetadata Package)> SearchPackagesAsync(string searchTerm, + int maxResults = 128, + bool includePrerelease = false, + string nugetConfigFilePath = "") + { + var providers = GetNugetResourceProviders(); + + var packageRootFolder = ""; + + if (!string.IsNullOrWhiteSpace(Assembly.GetEntryAssembly()?.Location)) + { + packageRootFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + } + + ISettings settings; + + if (!string.IsNullOrWhiteSpace(nugetConfigFilePath)) + { + settings = Settings.LoadSettingsGivenConfigPaths(new List() { nugetConfigFilePath }); + } + else + { + settings = Settings.LoadDefaultSettings(packageRootFolder ?? "", null, new MachineWideSettings()); + } + + var packageSourceProvider = new PackageSourceProvider(settings); + var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, providers); + + var repositories = sourceRepositoryProvider.GetRepositories(); + + foreach (var repository in repositories) + { + var packageSearchResource = await repository.GetResourceAsync(); + + SearchFilter searchFilter; + + if (includePrerelease) + { + searchFilter = new SearchFilter(includePrerelease, SearchFilterType.IsAbsoluteLatestVersion); + } + else + { + searchFilter = new SearchFilter(includePrerelease); + } + + var items = await packageSearchResource.SearchAsync(searchTerm, searchFilter, 0, maxResults, _logger, CancellationToken.None); + + foreach (var packageSearchMetadata in items) + { + yield return (repository, packageSearchMetadata); + } + } + } + + public async Task> SearchPackagesAsync(NuGetFeed packageFeed, + string searchTerm, int maxResults = 128, + bool includePrerelease = false) + { + var providers = GetNugetResourceProviders(); + var sourceRepo = GetSourceRepo(packageFeed, providers); + var packageSearchResource = await sourceRepo.GetResourceAsync(); + + SearchFilter searchFilter; + + if (includePrerelease) + { + searchFilter = new SearchFilter(includePrerelease, SearchFilterType.IsAbsoluteLatestVersion); + } + else + { + searchFilter = new SearchFilter(includePrerelease); + } + + var packages = await packageSearchResource.SearchAsync(searchTerm, searchFilter, 0, maxResults, _logger, CancellationToken.None); + + return packages.Select(x => (sourceRepo, x)); + } + + private async Task SearchPackageAsync(string packageName, string version, bool includePrerelease, + SourceRepository sourceRepository) + { + var packageMetadataResource = await sourceRepository.GetResourceAsync(); + var sourceCacheContext = new SourceCacheContext(); + + IPackageSearchMetadata packageMetaData = null; + + if (!string.IsNullOrEmpty(version) && !version.Contains('*')) + { + if (NuGetVersion.TryParse(version, out var nugetversion)) + { + var packageIdentity = new PackageIdentity(packageName, NuGetVersion.Parse(version)); + + packageMetaData = await packageMetadataResource.GetMetadataAsync( + packageIdentity, + sourceCacheContext, + _logger, + CancellationToken.None); + } + } + else + { + // Can't use await as we seem to lose the thread + var searchResults = packageMetadataResource.GetMetadataAsync( + packageName, + includePrerelease, + includeUnlisted: false, + sourceCacheContext, + _logger, + CancellationToken.None).Result; + + searchResults = searchResults + .OrderByDescending(p => p.Identity.Version); + + if (!string.IsNullOrEmpty(version)) + { + var searchPattern = version.Replace("*", ".*"); + searchResults = searchResults.Where(p => Regex.IsMatch(p.Identity.Version.ToString(), searchPattern)); + } + + packageMetaData = searchResults.FirstOrDefault(); + } + + return packageMetaData; + } + } +} diff --git a/src/Weikio.NugetDownloader/PackageNotFoundException.cs b/src/Weikio.NugetDownloader/PackageNotFoundException.cs new file mode 100644 index 0000000..20fb6b9 --- /dev/null +++ b/src/Weikio.NugetDownloader/PackageNotFoundException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Weikio.NugetDownloader +{ + public class PackageNotFoundException : Exception + { + public PackageNotFoundException(string message) + : base(message) + { + } + } +} diff --git a/src/Weikio.NugetDownloader/PluginFolderNugetProject.cs b/src/Weikio.NugetDownloader/PluginFolderNugetProject.cs new file mode 100644 index 0000000..c3f0a15 --- /dev/null +++ b/src/Weikio.NugetDownloader/PluginFolderNugetProject.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.ProjectManagement; +using NuGet.Protocol.Core.Types; + +namespace Weikio.NugetDownloader +{ + public class PluginFolderNugetProject : FolderNuGetProject + { + private const string PluginAssemblyFilesFileName = "pluginAssemblyFiles.txt"; + + private readonly string _root; + private readonly IPackageSearchMetadata _pluginNuGetPackage; + private readonly NuGetFramework _targetFramework; + private readonly bool _onlyDownload; + + public PluginFolderNugetProject(string root, IPackageSearchMetadata pluginNuGetPackage, NuGetFramework targetFramework, bool onlyDownload = false) : + base(root, new PackagePathResolver(root), targetFramework) + { + _root = root; + _pluginNuGetPackage = pluginNuGetPackage; + _targetFramework = targetFramework; + _onlyDownload = onlyDownload; + } + + public override async Task> GetInstalledPackagesAsync(CancellationToken token) + { + return await base.GetInstalledPackagesAsync(token); + } + + public override async Task InstallPackageAsync(PackageIdentity packageIdentity, DownloadResourceResult downloadResourceResult, INuGetProjectContext nuGetProjectContext, CancellationToken token) + { + var result = await base.InstallPackageAsync(packageIdentity, downloadResourceResult, nuGetProjectContext, token); + + if (_onlyDownload) + { + return result; + } + + using (var zipArchive = new ZipArchive(downloadResourceResult.PackageStream)) + { + var zipArchiveEntries = zipArchive.Entries + .Where(e => e.Name.EndsWith(".dll") || e.Name.EndsWith(".exe")).ToList(); + + var entriesWithTargetFramework = zipArchiveEntries + .Select(e => new + { + TargetFramework = NuGetFramework.Parse(e.FullName.Split('/')[1]), + Entry = e + }).ToList(); + + var matchingEntries = entriesWithTargetFramework + .Where(e => e.TargetFramework.Version.Major > 0 && + e.TargetFramework.Version <= _targetFramework.Version).ToList(); + + var orderedEntries = matchingEntries + .OrderBy(e => e.TargetFramework.GetShortFolderName()).ToList(); + + if (orderedEntries.Any()) + { + var dllEntries = orderedEntries + .GroupBy(e => e.TargetFramework.GetShortFolderName()) + .Last() + .Select(e => e.Entry) + .ToArray(); + + var pluginAssemblies = new List(); + + foreach (var e in dllEntries) + { + e.ExtractToFile(Path.Combine(_root, e.Name), overwrite: true); + + if (packageIdentity.Id == _pluginNuGetPackage.Identity.Id) + { + pluginAssemblies.Add(e.Name); + } + } + + await File.WriteAllLinesAsync(Path.Combine(_root, PluginAssemblyFilesFileName), pluginAssemblies); + } + } + + return result; + } + + public async Task GetPluginAssemblyFilesAsync() + { + return await File.ReadAllLinesAsync(Path.Combine(_root, PluginAssemblyFilesFileName)); + } + + public override async Task UninstallPackageAsync(PackageIdentity packageIdentity, INuGetProjectContext nuGetProjectContext, CancellationToken token) + { + return await base.UninstallPackageAsync(packageIdentity, nuGetProjectContext, token); + } + } +} diff --git a/src/Weikio.NugetDownloader/Weikio.NugetDownloader.csproj b/src/Weikio.NugetDownloader/Weikio.NugetDownloader.csproj new file mode 100644 index 0000000..331f5ac --- /dev/null +++ b/src/Weikio.NugetDownloader/Weikio.NugetDownloader.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.1 + true + true + Tool for downloading and installing NuGet packages from .NET. + NuGet catalog for Plugin Framework allows you to use NuGet packages as plugins with Plugin Framework. + Weikio.NugetDownloader + Weikio.NugetDownloader + Weikio.NugetDownloader + nuget;plugins;addons;extensions;plugin framework + logo_256.png + NuGet Catalog for Plugin Framework + https://github.com/weikio/NugetDownloader + + + + + + + + + + + + + + + + + From 21685c4031d15c82d1433dabb9a4d7cd4bd5de32 Mon Sep 17 00:00:00 2001 From: Mikko Uuksulainen Date: Wed, 16 Sep 2020 10:49:31 +0300 Subject: [PATCH 2/4] Add test project --- src/Weikio.NugetDownloader.sln | 11 ++++++++++ .../NugetDownloaderTests.cs | 14 +++++++++++++ .../Weikio.NugetDownloader.Tests.csproj | 21 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs create mode 100644 tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj diff --git a/src/Weikio.NugetDownloader.sln b/src/Weikio.NugetDownloader.sln index 3d8f298..f023899 100644 --- a/src/Weikio.NugetDownloader.sln +++ b/src/Weikio.NugetDownloader.sln @@ -5,6 +5,10 @@ VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weikio.NugetDownloader", "Weikio.NugetDownloader\Weikio.NugetDownloader.csproj", "{4BCDAB59-F231-4241-B6BE-93BDC047C86F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D712A662-C87A-433F-B3BC-2E5DB5EAB0A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weikio.NugetDownloader.Tests", "..\tests\integration\Weikio.NugetDownloader.Tests\Weikio.NugetDownloader.Tests.csproj", "{09606C73-4CAE-4069-9B74-A623F7957E4F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,10 +19,17 @@ Global {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Release|Any CPU.Build.0 = Release|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {09606C73-4CAE-4069-9B74-A623F7957E4F} = {D712A662-C87A-433F-B3BC-2E5DB5EAB0A5} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69466E23-050E-41B5-945E-367A476EF6CB} EndGlobalSection diff --git a/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs b/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs new file mode 100644 index 0000000..a75f5b5 --- /dev/null +++ b/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace Weikio.NugetDownloader.Tests +{ + public class NugetDownloaderTests + { + [Fact] + public void Test1() + { + + } + } +} diff --git a/tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj b/tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj new file mode 100644 index 0000000..9bc0d5a --- /dev/null +++ b/tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + From fdb7de718d807050f8e43150bb754d94d9c73d75 Mon Sep 17 00:00:00 2001 From: Mikko Uuksulainen Date: Thu, 17 Sep 2020 14:12:22 +0300 Subject: [PATCH 3/4] Add initial integration tests for NugetDownloader --- .../NugetDownloaderTests.cs | 236 +++++++++++++++++- 1 file changed, 234 insertions(+), 2 deletions(-) diff --git a/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs b/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs index a75f5b5..713e94a 100644 --- a/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs +++ b/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs @@ -1,14 +1,246 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using FluentAssertions; +using NuGet.Protocol.Core.Types; using Xunit; namespace Weikio.NugetDownloader.Tests { - public class NugetDownloaderTests + /* + * Note that these test depend on public NuGet feed and expect certain packages to be available. + * This tests can fail without notice if feed is down or package is removed. + */ + + public class NugetDownloaderTests : IDisposable { + private readonly string _packagesFolderInTestsBin; + private const string _packageFromNugetOrgName = "Newtonsoft.Json"; + private const string _packageFromThirdPartyFeedName = "DummyProject"; + private const string _thirdPartyFeedName = "AdafyPublic"; + private const string _thirdPartyFeed = "https://pkgs.dev.azure.com/adafy/df962856-ce0c-4e96-8999-bee7c8b0582c/_packaging/AdafyPublic/nuget/v3/index.json"; + + public NugetDownloaderTests() + { + var executingAssemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + _packagesFolderInTestsBin = Path.Combine(executingAssemblyDir!, "FeedTestPackages"); + } + + public void Dispose() + { + if (Directory.Exists(_packagesFolderInTestsBin)) + { + Directory.Delete(_packagesFolderInTestsBin, true); + } + } + + [Fact] + public async Task DownloadsPackageFromNugetOrg() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsPackageFromNugetOrg)); + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package is installed to the directory"); + + var files = Directory.GetFiles(packageFolder, "*.*", SearchOption.TopDirectoryOnly); + files.Should().NotBeEmpty("because NuGet package has been downloaded"); + files.Should().ContainMatch("*.dll", "because NuGet packages has dll file."); + } + + [Fact] + public async Task DownloadsOnlyPackageFromNugetOrg() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsOnlyPackageFromNugetOrg)); + var downloader = new NuGetDownloader(); + + // Act + var packageFiles = await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName, null, false, null, true); + + // Assert + packageFiles.Should().NotBeEmpty("because return value should contain listing of files in package."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package is installed to the directory"); + + var files = Directory.GetFiles(packageFolder, "*.*", SearchOption.TopDirectoryOnly); + files.Should().BeEmpty("because package is only download but not extracted"); + } + + [Fact] + public async Task DownloadsCorrectVersionFromNugetOrg() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsCorrectVersionFromNugetOrg)); + var version = "12.0.1"; + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName, version, false, null, false); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromNugetOrgName}.{version}*", "because package is installed to the directory"); + } + + [Fact] + public async Task ExtractsCorrectFrameworkVersionFromNugetOrg() + { + // Arrange + var dotnetFramework = GetDotnetFrameworkName(); + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsCorrectVersionFromNugetOrg)); + var downloader = new NuGetDownloader(); + + // Act + await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName, null, false, null, true); + + // Assert + var dllFiles = Directory.GetFiles(packageFolder, "*.dll", SearchOption.TopDirectoryOnly); + var assembiles = dllFiles.Select(Assembly.Load); + + foreach (var assembly in assembiles) + { + AssertAssemblyFrameWork(dotnetFramework, assembly); + } + } + [Fact] - public void Test1() + public async Task DownloadsPackageFromThirdPartyFeed() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsPackageFromThirdPartyFeed)); + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromThirdPartyFeedName, null, false, + new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed)); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because package is installed to the directory"); + + var files = Directory.GetFiles(packageFolder, "*.*", SearchOption.TopDirectoryOnly); + files.Should().NotBeEmpty("because NuGet package has been downloaded"); + files.Should().ContainMatch("*.dll", "because NuGet packages has dll file."); + } + + + [Fact] + public async Task DownloadsPreleasePackageFromThirdpartyFeed() + { + // Arrange + var version = "1.2.3-beta"; + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsPreleasePackageFromThirdpartyFeed)); + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromThirdPartyFeedName, version, true, + new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed)); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}.{version}*", "because package is installed to the directory"); + } + + [Fact] + public async Task SearchesPackageFromNugetOrg() + { + // Arrange + var downloader = new NuGetDownloader(); + + // Act + var packages = new List(); + var results = downloader.SearchPackagesAsync(_packageFromNugetOrgName); + await foreach (var result in results) + { + packages.Add(result.Package); + } + + // Assert + packages.Should().NotBeEmpty("because search should find some packages"); + packages.Select(p => p.Title).Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because feed should contain the package."); + } + + [Fact] + public async Task SearchesPackageFromThirdPartyFeed() + { + // Arrange + var downloader = new NuGetDownloader(); + + // Act + var packages = new List(); + var results = downloader.SearchPackagesAsync(new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed), _packageFromThirdPartyFeedName); + await foreach (var result in results) + { + packages.Add(result.Package); + } + + // Assert + packages.Should().NotBeEmpty("because search should find some packages"); + packages.Select(p => p.Title).Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because feed should contain the package."); + } + + [Fact] + public async Task SearchesPrereleasePackageFromThirdPartyFeed() + { + // Arrange + var version = "1.2.3-beta"; + var downloader = new NuGetDownloader(); + + // Act + var packages = new List(); + var results = downloader.SearchPackagesAsync( + new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed), _packageFromThirdPartyFeedName, 128, true); + await foreach (var result in results) + { + packages.Add(result.Package); + } + + // Assert + packages.Should().NotBeEmpty("because search should find some packages"); + packages.Select(p => p.Identity.Version.ToString()).Should().ContainMatch($"*{version}*", + "because feed should contain the package."); + } + + private string GetDotnetFrameworkName() + { + var dotNetFramework = Assembly + .GetEntryAssembly() + .GetCustomAttribute()? + .FrameworkName; + + return dotNetFramework; + } + + private void AssertAssemblyFrameWork(string targetFramework, Assembly assembly) { + var assemblyFramework = assembly + .GetCustomAttribute()? + .FrameworkName; + assemblyFramework.Should().Be(targetFramework, "because only target framework version should be extracted."); } } } From 74cf05f16a35c3b9ce9fec606c170daf8e47174b Mon Sep 17 00:00:00 2001 From: Mikko Uuksulainen Date: Thu, 17 Sep 2020 16:19:26 +0300 Subject: [PATCH 4/4] Slight refactoring to Nuget downloader to lower duplication --- src/Weikio.NugetDownloader/NugetDownloader.cs | 118 ++++++------------ 1 file changed, 38 insertions(+), 80 deletions(-) diff --git a/src/Weikio.NugetDownloader/NugetDownloader.cs b/src/Weikio.NugetDownloader/NugetDownloader.cs index 56024cf..6d0e0d1 100644 --- a/src/Weikio.NugetDownloader/NugetDownloader.cs +++ b/src/Weikio.NugetDownloader/NugetDownloader.cs @@ -33,30 +33,14 @@ public NuGetDownloader(ILogger logger = null) public async Task DownloadAsync(string packageFolder, string packageName, string packageVersion = null, bool includePrerelease = false, NuGetFeed packageFeed = null, bool onlyDownload = false) { - if (!Directory.Exists(packageFolder)) - { - Directory.CreateDirectory(packageFolder); - } + IPackageSearchMetadata package = null; + SourceRepository sourceRepo = null; var providers = GetNugetResourceProviders(); var settings = Settings.LoadDefaultSettings(packageFolder, null, new MachineWideSettings()); var packageSourceProvider = new PackageSourceProvider(settings); var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, providers); - var dotNetFramework = Assembly - .GetEntryAssembly() - .GetCustomAttribute()? - .FrameworkName; - - var frameworkNameProvider = new FrameworkNameProvider( - new[] { DefaultFrameworkMappings.Instance }, - new[] { DefaultPortableFrameworkMappings.Instance }); - - var nuGetFramework = NuGetFramework.ParseFrameworkName(dotNetFramework, frameworkNameProvider); - - IPackageSearchMetadata package = null; - SourceRepository sourceRepo = null; - if (!string.IsNullOrWhiteSpace(packageFeed?.Feed)) { sourceRepo = GetSourceRepo(packageFeed, providers); @@ -88,50 +72,7 @@ public async Task DownloadAsync(string packageFolder, string packageNa throw new PackageNotFoundException($"Couldn't find package '{packageVersion}'.{packageVersion}."); } - var project = new PluginFolderNugetProject(packageFolder, package, nuGetFramework, onlyDownload); - var packageManager = new NuGetPackageManager(sourceRepositoryProvider, settings, packageFolder) { PackagesFolderNuGetProject = project }; - - var clientPolicyContext = ClientPolicyContext.GetClientPolicy(settings, _logger); - - var projectContext = new FolderProjectContext(_logger) - { - PackageExtractionContext = new PackageExtractionContext( - PackageSaveMode.Defaultv2, - PackageExtractionBehavior.XmlDocFileSaveMode, - clientPolicyContext, - _logger) - }; - - var resolutionContext = new ResolutionContext( - DependencyBehavior.Lowest, - includePrerelease, - includeUnlisted: false, - VersionConstraints.None); - - var downloadContext = new PackageDownloadContext( - resolutionContext.SourceCacheContext, - packageFolder, - resolutionContext.SourceCacheContext.DirectDownload); - - // We are waiting here instead of await as await actually doesn't seem to work correctly. - packageManager.InstallPackageAsync( - project, - package.Identity, - resolutionContext, - projectContext, - downloadContext, - sourceRepo, - new List(), - CancellationToken.None).Wait(); - - if (onlyDownload) - { - var versionFolder = Path.Combine(packageFolder, package.Identity.ToString()); - - return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); - } - - return await project.GetPluginAssemblyFilesAsync(); + return await DownloadAsync(package, sourceRepo, packageFolder, onlyDownload); } public async Task DownloadAsync(IPackageSearchMetadata packageIdentity, SourceRepository repository, @@ -194,9 +135,14 @@ await packageManager.InstallPackageAsync( new List(), CancellationToken.None); - var versionFolder = Path.Combine(downloadFolder, packageIdentity.Identity.ToString()); + if (onlyDownload) + { + var versionFolder = Path.Combine(downloadFolder, packageIdentity.Identity.ToString()); - return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); + return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); + } + + return await project.GetPluginAssemblyFilesAsync(); } private static List> GetNugetResourceProviders() @@ -252,19 +198,40 @@ private static SourceRepository GetSourceRepo(NuGetFeed packageFeed, List GetPackages(string searchTerm, + int maxResults, bool includePrerelease, IEnumerable repositories) + { foreach (var repository in repositories) { - var packageSearchResource = await repository.GetResourceAsync(); + PackageSearchResource packageSearchResource; + + try + { + packageSearchResource = await repository.GetResourceAsync(); + } + catch (FatalProtocolException ex) + { + _logger.LogError($"Failed to download package search resource: {ex}"); + continue; + } SearchFilter searchFilter; if (includePrerelease) { - searchFilter = new SearchFilter(includePrerelease, SearchFilterType.IsAbsoluteLatestVersion); + searchFilter = new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion); } else { - searchFilter = new SearchFilter(includePrerelease); + searchFilter = new SearchFilter(false); } var items = await packageSearchResource.SearchAsync(searchTerm, searchFilter, 0, maxResults, _logger, CancellationToken.None); @@ -276,28 +243,19 @@ private static SourceRepository GetSourceRepo(NuGetFeed packageFeed, List> SearchPackagesAsync(NuGetFeed packageFeed, + public async IAsyncEnumerable<(SourceRepository Repository, IPackageSearchMetadata Package)> SearchPackagesAsync(NuGetFeed packageFeed, string searchTerm, int maxResults = 128, bool includePrerelease = false) { var providers = GetNugetResourceProviders(); var sourceRepo = GetSourceRepo(packageFeed, providers); - var packageSearchResource = await sourceRepo.GetResourceAsync(); - SearchFilter searchFilter; + var packages = GetPackages(searchTerm, maxResults, includePrerelease, new List { sourceRepo }); - if (includePrerelease) + await foreach (var package in packages) { - searchFilter = new SearchFilter(includePrerelease, SearchFilterType.IsAbsoluteLatestVersion); + yield return package; } - else - { - searchFilter = new SearchFilter(includePrerelease); - } - - var packages = await packageSearchResource.SearchAsync(searchTerm, searchFilter, 0, maxResults, _logger, CancellationToken.None); - - return packages.Select(x => (sourceRepo, x)); } private async Task SearchPackageAsync(string packageName, string version, bool includePrerelease,