From f30e12d74b58d6108441450cd825690b1cada2b9 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Fri, 12 Jun 2020 18:43:48 +0200 Subject: [PATCH 01/40] Add support for overriding nuspec repository url and version before pushing nupkg to GPR. Fixes #48 Update error message. Remove dead code because there are no "xmlns" attributes to remove when instantiating a new XElement. Rename file. Refactor: Owner property is no longer required. Address feedback regarding --repository option that should be in the following format: owner/repository. This makes it trivial to change repository url in a github actions scenario: gpr push your.nupkg --repository ${{ github.repository }} -k ${{ secrets.GITHUB_TOKEN }}. I have added additional test cases that verifies that we only rewrite nuspec if repository url, repository type or version has been changed. /cc @jcansdale Bugfix: Dispose nuspec context property. Only attempt to parse version if repository option has been set. Bugfix: Remove "_gpr.nupkg" prefix if nupkg is rewritten before uploading nupkg to GPR. Rename class to XElementExtensions. --- src/GprTool/GprTool.csproj | 1 + src/GprTool/IoExtensions.cs | 22 +++ src/GprTool/NuGetUtilities.cs | 117 +++++++++++++ src/GprTool/Program.cs | 58 ++++++- src/GprTool/XElementExtensions.cs | 67 ++++++++ test/GprTool.Tests/NuGetUtilitiesTests.cs | 197 ++++++++++++++++++++++ 6 files changed, 458 insertions(+), 4 deletions(-) create mode 100644 src/GprTool/IoExtensions.cs create mode 100644 src/GprTool/XElementExtensions.cs diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index a48e62d..051de24 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -25,6 +25,7 @@ + diff --git a/src/GprTool/IoExtensions.cs b/src/GprTool/IoExtensions.cs new file mode 100644 index 0000000..42a302c --- /dev/null +++ b/src/GprTool/IoExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace GprTool +{ + public static class IoExtensions + { + public static FileStream OpenReadShared(this string filename) + { + return File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + public static MemoryStream ReadSharedToStream(this string filename) + { + using var fileStream = filename.OpenReadShared(); + var outputStream = new MemoryStream(); + fileStream.CopyTo(outputStream); + outputStream.Seek(0, SeekOrigin.Begin); + return outputStream; + } + } +} diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 9c1807c..51f3f1c 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -1,11 +1,111 @@ using System; using System.IO; +using System.IO.Compression; +using System.Linq; using System.Xml; +using System.Xml.Linq; +using NuGet.Packaging; +using NuGet.Versioning; namespace GprTool { public class NuGetUtilities { + public static bool ShouldRewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) + { + if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); + if (repositoryUrl == null) throw new ArgumentNullException(nameof(repositoryUrl)); + + using var packageArchiveReader = new PackageArchiveReader(nupkgPath.OpenReadShared(), false); + + var nuspecXDocument = packageArchiveReader.NuspecReader.Xml; + var packageXElement = nuspecXDocument.Single("package"); + var metadataXElement = packageXElement.Single("metadata"); + var versionXElement = metadataXElement.Single("version"); + + if (!NuGetVersion.TryParse(versionXElement.Value, out var nuspecVersion) + || nuGetVersion != null && !nuGetVersion.Equals(nuspecVersion)) + { + return true; + } + + var repositoryXElement = metadataXElement.SingleOrDefault("repository"); + if (repositoryXElement == null) + { + return true; + } + + var nuspecRepositoryUrl = repositoryXElement.Attribute("url")?.Value; + var nuspecRepositoryType = repositoryXElement.Attribute("type")?.Value; + + return !string.Equals(repositoryUrl, nuspecRepositoryUrl, StringComparison.Ordinal) + || !string.Equals("git", nuspecRepositoryType, StringComparison.Ordinal); + } + + public static string RewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) + { + if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); + if (repositoryUrl == null) throw new ArgumentNullException(nameof(repositoryUrl)); + + var randomDirectoryId = Guid.NewGuid().ToString("N"); + var nupkgFilename = Path.GetFileName(nupkgPath); + var nupkgFilenameWithoutExt = Path.GetFileNameWithoutExtension(nupkgFilename); + var nupkgWorkingDirectoryAbsolutePath = Path.GetDirectoryName(nupkgPath); + var workingDirectory = Path.Combine(nupkgWorkingDirectoryAbsolutePath, $"{nupkgFilenameWithoutExt}_{randomDirectoryId}"); + + using var tmpDirectory = new DisposableDirectory(workingDirectory); + using var packageArchiveReader = new PackageArchiveReader(nupkgPath.ReadSharedToStream(), false); + using var nuspecMemoryStream = new MemoryStream(); + + var nuspecXDocument = packageArchiveReader.NuspecReader.Xml; + var packageXElement = nuspecXDocument.Single("package"); + var metadataXElement = packageXElement.Single("metadata"); + var packageId = packageXElement.Single("id").Value; + var versionXElement = metadataXElement.Single("version"); + + if (nuGetVersion != null) + { + versionXElement.SetValue(nuGetVersion); + } + else + { + nuGetVersion = NuGetVersion.Parse(versionXElement.Value); + } + + var repositoryXElement = metadataXElement.SingleOrDefault("repository"); + if (repositoryXElement == null) + { + repositoryXElement = new XElement("repository"); + repositoryXElement.SetAttributeValue("url", repositoryUrl); + repositoryXElement.SetAttributeValue("type", "git"); + metadataXElement.Add(repositoryXElement); + } + else + { + repositoryXElement.SetAttributeValue("url", repositoryUrl); + repositoryXElement.SetAttributeValue("type", "git"); + } + + nuspecXDocument.Save(nuspecMemoryStream); + nuspecMemoryStream.Seek(0, SeekOrigin.Begin); + + ZipFile.ExtractToDirectory(nupkgPath, tmpDirectory.WorkingDirectory, true); + + var nuspecDstFilename = Path.Combine(tmpDirectory.WorkingDirectory, $"{packageId}.nuspec"); + File.WriteAllBytes(nuspecDstFilename, nuspecMemoryStream.ToArray()); + + using var outputStream = new MemoryStream(); + + var packageBuilder = new PackageBuilder(nuspecMemoryStream, tmpDirectory.WorkingDirectory, propertyProvider => throw new NotImplementedException()); + packageBuilder.Save(outputStream); + + var nupkgDstFilenameAbsolutePath = Path.Combine(nupkgWorkingDirectoryAbsolutePath, $"{packageId}.{nuGetVersion}_gpr.nupkg"); + + File.WriteAllBytes(nupkgDstFilenameAbsolutePath, outputStream.ToArray()); + + return nupkgDstFilenameAbsolutePath; + } + public static string FindTokenInNuGetConfig(Action warning = null) { var configFile = GetDefaultConfigFile(warning); @@ -97,5 +197,22 @@ public static string GetDefaultConfigFile(Action warning = null) return Path.Combine(baseDir, "NuGet", "NuGet.Config"); } + + } + + public class DisposableDirectory : IDisposable + { + public string WorkingDirectory { get; } + + public DisposableDirectory(string workingDirectory) + { + WorkingDirectory = workingDirectory; + Directory.CreateDirectory(workingDirectory); + } + + public void Dispose() + { + Directory.Delete(WorkingDirectory, true); + } } } diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 7416c34..8d62374 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using System.Collections.Generic; using McMaster.Extensions.CommandLineUtils; +using NuGet.Packaging; +using NuGet.Versioning; using RestSharp; using RestSharp.Authenticators; using Octokit.GraphQL; @@ -320,12 +322,57 @@ public class PushCommand : GprCommandBase { protected override Task OnExecute(CommandLineApplication app) { + var owner = "GPR-TOOL-DEFAULT-OWNER"; + string rewrittenPackageFile = null; + + if (Repository != null) + { + NuGetVersion nuGetVersion = null; + if (Version != null && !NuGetVersion.TryParse(Version, out nuGetVersion)) + { + Console.WriteLine("Unable to parse version"); + return Task.CompletedTask; + } + + var ownerAndRepositoryName = Repository + .Replace("\\", "/") + .Split("/", StringSplitOptions.RemoveEmptyEntries) + .Take(2) + .ToList(); + + if (ownerAndRepositoryName.Count != 2) + { + Console.WriteLine( + "Invalid repository value. Please use the following format: owner/repository. E.g: jcansdale/gpr"); + return Task.CompletedTask; + } + + owner = ownerAndRepositoryName[0]; + var repositoryName = ownerAndRepositoryName[1]; + var repositoryUrl = $"https://github.com/{owner}/{repositoryName}"; + + if (NuGetUtilities.ShouldRewriteNupkg(PackageFile, repositoryUrl, nuGetVersion)) + { + rewrittenPackageFile = NuGetUtilities.RewriteNupkg(PackageFile, repositoryUrl, nuGetVersion); + } + } + var user = "GprTool"; var token = GetAccessToken(); - var client = new RestClient($"https://nuget.pkg.github.com/{Owner}/"); + var client = new RestClient($"https://nuget.pkg.github.com/{owner}/"); client.Authenticator = new HttpBasicAuthenticator(user, token); var request = new RestRequest(Method.PUT); - request.AddFile("package", PackageFile); + if (rewrittenPackageFile != null) + { + using var packageStream = rewrittenPackageFile.ReadSharedToStream(); + + rewrittenPackageFile = rewrittenPackageFile.Replace("_gpr.nupkg", ".nupkg", StringComparison.OrdinalIgnoreCase); + request.AddFile("package", packageStream.ToArray(), Path.GetFileName(rewrittenPackageFile)); + } + else + { + request.AddFile("package", PackageFile); + } var response = client.Execute(request); if (response.StatusCode == HttpStatusCode.OK) @@ -353,8 +400,11 @@ protected override Task OnExecute(CommandLineApplication app) [Argument(0, Description = "Path to the package file")] public string PackageFile { get; set; } - [Option("--owner", Description = "The owner if repository URL wasn't specified in nupkg/nuspec")] - public string Owner { get; } = "GPR-TOOL-DEFAULT-OWNER"; + [Option("--repository", Description = "Override current nupkg repository url. Format: owner/repository. E.g: jcansdale/gpr")] + public string Repository { get; set; } + + [Option("--version", Description = "Override current nupkg version")] + public string Version { get; set; } } [Command(Description = "View package details")] diff --git a/src/GprTool/XElementExtensions.cs b/src/GprTool/XElementExtensions.cs new file mode 100644 index 0000000..123d636 --- /dev/null +++ b/src/GprTool/XElementExtensions.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Linq; + +namespace GprTool +{ + public static class XElementExtensions + { + public static XElement Single([NotNull] this XDocument xDocument, [NotNull] XName name, bool ignoreCase = true, bool ignoreNamespace = true) + { + if (xDocument == null) throw new ArgumentNullException(nameof(xDocument)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return xDocument.Descendants().SingleOrDefault(name, ignoreCase, ignoreNamespace, true); + } + + public static XElement Single([NotNull] this XElement xElement, [NotNull] XName name, bool ignoreCase = true, bool ignoreNamespace = true) + { + if (xElement == null) throw new ArgumentNullException(nameof(xElement)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return xElement.Descendants().SingleOrDefault(name, ignoreCase, ignoreNamespace, true); + } + + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public static XElement SingleOrDefault([NotNull] this XDocument xDocument, [NotNull] XName name, bool ignoreCase = true, bool ignoreNamespace = true) + { + if (xDocument == null) throw new ArgumentNullException(nameof(xDocument)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return xDocument.Descendants().SingleOrDefault(name, ignoreCase, ignoreNamespace); + } + + public static XElement SingleOrDefault([NotNull] this XElement xElement, [NotNull] XName name, bool ignoreCase = true, bool ignoreNamespace = true) + { + if (xElement == null) throw new ArgumentNullException(nameof(xElement)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return xElement.Descendants().SingleOrDefault(name, ignoreCase, ignoreNamespace); + } + + public static XElement SingleOrDefault([NotNull] this IEnumerable xElements, [NotNull] XName name, bool ignoreCase = true, bool ignoreNamespace = true, bool throwifNotFound = false) + { + if (xElements == null) throw new ArgumentNullException(nameof(xElements)); + if (name == null) throw new ArgumentNullException(nameof(name)); + foreach (var node in xElements) + { + var comperator = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + if (!string.Equals(node.Name.LocalName, name.LocalName, comperator)) + { + continue; + } + + if (!ignoreNamespace && !string.Equals(node.Name.NamespaceName, name.NamespaceName, comperator)) + { + continue; + } + + return node; + } + + if (throwifNotFound) + { + throw new Exception($"The required element '{name}' is missing"); + } + + return null; + } + } +} diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index e412846..ce9a6ed 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -1,11 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Text; using System.Xml; using NUnit.Framework; using GprTool; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Versioning; +[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] public class NuGetUtilitiesTests { public class TheSetApiKeyMethod { + public string TmpDirectoryPath => Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N")); + + const string NuspecXml = @" + + + test + 1.0.0 + abc123 + abc123 + + + + + + +"; + [Test] public void AddClearTextPassword() { @@ -111,5 +137,176 @@ public void UpdatePackageSourceCredentials() var packageSourceCredentialsElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials/*"); Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); } + + [TestCase("1.0.0", "1.0.0", false)] + [TestCase("1.0.0", "1.0.0", false)] + [TestCase("1.0.0", "1.0.1", true)] + public void ShouldRewriteNupkg_Version(string currentVersion, string updatedVersion, bool shouldUpdateVersion) + { + const string repositoryUrl = "https://github.com/jcansdale/gpr"; + + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + { + manifest.Metadata.Version = new NuGetVersion(currentVersion); + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = repositoryUrl, + Type = "git" + }; + })); + + packageBuilderContext.Build(); + + Assert.That( + NuGetUtilities.ShouldRewriteNupkg( + packageBuilderContext.NupkgFilename, + repositoryUrl, NuGetVersion.Parse(updatedVersion)), Is.EqualTo(shouldUpdateVersion)); + } + + [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git", false, Description = "Equals")] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo.git", true, Description = "Url ends with .git")] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/REPO", true, Description = "Case insensitive")] + [TestCase(null, "https://github.com/owner/repo.git", true)] + [TestCase("https://google.com", "https://github.com/owner/repo.git", true)] + public void ShouldRewriteNupkg_RepositoryUrl(string currentRepositoryUrl, string updatedRepositoryUrl, bool shouldUpdateRepositoryUrl) + { + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + { + if (currentRepositoryUrl == null) + { + manifest.Metadata.Repository = null; + return; + } + + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = currentRepositoryUrl, + Type = "git" + }; + })); + + packageBuilderContext.Build(); + + Assert.That( + NuGetUtilities.ShouldRewriteNupkg( + packageBuilderContext.NupkgFilename, + updatedRepositoryUrl), Is.EqualTo(shouldUpdateRepositoryUrl)); + } + + [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git")] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo")] + public void RewriteNuspec(string repositoryUrl, string expectedRepositoryUrl) + { + using var originalPackageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( + manifest => + { + manifest.Metadata.Repository = null; + })); + originalPackageBuilderContext.Build(); + + var rewrittenNupkgAbsolutePath = NuGetUtilities.RewriteNupkg(originalPackageBuilderContext.NupkgFilename, + repositoryUrl, NuGetVersion.Parse("2.0.0")); + using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(rewrittenNupkgAbsolutePath)); + + var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); + var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); + + Assert.That(rewrittenNupkgPackageIdentity, Is.Not.Null); + Assert.That(rewrittenNupkgPackageIdentity.Id, Is.EqualTo("test")); + Assert.That(rewrittenNupkgPackageIdentity.Version, Is.EqualTo(NuGetVersion.Parse("2.0.0"))); + Assert.That(rewrittenNupkgRepositoryMetadata, Is.Not.Null); + Assert.That(rewrittenNupkgRepositoryMetadata.Url, Is.EqualTo(expectedRepositoryUrl)); + Assert.That(rewrittenNupkgRepositoryMetadata.Type, Is.EqualTo("git")); + } + + [Test] + public void RewriteNuspec_Overwrites_Existing_Repository_Url() + { + using var originalPackageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( + manifest => + { + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = "https://google.com", + Type = "google" + }; + })); + originalPackageBuilderContext.Build(); + + var rewrittenNupkgAbsolutePath = NuGetUtilities.RewriteNupkg(originalPackageBuilderContext.NupkgFilename, + "https://github.com/owner/repo", NuGetVersion.Parse("2.0.0")); + using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(rewrittenNupkgAbsolutePath)); + + var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); + var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); + + Assert.That(rewrittenNupkgPackageIdentity, Is.Not.Null); + Assert.That(rewrittenNupkgPackageIdentity.Id, Is.EqualTo("test")); + Assert.That(rewrittenNupkgPackageIdentity.Version, Is.EqualTo(NuGetVersion.Parse("2.0.0"))); + Assert.That(rewrittenNupkgRepositoryMetadata, Is.Not.Null); + Assert.That(rewrittenNupkgRepositoryMetadata.Url, Is.EqualTo("https://github.com/owner/repo")); + Assert.That(rewrittenNupkgRepositoryMetadata.Type, Is.EqualTo("git")); + } + + [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] + class NuspecContext : IDisposable + { + public Manifest Manifest { get; } + public MemoryStream ManifestStream { get; } + + public NuspecContext(Action manifestBuilder = null) + { + using var nuspecMemoryStream = new MemoryStream(Encoding.UTF8.GetBytes(NuspecXml)); + Manifest = Manifest.ReadFrom(nuspecMemoryStream, true); + manifestBuilder?.Invoke(Manifest); + ManifestStream = new MemoryStream(); + Manifest.Save(ManifestStream, true); + ManifestStream.Seek(0, SeekOrigin.Begin); + } + + public void Dispose() + { + ManifestStream?.Dispose(); + } + } + + class PackageBuilderContext : IDisposable + { + readonly DisposableDirectory _disposableDirectory; + + public string WorkingDirectory => _disposableDirectory.WorkingDirectory; + public string Filename { get; } + public string NupkgFilename => Path.Combine(WorkingDirectory, Filename); + public NuspecContext NuspecContext { get; } + + public PackageBuilderContext(string workingDirectory, NuspecContext nuspecContext) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (nuspecContext == null) throw new ArgumentNullException(nameof(nuspecContext)); + _disposableDirectory = new DisposableDirectory(workingDirectory); + + Filename = $"{nuspecContext.Manifest.Metadata.Id}.{nuspecContext.Manifest.Metadata.Version}.nupkg"; + NuspecContext = nuspecContext; + } + + public void Build(Action builder = null) + { + using var packageBuilderOutputStream = new MemoryStream(); + + var nupkgPath = Path.Combine(WorkingDirectory, Filename); + var packageBuilder = new PackageBuilder(NuspecContext.ManifestStream, WorkingDirectory, s => throw new NotImplementedException()); + + builder?.Invoke(packageBuilder); + + packageBuilder.Save(packageBuilderOutputStream); + + File.WriteAllBytes(nupkgPath, packageBuilderOutputStream.ToArray()); + } + + public void Dispose() + { + NuspecContext.Dispose(); + } + } } } From 3d5de9a74b8939819d49b04aa744fc32a77cc5e2 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Sat, 13 Jun 2020 13:54:47 +0200 Subject: [PATCH 02/40] Add support for wildcards when pushing. Fixes #30 --- src/GprTool/GlobExtensions.cs | 68 ++++++++++++++++++++++++ src/GprTool/GprTool.csproj | 1 + src/GprTool/Program.cs | 33 +++++++++++- test/GprTool.Tests/GlobExtensionTests.cs | 29 ++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/GprTool/GlobExtensions.cs create mode 100644 test/GprTool.Tests/GlobExtensionTests.cs diff --git a/src/GprTool/GlobExtensions.cs b/src/GprTool/GlobExtensions.cs new file mode 100644 index 0000000..87b11fa --- /dev/null +++ b/src/GprTool/GlobExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using DotNet.Globbing; +using DotNet.Globbing.Token; + +namespace GprTool +{ + public static class GlobExtensions + { + public static bool IsGlobPattern(this Glob glob) + { + return glob.Tokens.Any(x => !(x is PathSeparatorToken || x is LiteralToken)); + } + + public static string BuildBasePathFromGlob(this Glob glob, string fallbackPath = null) + { + if (glob == null) throw new ArgumentNullException(nameof(glob)); + + var tokensLength = glob.Tokens.Length; + + var path = new StringBuilder(); + + for (var index = 0; index < tokensLength; index++) + { + var token = glob.Tokens[index]; + var tokenNext = index + 1 < tokensLength ? glob.Tokens[index + 1] : null; + var tokenPrevious = index - 1 >= 0 ? glob.Tokens[index - 1] : null; + var tokenPreviousPrevious = index - 2 >= 0 ? glob.Tokens[index - 2] : null; + + switch (token) + { + case PathSeparatorToken pathSeparatorToken: + path.Append(pathSeparatorToken.Value); + break; + case LiteralToken literalToken: + + if (tokenPrevious is WildcardToken + || tokenPreviousPrevious is WildcardDirectoryToken) + { + goto done; + } + + path.Append(literalToken.Value); + + if (tokenNext is WildcardToken + || tokenNext is WildcardDirectoryToken) + { + goto done; + } + + break; + } + } + + done: + + var pathStr = path.ToString(); + if (fallbackPath != null && string.IsNullOrWhiteSpace(pathStr)) + { + return fallbackPath; + } + + return pathStr; + } + } +} diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index a48e62d..6ed399d 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -22,6 +22,7 @@ + diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 7416c34..ae2b781 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -5,6 +5,11 @@ using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; +using System.Text; +using DotNet.Globbing; +using DotNet.Globbing.Evaluation; +using DotNet.Globbing.Generation; +using DotNet.Globbing.Token; using McMaster.Extensions.CommandLineUtils; using RestSharp; using RestSharp.Authenticators; @@ -320,12 +325,38 @@ public class PushCommand : GprCommandBase { protected override Task OnExecute(CommandLineApplication app) { + var glob = Glob.Parse(PackageFile); + var isGlobPattern = glob.IsGlobPattern(); + var user = "GprTool"; var token = GetAccessToken(); var client = new RestClient($"https://nuget.pkg.github.com/{Owner}/"); client.Authenticator = new HttpBasicAuthenticator(user, token); var request = new RestRequest(Method.PUT); - request.AddFile("package", PackageFile); + + if (isGlobPattern) + { + var fallbackBaseDirectory = Directory.GetCurrentDirectory(); + var baseDirectory = glob.BuildBasePathFromGlob(fallbackBaseDirectory); + foreach (var nupkgFilename in Directory.GetFiles(baseDirectory, "*.nupkg", SearchOption.AllDirectories)) + { + if (glob.IsMatch(nupkgFilename)) + { + request.AddFile("package", nupkgFilename); + } + } + + if (!request.Files.Any()) + { + Console.WriteLine($"Unable to find any nupkgs in directory {baseDirectory} matching glob pattern: {glob}"); + return Task.CompletedTask; + } + } + else + { + request.AddFile("package", PackageFile); + } + var response = client.Execute(request); if (response.StatusCode == HttpStatusCode.OK) diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs new file mode 100644 index 0000000..570d2d4 --- /dev/null +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -0,0 +1,29 @@ +using DotNet.Globbing; +using GprTool; +using NUnit.Framework; + +[TestFixture] +class GlobExtensionTests +{ + [TestCase("c:\\test.nupkg", false)] + [TestCase("c:\\test", false)] + [TestCase("c:\\test\\**", true)] + [TestCase("c:\\test\\**\\*.nupkg", true)] + public void IsGlobPattern(string path, bool isGlobPattern) + { + var glob = Glob.Parse(path); + Assert.That(glob.IsGlobPattern(), Is.EqualTo(isGlobPattern)); + } + + [TestCase("c:\\test.nupkg", "c:\\test.nupkg")] + [TestCase("c:\\test", "c:\\test")] + [TestCase("c:\\test\\**", "c:\\test")] + [TestCase("c:\\test\\**\\*.nupkg", "c:\\test")] + [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\test\\subdirectory")] + [TestCase("c:\\test\\**\\subdirectory\\**\\*.nupkg", "c:\\test")] + public void BuildBasePathFromGlob(string path, string expectedBaseDirectory) + { + var glob = Glob.Parse(path); + Assert.That(glob.BuildBasePathFromGlob(), Is.EqualTo(expectedBaseDirectory)); + } +} \ No newline at end of file From 1f7699f269ec440cd62bf5fcd67d6b5b66ca318f Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 09:56:27 +0200 Subject: [PATCH 03/40] Add additional test cases. Ref. https://github.com/jcansdale/gpr/pull/55#discussion_r439743961 --- test/GprTool.Tests/GlobExtensionTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index 570d2d4..5db0e14 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -8,6 +8,8 @@ class GlobExtensionTests [TestCase("c:\\test.nupkg", false)] [TestCase("c:\\test", false)] [TestCase("c:\\test\\**", true)] + [TestCase("c:\\test?\\**", true)] + [TestCase("c:\\test?\\[abc]\\**", true)] [TestCase("c:\\test\\**\\*.nupkg", true)] public void IsGlobPattern(string path, bool isGlobPattern) { @@ -17,6 +19,8 @@ public void IsGlobPattern(string path, bool isGlobPattern) [TestCase("c:\\test.nupkg", "c:\\test.nupkg")] [TestCase("c:\\test", "c:\\test")] + [TestCase("c:\\test?\\**", "c:\\test")] + [TestCase("c:\\test?\\[abc]\\**", "c:\\test")] [TestCase("c:\\test\\**", "c:\\test")] [TestCase("c:\\test\\**\\*.nupkg", "c:\\test")] [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\test\\subdirectory")] From 78258d691d902f7c47cf1cb7da08428950380aed Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 10:27:46 +0200 Subject: [PATCH 04/40] Add support for *.snupkg (symbol packages) and parallelization allowing the user to upload multiple packages simultaneously. Concurrency may be controlled via the optional "--concurrency" command. --- src/GprTool/GlobExtensions.cs | 1 - src/GprTool/Program.cs | 121 +++++++++++++++++++++++----------- 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/GprTool/GlobExtensions.cs b/src/GprTool/GlobExtensions.cs index 87b11fa..a9fcade 100644 --- a/src/GprTool/GlobExtensions.cs +++ b/src/GprTool/GlobExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using System.Text; using DotNet.Globbing; diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index ae2b781..7d5c636 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Text; +using System.Threading; using DotNet.Globbing; using DotNet.Globbing.Evaluation; using DotNet.Globbing.Generation; @@ -323,69 +324,111 @@ class PackageInfo [Command(Description = "Publish a package")] public class PushCommand : GprCommandBase { - protected override Task OnExecute(CommandLineApplication app) + protected override async Task OnExecute(CommandLineApplication app) { var glob = Glob.Parse(PackageFile); var isGlobPattern = glob.IsGlobPattern(); - var user = "GprTool"; - var token = GetAccessToken(); - var client = new RestClient($"https://nuget.pkg.github.com/{Owner}/"); - client.Authenticator = new HttpBasicAuthenticator(user, token); - var request = new RestRequest(Method.PUT); - + var packages = new List(); if (isGlobPattern) { var fallbackBaseDirectory = Directory.GetCurrentDirectory(); var baseDirectory = glob.BuildBasePathFromGlob(fallbackBaseDirectory); - foreach (var nupkgFilename in Directory.GetFiles(baseDirectory, "*.nupkg", SearchOption.AllDirectories)) - { - if (glob.IsMatch(nupkgFilename)) - { - request.AddFile("package", nupkgFilename); - } - } - - if (!request.Files.Any()) + packages.AddRange(Directory + .GetFiles(baseDirectory, "*.*", SearchOption.AllDirectories) + .Where(x => + x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) + || x.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) + .Where(filename => glob.IsMatch(filename))); + + if (!packages.Any()) { - Console.WriteLine($"Unable to find any nupkgs in directory {baseDirectory} matching glob pattern: {glob}"); - return Task.CompletedTask; + Console.WriteLine($"Unable to find any packages in directory {baseDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); + return; } } else { - request.AddFile("package", PackageFile); - } - - var response = client.Execute(request); + if (!File.Exists(PackageFile)) + { + Console.WriteLine($"Package file was not found: {PackageFile}"); + return; + } - if (response.StatusCode == HttpStatusCode.OK) - { - Console.WriteLine(response.Content); - return Task.CompletedTask; + packages.Add(PackageFile); } - var nugetWarning = response.Headers.FirstOrDefault(h => - h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); - if (nugetWarning != null) + const string user = "GprTool"; + var token = GetAccessToken(); + var client = new RestClient($"https://nuget.pkg.github.com/{Owner}/") { - Console.WriteLine(nugetWarning.Value); - return Task.CompletedTask; - } + Authenticator = new HttpBasicAuthenticator(user, token) + }; - Console.WriteLine(response.StatusDescription); - foreach (var header in response.Headers) + using var concurrencySemaphore = new SemaphoreSlim(Math.Max(1, Concurrency)); + + Console.WriteLine($"Uploading {packages.Count} packages."); + + var uploadPackageTasks = packages.Select(packageFilePath => { - Console.WriteLine($"{header.Name}: {header.Value}"); - } - return Task.CompletedTask; + var packageFilename = Path.GetFileName(packageFilePath); + + return Task.Run(async () => + { + try + { + await UploadPackageAsync(); + } + finally + { + concurrencySemaphore.Dispose(); + } + }); + + async Task UploadPackageAsync() + { + await concurrencySemaphore.WaitAsync(); + + var request = new RestRequest(Method.PUT); + request.AddFile("package", packageFilePath); + + var response = await client.ExecuteAsync(request); + + Console.WriteLine($"[{packageFilename}]: Uploading package."); + + if (response.StatusCode == HttpStatusCode.OK) + { + Console.WriteLine($"[{packageFilename}]: {response.Content}"); + return; + } + + var nugetWarning = response.Headers.FirstOrDefault(h => + h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); + if (nugetWarning != null) + { + Console.WriteLine($"[{packageFilename}]: {nugetWarning.Value}"); + return; + } + + Console.WriteLine($"[{packageFilename}]: {response.StatusDescription}"); + foreach (var header in response.Headers) + { + Console.WriteLine($"[{packageFilename}]: {header.Name}: {header.Value}"); + } + } + }); + + await Task.WhenAll(uploadPackageTasks); } - [Argument(0, Description = "Path to the package file")] + [Argument(0, Description = "Path to the package file. You can optionally use a glob pattern if you want to upload multiple packages.")] public string PackageFile { get; set; } - [Option("--owner", Description = "The owner if repository URL wasn't specified in nupkg/nuspec")] + [Option("--owner", Description = "The owner if repository URL wasn't specified in nupkg/nuspec.")] public string Owner { get; } = "GPR-TOOL-DEFAULT-OWNER"; + + [Option("--concurrency", Description = "The number of packages to upload simultaneously. Default value is 4.")] + public int Concurrency { get; set; } = 4; } [Command(Description = "View package details")] From 5874ae5a3c0bda7bfaf17b576728487ee3fc1bb4 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 10:28:35 +0200 Subject: [PATCH 05/40] Add additional test cases. --- test/GprTool.Tests/GlobExtensionTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index 5db0e14..f1d2adb 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -11,6 +11,7 @@ class GlobExtensionTests [TestCase("c:\\test?\\**", true)] [TestCase("c:\\test?\\[abc]\\**", true)] [TestCase("c:\\test\\**\\*.nupkg", true)] + [TestCase("c:\\test\\*.*", true)] public void IsGlobPattern(string path, bool isGlobPattern) { var glob = Glob.Parse(path); @@ -19,6 +20,7 @@ public void IsGlobPattern(string path, bool isGlobPattern) [TestCase("c:\\test.nupkg", "c:\\test.nupkg")] [TestCase("c:\\test", "c:\\test")] + [TestCase("c:\\test\\*.*", "c:\\test")] [TestCase("c:\\test?\\**", "c:\\test")] [TestCase("c:\\test?\\[abc]\\**", "c:\\test")] [TestCase("c:\\test\\**", "c:\\test")] From 881033243a9bb8a7416d8b98eb592bebf953d844 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 10:34:12 +0200 Subject: [PATCH 06/40] Ignore repository type when determining if we should rewrite nupkg as it's determined to be irrelevant (has no effect in the package upload process). --- src/GprTool/NuGetUtilities.cs | 8 ++------ test/GprTool.Tests/NuGetUtilitiesTests.cs | 25 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 51f3f1c..ee2b7c9 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.IO.Compression; -using System.Linq; using System.Xml; using System.Xml.Linq; using NuGet.Packaging; @@ -14,7 +13,6 @@ public class NuGetUtilities public static bool ShouldRewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) { if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); - if (repositoryUrl == null) throw new ArgumentNullException(nameof(repositoryUrl)); using var packageArchiveReader = new PackageArchiveReader(nupkgPath.OpenReadShared(), false); @@ -36,10 +34,8 @@ public static bool ShouldRewriteNupkg(string nupkgPath, string repositoryUrl, Nu } var nuspecRepositoryUrl = repositoryXElement.Attribute("url")?.Value; - var nuspecRepositoryType = repositoryXElement.Attribute("type")?.Value; - - return !string.Equals(repositoryUrl, nuspecRepositoryUrl, StringComparison.Ordinal) - || !string.Equals("git", nuspecRepositoryType, StringComparison.Ordinal); + + return !string.Equals(repositoryUrl, nuspecRepositoryUrl, StringComparison.Ordinal); } public static string RewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index ce9a6ed..ab8b5ee 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -138,7 +138,6 @@ public void UpdatePackageSourceCredentials() Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); } - [TestCase("1.0.0", "1.0.0", false)] [TestCase("1.0.0", "1.0.0", false)] [TestCase("1.0.0", "1.0.1", true)] public void ShouldRewriteNupkg_Version(string currentVersion, string updatedVersion, bool shouldUpdateVersion) @@ -193,6 +192,30 @@ public void ShouldRewriteNupkg_RepositoryUrl(string currentRepositoryUrl, string updatedRepositoryUrl), Is.EqualTo(shouldUpdateRepositoryUrl)); } + [TestCase(null)] + [TestCase("randomvalue")] + [TestCase("git")] + public void ShouldRewriteNupkg_Ignores_RepositoryType(string repositoryType) + { + const string currentRepositoryUrl = "https://github.com/jcansdale/gpr"; + + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + { + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = currentRepositoryUrl, + Type = repositoryType + }; + })); + + packageBuilderContext.Build(); + + Assert.That( + NuGetUtilities.ShouldRewriteNupkg( + packageBuilderContext.NupkgFilename, + repositoryType), Is.EqualTo(true)); + } + [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git")] [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo")] public void RewriteNuspec(string repositoryUrl, string expectedRepositoryUrl) From 30e1dd823b926d0e9fc8b2f624341dc8c7ebb8a0 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 11:09:39 +0200 Subject: [PATCH 07/40] If --repository option is not supplied then read from nupkg metadata. If value is invalid then inform user that they may override repository url using the --repository option. --- src/GprTool/NuGetUtilities.cs | 7 ++++ src/GprTool/Program.cs | 39 ++++++++++++++++------- src/GprTool/StringExtensions.cs | 30 +++++++++++++++++ test/GprTool.Tests/NuGetUtilitiesTests.cs | 22 +++++++++++++ test/GprTool.Tests/StringExtensions.cs | 27 ++++++++++++++++ 5 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/GprTool/StringExtensions.cs create mode 100644 test/GprTool.Tests/StringExtensions.cs diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index ee2b7c9..9bb88ad 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -10,6 +10,13 @@ namespace GprTool { public class NuGetUtilities { + public static Manifest ReadNupkgManifest(string nupkgPath) + { + if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); + using var packageArchiveReader = new PackageArchiveReader(nupkgPath.ReadSharedToStream()); + return Manifest.ReadFrom(packageArchiveReader.GetNuspec(), false); + } + public static bool ShouldRewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) { if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 8d62374..9f4180c 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -322,7 +322,7 @@ public class PushCommand : GprCommandBase { protected override Task OnExecute(CommandLineApplication app) { - var owner = "GPR-TOOL-DEFAULT-OWNER"; + string owner = null; string rewrittenPackageFile = null; if (Repository != null) @@ -334,26 +334,41 @@ protected override Task OnExecute(CommandLineApplication app) return Task.CompletedTask; } - var ownerAndRepositoryName = Repository - .Replace("\\", "/") - .Split("/", StringSplitOptions.RemoveEmptyEntries) - .Take(2) - .ToList(); + var (githubOwner, githubRepositoryName, githubRepositoryUri) = Repository.BuildGithubRepositoryDetails(); - if (ownerAndRepositoryName.Count != 2) + if (githubOwner == null + || githubRepositoryName == null + || githubRepositoryUri == null + || githubRepositoryUri.Host != "github.com") { Console.WriteLine( "Invalid repository value. Please use the following format: owner/repository. E.g: jcansdale/gpr"); return Task.CompletedTask; } - owner = ownerAndRepositoryName[0]; - var repositoryName = ownerAndRepositoryName[1]; - var repositoryUrl = $"https://github.com/{owner}/{repositoryName}"; + owner = githubOwner; - if (NuGetUtilities.ShouldRewriteNupkg(PackageFile, repositoryUrl, nuGetVersion)) + if (NuGetUtilities.ShouldRewriteNupkg(PackageFile, githubRepositoryUri.ToString(), nuGetVersion)) { - rewrittenPackageFile = NuGetUtilities.RewriteNupkg(PackageFile, repositoryUrl, nuGetVersion); + rewrittenPackageFile = NuGetUtilities.RewriteNupkg(PackageFile, githubRepositoryUri.ToString(), nuGetVersion); + } + } + else + { + var manifest = NuGetUtilities.ReadNupkgManifest(PackageFile); + var manifestRepositoryUrl = manifest.Metadata.Repository?.Url; + + var (githubOwner, githubRepositoryName, githubRepositoryUri) = manifestRepositoryUrl.BuildGithubRepositoryDetails(); + + if (githubOwner == null + || githubRepositoryName == null + || githubRepositoryUri == null + || githubRepositoryUri.Host != "github.com") + { + Console.WriteLine($"Package is missing a valid XML element value: {manifestRepositoryUrl} " + + "Please use --repository option to set a valid upstream GitHub repository. " + + "Additional details are available at: https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#repositoryurl"); + return Task.CompletedTask; } } diff --git a/src/GprTool/StringExtensions.cs b/src/GprTool/StringExtensions.cs new file mode 100644 index 0000000..1f14e84 --- /dev/null +++ b/src/GprTool/StringExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; + +namespace GprTool +{ + public static class StringExtensions + { + public static (string owner, string repositoryName, Uri repositoryUri) BuildGithubRepositoryDetails(this string url) + { + if (url == null) + { + return default; + } + + if (!Uri.TryCreate(url, UriKind.Absolute, out var repositoryUri)) + { + return default; + } + + var ownerAndRepositoryName = repositoryUri.PathAndQuery + .Substring(1) + .Replace("\\", "/") + .Split("/", StringSplitOptions.RemoveEmptyEntries) + .Take(2) + .ToList(); + + return ownerAndRepositoryName.Count != 2 ? default : (ownerAndRepositoryName[0], ownerAndRepositoryName[1], uri: repositoryUri); + } + } +} diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index ab8b5ee..49bbdbc 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -138,6 +138,28 @@ public void UpdatePackageSourceCredentials() Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); } + [Test] + public void ReadNupkgManifest() + { + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + { + manifest.Metadata.Version = new NuGetVersion("1.0.0"); + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = "https://github.com/jcansdale/gpr", + Type = "git" + }; + })); + + packageBuilderContext.Build(); + + var manifest = NuGetUtilities.ReadNupkgManifest(packageBuilderContext.NupkgFilename); + Assert.That(manifest, Is.Not.Null); + Assert.That(manifest.Metadata.Version, Is.EqualTo(packageBuilderContext.NuspecContext.Manifest.Metadata.Version)); + Assert.That(manifest.Metadata.Repository, Is.Not.Null); + Assert.That(manifest.Metadata.Repository.Url, Is.EqualTo(packageBuilderContext.NuspecContext.Manifest.Metadata.Repository.Url)); + } + [TestCase("1.0.0", "1.0.0", false)] [TestCase("1.0.0", "1.0.1", true)] public void ShouldRewriteNupkg_Version(string currentVersion, string updatedVersion, bool shouldUpdateVersion) diff --git a/test/GprTool.Tests/StringExtensions.cs b/test/GprTool.Tests/StringExtensions.cs new file mode 100644 index 0000000..12e81e0 --- /dev/null +++ b/test/GprTool.Tests/StringExtensions.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; + +namespace GprTool.Tests +{ + [TestFixture] + public class StringExtensions + { + [TestCase(null, null, null, null)] + [TestCase("jcansdale/gpr", null, null, null)] + [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale///////gpr")] + public void BuildGithubRepositoryDetails(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) + { + var (githubOwner, githubRepositoryName, githubRepositoryUri) = repositoryUrl.BuildGithubRepositoryDetails(); + Assert.That(githubOwner, Is.EqualTo(expectedOwner)); + Assert.That(githubRepositoryName, Is.EqualTo(expectedRepositoryName)); + if (expectedGithubRepositoryUrl == null) + { + Assert.That(githubRepositoryUri, Is.Null); + return; + } + Assert.That(githubRepositoryUri, Is.Not.Null); + Assert.That(githubRepositoryUri.ToString(), Is.EqualTo(expectedGithubRepositoryUrl)); + } + } +} From 554e4593232f333947d1d50f79c5b94e04b12f87 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 15:07:08 +0200 Subject: [PATCH 08/40] Remove all whitespace tokens after reading access token. --- src/GprTool/Program.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index a8fc0e3..0770046 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -639,26 +639,26 @@ protected IConnection CreateConnection() public string GetAccessToken() { - if (AccessToken is string accessToken) + if (AccessToken is { } accessToken) { - return accessToken; + return accessToken.Trim(); } - if (NuGetUtilities.FindTokenInNuGetConfig(Warning) is string configToken) + if (NuGetUtilities.FindTokenInNuGetConfig(Warning) is { } configToken) { - return configToken; + return configToken.Trim(); } - if (FindReadPackagesToken() is string readToken) + if (FindReadPackagesToken() is { } readToken) { - return readToken; + return readToken.Trim(); } throw new ApplicationException("Couldn't find personal access token"); } static string FindReadPackagesToken() => - (Environment.GetEnvironmentVariable("READ_PACKAGES_TOKEN") is string token && token != string.Empty) ? token : null; + Environment.GetEnvironmentVariable("READ_PACKAGES_TOKEN") is { } token && token != string.Empty ? token.Trim() : null; protected void Warning(string line) => Console.WriteLine(line); From a11419876273ad717e522ee011d2c6e65e7a6d4b Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 15:08:53 +0200 Subject: [PATCH 09/40] Add test case that proves whitespace is removed from repository url. --- test/GprTool.Tests/NuGetUtilitiesTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index 6c66ef7..523fe37 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -143,6 +143,7 @@ public void UpdatePackageSourceCredentials() [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale///////gpr")] + [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] public void BuildPackageFile(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) { var packageFile = NuGetUtilities.BuildPackageFile("test.nupkg", repositoryUrl); From 7677bb7b6b18e780ca9728862306090b496fb29b Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 15:17:14 +0200 Subject: [PATCH 10/40] Add shorthand syntax for new push command options. --- src/GprTool/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 0770046..9b2c280 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -462,13 +462,13 @@ async Task UploadPackageAsync() [Argument(0, Description = "Path to the package file")] public string PackageFilename { get; set; } - [Option("--repository", Description = "Override current nupkg repository url. Format: owner/repository. E.g: jcansdale/gpr")] + [Option("-r|--repository", Description = "Override current nupkg repository url. Format: owner/repository. E.g: jcansdale/gpr")] public string RepositoryUrl { get; set; } - [Option("--version", Description = "Override current nupkg version")] + [Option("-v|--version", Description = "Override current nupkg version")] public string Version { get; set; } - [Option("--concurrency", Description = "The number of packages to upload simultaneously. Default value is 4.")] + [Option("-c|--concurrency", Description = "The number of packages to upload simultaneously. Default value is 4.")] public int Concurrency { get; set; } = 4; } From c3cb47a9559d885a15a4a08a3ad276563244794d Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 15:42:10 +0200 Subject: [PATCH 11/40] Fix bad formatting. --- src/GprTool/Program.cs | 106 ++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 9b2c280..dff9d1a 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -333,24 +333,24 @@ protected override async Task OnExecute(CommandLineApplication app) Console.WriteLine($"Invalid version: {Version}"); return; } - + if (isGlobPattern) { - var fallbackBaseDirectory = Directory.GetCurrentDirectory(); - var baseDirectory = glob.BuildBasePathFromGlob(fallbackBaseDirectory); - packageFiles.AddRange(Directory - .GetFiles(baseDirectory, "*.*", SearchOption.AllDirectories) - .Where(x => - x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) - || x.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) - .Where(filename => glob.IsMatch(filename)) + var fallbackBaseDirectory = Directory.GetCurrentDirectory(); + var baseDirectory = glob.BuildBasePathFromGlob(fallbackBaseDirectory); + packageFiles.AddRange(Directory + .GetFiles(baseDirectory, "*.*", SearchOption.AllDirectories) + .Where(x => + x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) + || x.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) + .Where(filename => glob.IsMatch(filename)) .Select(filename => NuGetUtilities.BuildPackageFile(filename, RepositoryUrl))); - if (!packageFiles.Any()) - { - Console.WriteLine($"Unable to find any packages in directory {baseDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); - return; - } + if (!packageFiles.Any()) + { + Console.WriteLine($"Unable to find any packages in directory {baseDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); + return; + } } else { @@ -405,23 +405,23 @@ protected override async Task OnExecute(CommandLineApplication app) var uploadPackageTasks = packageFiles.Select(packageFile => { return Task.Run(async () => - { - try - { - await UploadPackageAsync(); - } - finally - { - concurrencySemaphore.Dispose(); - } - }); - - async Task UploadPackageAsync() - { - await concurrencySemaphore.WaitAsync(); - - var request = new RestRequest(Method.PUT); - + { + try + { + await UploadPackageAsync(); + } + finally + { + concurrencySemaphore.Dispose(); + } + }); + + async Task UploadPackageAsync() + { + await concurrencySemaphore.WaitAsync(); + + var request = new RestRequest(Method.PUT); + await using var packageStream = packageFile.Filename.ReadSharedToStream(); request.AddFile("package", packageStream.ToArray(), packageFile.FilenameWithoutGprPrefixAndPath); @@ -430,30 +430,30 @@ async Task UploadPackageAsync() Authenticator = new HttpBasicAuthenticator(user, token) }; - var response = await client.ExecuteAsync(request); + var response = await client.ExecuteAsync(request); Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: Uploading package."); - if (response.StatusCode == HttpStatusCode.OK) - { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {response.Content}"); - return; - } - - var nugetWarning = response.Headers.FirstOrDefault(h => - h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); - if (nugetWarning != null) - { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {nugetWarning.Value}"); - return; - } - - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {response.StatusDescription}"); - foreach (var header in response.Headers) - { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {header.Name}: {header.Value}"); - } - } + if (response.StatusCode == HttpStatusCode.OK) + { + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {response.Content}"); + return; + } + + var nugetWarning = response.Headers.FirstOrDefault(h => + h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); + if (nugetWarning != null) + { + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {nugetWarning.Value}"); + return; + } + + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {response.StatusDescription}"); + foreach (var header in response.Headers) + { + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {header.Name}: {header.Value}"); + } + } }); await Task.WhenAll(uploadPackageTasks); @@ -466,7 +466,7 @@ async Task UploadPackageAsync() public string RepositoryUrl { get; set; } [Option("-v|--version", Description = "Override current nupkg version")] - public string Version { get; set; } + public string Version { get; set; } [Option("-c|--concurrency", Description = "The number of packages to upload simultaneously. Default value is 4.")] public int Concurrency { get; set; } = 4; From 328a01865ce88f5725d21c45069e0a93abd73955 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 17:59:46 +0200 Subject: [PATCH 12/40] Add additional test cases. --- test/GprTool.Tests/GlobExtensionTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index 4857e05..4f68dc6 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -7,9 +7,10 @@ class GlobExtensionTests { [TestCase("c:\\test.nupkg", false)] [TestCase("c:\\test", false)] - [TestCase("c:\\test\\**", true)] + [TestCase("c:\\test?", true)] [TestCase("c:\\test?\\**", true)] [TestCase("c:\\test?\\[abc]\\**", true)] + [TestCase("c:\\test\\**", true)] [TestCase("c:\\test\\**\\*.nupkg", true)] [TestCase("c:\\test\\*.*", true)] public void IsGlobPattern(string path, bool isGlobPattern) @@ -20,9 +21,10 @@ public void IsGlobPattern(string path, bool isGlobPattern) [TestCase("c:\\test.nupkg", "c:\\test.nupkg")] [TestCase("c:\\test", "c:\\test")] - [TestCase("c:\\test\\*.*", "c:\\test")] + [TestCase("c:\\test?", "c:\\test")] [TestCase("c:\\test?\\**", "c:\\test")] [TestCase("c:\\test?\\[abc]\\**", "c:\\test")] + [TestCase("c:\\test\\*.*", "c:\\test")] [TestCase("c:\\test\\**", "c:\\test")] [TestCase("c:\\test\\**\\*.nupkg", "c:\\test")] [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\test\\subdirectory")] From a47485da78644d35d3053d765a7cdeaec57f7557 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 18:25:01 +0200 Subject: [PATCH 13/40] Add retry policy in order to deal with intermittent connection issue. --- src/GprTool/GprTool.csproj | 1 + src/GprTool/Program.cs | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index 95b646b..e0d0fa6 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -26,6 +26,7 @@ + diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 8793ccf..8e35c25 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -15,6 +15,7 @@ using Octokit.GraphQL; using Octokit.GraphQL.Model; using Octokit.GraphQL.Core; +using Polly; using RestSharp.Extensions; namespace GprTool @@ -323,6 +324,25 @@ class PackageInfo [Command(Description = "Publish a package")] public class PushCommand : GprCommandBase { + static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int retrySleepSeconds, int timeoutSeconds) + { + if (retryNumber <= 0) + { + return Policy.NoOpAsync(); + } + + var retryPolicy = Policy + // http://restsharp.org/usage/exceptions.html + .HandleResult(x => x.StatusCode != HttpStatusCode.Unauthorized + && x.StatusCode != HttpStatusCode.Conflict + && x.StatusCode != HttpStatusCode.OK) + .WaitAndRetryAsync(retryNumber, retryAttempt => TimeSpan.FromSeconds(retrySleepSeconds)); + + var timeoutPolicy = Policy.TimeoutAsync(timeoutSeconds); + + return Policy.WrapAsync(retryPolicy, timeoutPolicy); + } + protected override async Task OnExecute(CommandLineApplication app) { var packageFiles = new List(); @@ -404,6 +424,11 @@ protected override async Task OnExecute(CommandLineApplication app) Console.WriteLine($"Found {packageFiles.Count} package{(packageFiles.Count > 1 ? "s" : string.Empty)}."); + // Retry X times -> + // Sleep for X seconds -> + // Timeout if the request takes longer than X seconds. + var retryPolicy = BuildRetryAsyncPolicy(Math.Max(0, Retries), 10, 300); + var uploadPackageTasks = packageFiles.Select(packageFile => { return Task.Run(async () => @@ -432,9 +457,9 @@ async Task UploadPackageAsync() x.Authenticator = new HttpBasicAuthenticator(user, token); }); - var response = await client.ExecuteAsync(request); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: Uploading package."); + + var response = await retryPolicy.ExecuteAsync(() => client.ExecuteAsync(request)); if (response.StatusCode == HttpStatusCode.OK) { @@ -472,6 +497,9 @@ async Task UploadPackageAsync() [Option("-c|--concurrency", Description = "The number of packages to upload simultaneously. Default value is 4.")] public int Concurrency { get; set; } = 4; + + [Option("--retries", Description = "The number of retries in case of intermittent connection issue. Default value is 3. Set to 0 if you want to disable automatic retry.")] + public int Retries { get; set; } = 3; } [Command(Description = "View package details")] From f870f07ef0f63cec81ed09427804bc84255244b7 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 20:47:07 +0200 Subject: [PATCH 14/40] Bugfix: Support relative urls. E.g owner/repositoryname. Also always convert uri scheme to https. --- src/GprTool/NuGetUtilities.cs | 30 ++++++++++++++++++----- src/GprTool/Program.cs | 2 +- test/GprTool.Tests/NuGetUtilitiesTests.cs | 9 ++++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 72a2ef5..9a47388 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -15,7 +15,6 @@ public class PackageFile public string Owner { get; set; } public string RepositoryName { get; set; } public string RepositoryUrl { get; set; } - public bool IsGithubRepository { get; set; } public bool IsNuspecRewritten { get; set; } public string FilenameWithoutGprPrefixAndPath @@ -42,17 +41,37 @@ public class NuGetUtilities { public static bool BuildOwnerAndRepositoryFromUrl(PackageFile packageFile, string repositoryUrl) { - if (repositoryUrl == null - || !Uri.TryCreate(repositoryUrl, UriKind.Absolute, out var repositoryUri)) + if (repositoryUrl == null) { return false; } + repositoryUrl = repositoryUrl.Trim(); + + if (Uri.IsWellFormedUriString(repositoryUrl, UriKind.Relative)) + { + repositoryUrl = $"https://github.com/{repositoryUrl}"; + } + + if (!Uri.TryCreate(repositoryUrl, UriKind.Absolute, out var repositoryUri) + || repositoryUri.Host != "github.com") + { + return false; + } + + if (repositoryUri.Scheme != Uri.UriSchemeHttps) + { + repositoryUri = new UriBuilder(repositoryUri) + { + Scheme = Uri.UriSchemeHttps, + Port = -1 + }.Uri; + } + var ownerAndRepositoryName = repositoryUri.PathAndQuery .Substring(1) .Replace("\\", "/") .Split("/", StringSplitOptions.RemoveEmptyEntries) - .Take(2) .ToList(); if (ownerAndRepositoryName.Count != 2) @@ -62,8 +81,7 @@ public static bool BuildOwnerAndRepositoryFromUrl(PackageFile packageFile, strin packageFile.Owner = ownerAndRepositoryName[0]; packageFile.RepositoryName = ownerAndRepositoryName[1]; - packageFile.RepositoryUrl = repositoryUri.ToString(); - packageFile.IsGithubRepository = string.Equals("github.com", repositoryUri.Host, StringComparison.OrdinalIgnoreCase); + packageFile.RepositoryUrl = $"https://github.com/{packageFile.Owner}/{packageFile.RepositoryName}"; return true; } diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 8e35c25..39033b7 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -398,7 +398,7 @@ protected override async Task OnExecute(CommandLineApplication app) if (packageFile.Owner == null || packageFile.RepositoryName == null - || !packageFile.IsGithubRepository) + || packageFile.RepositoryUrl == null) { Console.WriteLine( $"Project is missing a valid XML element value: {packageFile.RepositoryUrl}. " + diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index 523fe37..5235993 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -139,10 +139,13 @@ public void UpdatePackageSourceCredentials() } [TestCase(null, null, null, null)] - [TestCase("jcansdale/gpr", null, null, null)] + [TestCase("jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("jcansdale//gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("http://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale///////gpr")] + [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] public void BuildPackageFile(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) { @@ -154,14 +157,12 @@ public void BuildPackageFile(string repositoryUrl, string expectedOwner, string Assert.Null(packageFile.Owner); Assert.Null(packageFile.RepositoryName); Assert.That(packageFile.RepositoryUrl, Is.Null); - Assert.False(packageFile.IsGithubRepository); return; } Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); Assert.That(packageFile.RepositoryUrl, Is.Not.Null); Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); - Assert.That(packageFile.IsGithubRepository); } [TestCase("test.nupkg", "test.nupkg")] From 50878c53d8fbd385d4675a0af8095ef654d42552 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Mon, 15 Jun 2020 22:48:40 +0200 Subject: [PATCH 15/40] Add support for expanding a relative path combined with a base directory. --- src/GprTool/GlobExtensions.cs | 9 +++++---- src/GprTool/Program.cs | 9 +++++---- test/GprTool.Tests/GlobExtensionTests.cs | 20 +++++++++++++++----- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/GprTool/GlobExtensions.cs b/src/GprTool/GlobExtensions.cs index 8b872cc..eee2bcf 100644 --- a/src/GprTool/GlobExtensions.cs +++ b/src/GprTool/GlobExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Text; using DotNet.Globbing; @@ -13,7 +14,7 @@ public static bool IsGlobPattern(this Glob glob) return glob.Tokens.Any(x => !(x is PathSeparatorToken || x is LiteralToken)); } - public static string BuildBasePathFromGlob(this Glob glob, string fallbackPath = null) + public static string BuildBasePathFromGlob(this Glob glob, string baseDirectory = null) { if (glob == null) throw new ArgumentNullException(nameof(glob)); @@ -56,12 +57,12 @@ public static string BuildBasePathFromGlob(this Glob glob, string fallbackPath = done: var pathStr = path.ToString(); - if (fallbackPath != null && string.IsNullOrWhiteSpace(pathStr)) + if (baseDirectory != null && !Path.IsPathRooted(pathStr)) { - return fallbackPath; + return Path.GetFullPath(pathStr, baseDirectory); } - return pathStr; + return Path.GetFullPath(pathStr); } } } diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 39033b7..51000ce 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -358,10 +358,11 @@ protected override async Task OnExecute(CommandLineApplication app) if (isGlobPattern) { - var fallbackBaseDirectory = Directory.GetCurrentDirectory(); - var baseDirectory = glob.BuildBasePathFromGlob(fallbackBaseDirectory); + var baseDirectory = Directory.GetCurrentDirectory(); + var searchDirectory = glob.BuildBasePathFromGlob(baseDirectory); + packageFiles.AddRange(Directory - .GetFiles(baseDirectory, "*.*", SearchOption.AllDirectories) + .GetFiles(searchDirectory, "*.*", SearchOption.AllDirectories) .Where(x => x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) @@ -370,7 +371,7 @@ protected override async Task OnExecute(CommandLineApplication app) if (!packageFiles.Any()) { - Console.WriteLine($"Unable to find any packages in directory {baseDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); + Console.WriteLine($"Unable to find any packages in directory {searchDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); return; } } diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index 4f68dc6..fbde442 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -7,6 +7,11 @@ class GlobExtensionTests { [TestCase("c:\\test.nupkg", false)] [TestCase("c:\\test", false)] + [TestCase("packages", false)] + [TestCase("./packages", false)] + [TestCase(".\\packages", false)] + [TestCase("packages/**/*.nupkg", true)] + [TestCase("packages\\**\\*.nupkg", true)] [TestCase("c:\\test?", true)] [TestCase("c:\\test?\\**", true)] [TestCase("c:\\test?\\[abc]\\**", true)] @@ -19,6 +24,9 @@ public void IsGlobPattern(string path, bool isGlobPattern) Assert.That(glob.IsGlobPattern(), Is.EqualTo(isGlobPattern)); } + [TestCase("./packages/**/*.nupkg", "c:\\test\\packages")] + [TestCase(".\\packages/**/*.nupkg", "c:\\test\\packages")] + [TestCase("packages", "c:\\test\\packages")] [TestCase("c:\\test.nupkg", "c:\\test.nupkg")] [TestCase("c:\\test", "c:\\test")] [TestCase("c:\\test?", "c:\\test")] @@ -32,13 +40,15 @@ public void IsGlobPattern(string path, bool isGlobPattern) public void BuildBasePathFromGlob(string path, string expectedBaseDirectory) { var glob = Glob.Parse(path); - Assert.That(glob.BuildBasePathFromGlob(), Is.EqualTo(expectedBaseDirectory)); + Assert.That(glob.BuildBasePathFromGlob("c:\\test"), Is.EqualTo(expectedBaseDirectory)); } - [Test] - public void BuildBasePathFromGlob_FallbackPath() + [TestCase("packages/**/*.nupkg", "c:\\test", "c:\\test\\packages")] + [TestCase("packages\\**\\*.nupkg", "c:\\test", "c:\\test\\packages")] + [TestCase("packages", "c:\\test", "c:\\test\\packages")] + public void BuildBasePathFromGlob_Uses_BaseDirectory_When_Path_Is_Not_Rooted(string relativePath, string baseDirectory, string expectedPath) { - var glob = Glob.Parse("*.*"); - Assert.That(glob.BuildBasePathFromGlob("c:\\test"), Is.EqualTo("c:\\test")); + var glob = Glob.Parse(relativePath); + Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedPath)); } } \ No newline at end of file From fe86d605a44edde9a7f1dd7d9722e19ed62de8d7 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 11:42:52 +0200 Subject: [PATCH 16/40] Cancel push if "CTRL - C" is pressed. --- src/GprTool/Program.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 51000ce..f208c0d 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -32,7 +32,7 @@ namespace GprTool )] public class Program : GprCommandBase { - public async static Task Main(string[] args) + public static async Task Main(string[] args) { try { @@ -324,7 +324,7 @@ class PackageInfo [Command(Description = "Publish a package")] public class PushCommand : GprCommandBase { - static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int retrySleepSeconds, int timeoutSeconds) + static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int retrySleepSeconds, int timeoutSeconds, CancellationToken cancellationToken) { if (retryNumber <= 0) { @@ -333,7 +333,8 @@ static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int re var retryPolicy = Policy // http://restsharp.org/usage/exceptions.html - .HandleResult(x => x.StatusCode != HttpStatusCode.Unauthorized + .HandleResult(x => !cancellationToken.IsCancellationRequested + && x.StatusCode != HttpStatusCode.Unauthorized && x.StatusCode != HttpStatusCode.Conflict && x.StatusCode != HttpStatusCode.OK) .WaitAndRetryAsync(retryNumber, retryAttempt => TimeSpan.FromSeconds(retrySleepSeconds)); @@ -428,7 +429,7 @@ protected override async Task OnExecute(CommandLineApplication app) // Retry X times -> // Sleep for X seconds -> // Timeout if the request takes longer than X seconds. - var retryPolicy = BuildRetryAsyncPolicy(Math.Max(0, Retries), 10, 300); + var retryPolicy = BuildRetryAsyncPolicy(Math.Max(0, Retries), 10, 300, CancellationToken); var uploadPackageTasks = packageFiles.Select(packageFile => { @@ -460,7 +461,7 @@ async Task UploadPackageAsync() Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: Uploading package."); - var response = await retryPolicy.ExecuteAsync(() => client.ExecuteAsync(request)); + var response = await retryPolicy.ExecuteAsync(() => client.ExecuteAsync(request, CancellationToken)); if (response.StatusCode == HttpStatusCode.OK) { @@ -657,11 +658,20 @@ static string UnicodeEncode(string str) [HelpOption("--help")] public abstract class GprCommandBase { + readonly CancellationTokenSource _cancellationTokenSource; + protected string AssemblyProduct => Assembly.GetExecutingAssembly().GetCustomAttribute()?.Product; protected string AssemblyInformationalVersion => ThisAssembly.AssemblyInformationalVersion; + protected CancellationToken CancellationToken => _cancellationTokenSource.Token; protected abstract Task OnExecute(CommandLineApplication app); + protected GprCommandBase() + { + _cancellationTokenSource = new CancellationTokenSource(); + Console.CancelKeyPress += (sender, args) => _cancellationTokenSource.Cancel(); + } + protected RestClient WithRestClient(string baseUrl, Action builderAction = null) { var restClient = new RestClient(baseUrl) From e3cb47a8be9187a26a8b24c156576436fdfe0afa Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 11:50:30 +0200 Subject: [PATCH 17/40] Address latest feedback. --- src/GprTool/NuGetUtilities.cs | 103 +++++++++------------- src/GprTool/Program.cs | 80 +++++++++++------ test/GprTool.Tests/NuGetUtilitiesTests.cs | 93 +++++++++++-------- 3 files changed, 149 insertions(+), 127 deletions(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 9a47388..a2e4231 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -11,30 +11,15 @@ namespace GprTool { public class PackageFile { - public string Filename { get; set; } public string Owner { get; set; } public string RepositoryName { get; set; } public string RepositoryUrl { get; set; } + public bool ShouldRewriteNuspec { get; set; } public bool IsNuspecRewritten { get; set; } - public string FilenameWithoutGprPrefixAndPath - { - get - { - var gprIndex = !IsNuspecRewritten ? -1 : Filename.LastIndexOf("_gpr", StringComparison.OrdinalIgnoreCase); - if (gprIndex == -1) - { - return Filename; - } - - // Support case sensitive filename extensions (e.g. test_gpr.NuPkg -> test.NuPkg) - - var filenameUntilGpr = Filename.Substring(0, gprIndex); - var filenameExcludingGpr = Filename.Substring(gprIndex + 4); - - return Path.GetFileName(filenameUntilGpr + filenameExcludingGpr); - } - } + public string Filename { get; set; } + public string FilenameAbsolutePath { get; set; } + public string FilenameWithoutGprPrefix { get; set; } } public class NuGetUtilities @@ -86,24 +71,10 @@ public static bool BuildOwnerAndRepositoryFromUrl(PackageFile packageFile, strin return true; } - public static bool TryReadPackageFileMetadata(PackageFile packageFile) + public static bool BuildOwnerAndRepositoryFromUrlFromNupkg(PackageFile packageFile) { - if (!File.Exists(packageFile.Filename)) - { - return false; - } - - Manifest manifest; - - try - { - manifest = ReadNupkgManifest(packageFile.Filename); - } - catch (Exception) - { - return false; - } - + var manifest = ReadNupkgManifest(packageFile.FilenameAbsolutePath); + return BuildOwnerAndRepositoryFromUrl(packageFile, manifest.Metadata.Repository?.Url); } @@ -111,7 +82,9 @@ public static PackageFile BuildPackageFile(string filename, string repositoryUrl { var packageFile = new PackageFile { - Filename = filename + Filename = Path.GetFileName(filename), + FilenameWithoutGprPrefix = Path.GetFileName(filename), + FilenameAbsolutePath = Path.GetFullPath(filename) }; BuildOwnerAndRepositoryFromUrl(packageFile, repositoryUrl); @@ -126,35 +99,29 @@ public static Manifest ReadNupkgManifest(string nupkgPath) return Manifest.ReadFrom(packageArchiveReader.GetNuspec(), false); } - public static bool ShouldRewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) + public static bool ShouldRewriteNupkg(PackageFile packageFile, NuGetVersion nuGetVersion = null) { - if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); + if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); - var manifest = ReadNupkgManifest(nupkgPath); + var manifest = ReadNupkgManifest(packageFile.FilenameAbsolutePath); if (nuGetVersion != null && !nuGetVersion.Equals(manifest.Metadata.Version)) { return true; } - return !string.Equals(repositoryUrl, manifest.Metadata.Repository?.Url, StringComparison.OrdinalIgnoreCase); + return !string.Equals(packageFile.RepositoryUrl, manifest.Metadata.Repository?.Url, StringComparison.OrdinalIgnoreCase); } - public static string RewriteNupkg(string nupkgPath, string repositoryUrl, NuGetVersion nuGetVersion = null) + public static void RewriteNupkg(PackageFile packageFile, NuGetVersion nuGetVersion = null) { - if (nupkgPath == null) throw new ArgumentNullException(nameof(nupkgPath)); - if (repositoryUrl == null) throw new ArgumentNullException(nameof(repositoryUrl)); - - var randomDirectoryId = Guid.NewGuid().ToString("N"); - var nupkgFilename = Path.GetFileName(nupkgPath); - var nupkgFilenameWithoutExt = Path.GetFileNameWithoutExtension(nupkgFilename); - var nupkgWorkingDirectoryAbsolutePath = Path.GetDirectoryName(nupkgPath); - var workingDirectory = Path.Combine(nupkgWorkingDirectoryAbsolutePath, $"{nupkgFilenameWithoutExt}_{randomDirectoryId}"); - - using var tmpDirectory = new DisposableDirectory(workingDirectory); - using var packageArchiveReader = new PackageArchiveReader(nupkgPath.ReadSharedToStream(), false); - using var nuspecMemoryStream = new MemoryStream(); - + if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); + + var randomId = Guid.NewGuid().ToString("N"); + + using var packageArchiveReader = new PackageArchiveReader( + packageFile.FilenameAbsolutePath.ReadSharedToStream(), false); + var nuspecXDocument = packageArchiveReader.NuspecReader.Xml; var packageXElement = nuspecXDocument.Single("package"); var metadataXElement = packageXElement.Single("metadata"); @@ -174,34 +141,44 @@ public static string RewriteNupkg(string nupkgPath, string repositoryUrl, NuGetV if (repositoryXElement == null) { repositoryXElement = new XElement("repository"); - repositoryXElement.SetAttributeValue("url", repositoryUrl); + repositoryXElement.SetAttributeValue("url", packageFile.RepositoryUrl); repositoryXElement.SetAttributeValue("type", "git"); metadataXElement.Add(repositoryXElement); } else { - repositoryXElement.SetAttributeValue("url", repositoryUrl); + repositoryXElement.SetAttributeValue("url", packageFile.RepositoryUrl); repositoryXElement.SetAttributeValue("type", "git"); } + using var nuspecMemoryStream = new MemoryStream(); nuspecXDocument.Save(nuspecMemoryStream); nuspecMemoryStream.Seek(0, SeekOrigin.Begin); - ZipFile.ExtractToDirectory(nupkgPath, tmpDirectory.WorkingDirectory, true); + var packageFileWorkingDirectoryAbsolutePath = Path.GetDirectoryName(packageFile.FilenameAbsolutePath); + var packageFileRewriteWorkingDirectory = Path.Combine(packageFileWorkingDirectoryAbsolutePath, + $"{packageId}.{nuGetVersion}_{randomId}"); + + using var tmpDirectory = new DisposableDirectory(packageFileRewriteWorkingDirectory); + + ZipFile.ExtractToDirectory(packageFile.FilenameAbsolutePath, tmpDirectory.WorkingDirectory); var nuspecDstFilename = Path.Combine(tmpDirectory.WorkingDirectory, $"{packageId}.nuspec"); File.WriteAllBytes(nuspecDstFilename, nuspecMemoryStream.ToArray()); using var outputStream = new MemoryStream(); - var packageBuilder = new PackageBuilder(nuspecMemoryStream, tmpDirectory.WorkingDirectory, propertyProvider => throw new NotImplementedException()); + var packageBuilder = new PackageBuilder(nuspecMemoryStream, tmpDirectory.WorkingDirectory, + propertyProvider => throw new NotImplementedException()); packageBuilder.Save(outputStream); - var nupkgDstFilenameAbsolutePath = Path.Combine(nupkgWorkingDirectoryAbsolutePath, $"{packageId}.{nuGetVersion}_gpr.nupkg"); - - File.WriteAllBytes(nupkgDstFilenameAbsolutePath, outputStream.ToArray()); + packageFile.FilenameAbsolutePath = Path.Combine(packageFileWorkingDirectoryAbsolutePath, + $"{packageId}.{nuGetVersion}_{randomId}_gpr.nupkg"); + packageFile.Filename = Path.GetFileName(packageFile.FilenameAbsolutePath); + packageFile.FilenameWithoutGprPrefix = $"{packageId}.{nuGetVersion}.nupkg"; + packageFile.IsNuspecRewritten = true; - return nupkgDstFilenameAbsolutePath; + File.WriteAllBytes(packageFile.FilenameAbsolutePath, outputStream.ToArray()); } public static string FindTokenInNuGetConfig(Action warning = null) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index f208c0d..4090fd8 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -9,6 +9,7 @@ using System.Threading; using DotNet.Globbing; using McMaster.Extensions.CommandLineUtils; +using NuGet.Packaging; using NuGet.Versioning; using RestSharp; using RestSharp.Authenticators; @@ -333,9 +334,10 @@ static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int re var retryPolicy = Policy // http://restsharp.org/usage/exceptions.html - .HandleResult(x => !cancellationToken.IsCancellationRequested + .HandleResult(x => !cancellationToken.IsCancellationRequested && x.StatusCode != HttpStatusCode.Unauthorized - && x.StatusCode != HttpStatusCode.Conflict + && x.StatusCode != HttpStatusCode.Conflict + && x.StatusCode != HttpStatusCode.BadRequest && x.StatusCode != HttpStatusCode.OK) .WaitAndRetryAsync(retryNumber, retryAttempt => TimeSpan.FromSeconds(retrySleepSeconds)); @@ -351,7 +353,7 @@ protected override async Task OnExecute(CommandLineApplication app) var isGlobPattern = glob.IsGlobPattern(); NuGetVersion nuGetVersion = null; - if (RepositoryUrl != null && Version != null && !NuGetVersion.TryParse(Version, out nuGetVersion)) + if (Version != null && !NuGetVersion.TryParse(Version, out nuGetVersion)) { Console.WriteLine($"Invalid version: {Version}"); return; @@ -383,7 +385,7 @@ protected override async Task OnExecute(CommandLineApplication app) foreach (var packageFile in packageFiles) { - if (!File.Exists(packageFile.Filename)) + if (!File.Exists(packageFile.FilenameAbsolutePath)) { Console.WriteLine($"Package file was not found: {packageFile}"); return; @@ -391,7 +393,7 @@ protected override async Task OnExecute(CommandLineApplication app) if (RepositoryUrl == null) { - NuGetUtilities.TryReadPackageFileMetadata(packageFile); + NuGetUtilities.BuildOwnerAndRepositoryFromUrlFromNupkg(packageFile); } else { @@ -404,19 +406,13 @@ protected override async Task OnExecute(CommandLineApplication app) { Console.WriteLine( $"Project is missing a valid XML element value: {packageFile.RepositoryUrl}. " + - $"Package filename: {packageFile.Filename} " + + $"Package filename: {packageFile.FilenameAbsolutePath} " + "Please use --repository option to set a valid upstream GitHub repository. " + "Additional details are available at: https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#repositoryurl"); return; } - if (!NuGetUtilities.ShouldRewriteNupkg(packageFile.Filename, packageFile.RepositoryUrl, nuGetVersion)) - { - continue; - } - - packageFile.Filename = NuGetUtilities.RewriteNupkg(packageFile.Filename, packageFile.RepositoryUrl, nuGetVersion); - packageFile.IsNuspecRewritten = true; + packageFile.ShouldRewriteNuspec = NuGetUtilities.ShouldRewriteNupkg(packageFile, nuGetVersion); } const string user = "GprTool"; @@ -437,51 +433,79 @@ protected override async Task OnExecute(CommandLineApplication app) { try { - await UploadPackageAsync(); + await concurrencySemaphore.WaitAsync(CancellationToken); + + NuGetVersion packageVersion; + if (packageFile.ShouldRewriteNuspec) + { + NuGetUtilities.RewriteNupkg(packageFile, nuGetVersion); + + var manifest = NuGetUtilities.ReadNupkgManifest(packageFile.FilenameAbsolutePath); + packageVersion = manifest.Metadata.Version; + } + else + { + var manifest = NuGetUtilities.ReadNupkgManifest(packageFile.FilenameAbsolutePath); + packageVersion = manifest.Metadata.Version; + } + + await using var packageStream = packageFile.FilenameAbsolutePath.ReadSharedToStream(); + + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: " + + $"Repository url: {packageFile.RepositoryUrl}. " + + $"Version: {packageVersion}. " + + $"Size: {packageStream.Length} bytes. "); + + + return await retryPolicy.ExecuteAsync(() => UploadPackageAsync(packageStream)); } finally { - concurrencySemaphore.Dispose(); + concurrencySemaphore.Release(); } }); - async Task UploadPackageAsync() + async Task UploadPackageAsync(MemoryStream packageStream) { - await concurrencySemaphore.WaitAsync(); + if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); var request = new RestRequest(Method.PUT); - await using var packageStream = packageFile.Filename.ReadSharedToStream(); - request.AddFile("package", packageStream.ToArray(), packageFile.FilenameWithoutGprPrefixAndPath); - var client = WithRestClient($"https://nuget.pkg.github.com/{packageFile.Owner}/", x => { x.Authenticator = new HttpBasicAuthenticator(user, token); }); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: Uploading package."); + packageStream.Seek(0, SeekOrigin.Begin); + + request.AddFile("package", packageStream.CopyTo, packageFile.FilenameWithoutGprPrefix, packageStream.Length); + + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: Uploading package."); - var response = await retryPolicy.ExecuteAsync(() => client.ExecuteAsync(request, CancellationToken)); + var response = await client.ExecuteAsync(request, CancellationToken); if (response.StatusCode == HttpStatusCode.OK) { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {response.Content}"); - return; + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.Content}"); + goto done; } var nugetWarning = response.Headers.FirstOrDefault(h => h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); if (nugetWarning != null) { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {nugetWarning.Value}"); - return; + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {nugetWarning.Value}"); + goto done; } - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {response.StatusDescription}"); + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.StatusDescription}"); foreach (var header in response.Headers) { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefixAndPath}]: {header.Name}: {header.Value}"); + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {header.Name}: {header.Value}"); } + + done: + return response; } }); diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index 5235993..261cccd 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -149,9 +149,14 @@ public void UpdatePackageSourceCredentials() [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] public void BuildPackageFile(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) { - var packageFile = NuGetUtilities.BuildPackageFile("test.nupkg", repositoryUrl); + var packageFile = NuGetUtilities.BuildPackageFile("c:\\test\\test.nupkg", repositoryUrl); + packageFile.IsNuspecRewritten = true; + Assert.That(packageFile, Is.Not.Null); Assert.That(packageFile.Filename, Is.EqualTo("test.nupkg")); + Assert.That(packageFile.FilenameWithoutGprPrefix, Is.EqualTo("test.nupkg")); + Assert.That(packageFile.FilenameAbsolutePath, Is.EqualTo("c:\\test\\test.nupkg")); + if (expectedGithubRepositoryUrl == null) { Assert.Null(packageFile.Owner); @@ -159,28 +164,41 @@ public void BuildPackageFile(string repositoryUrl, string expectedOwner, string Assert.That(packageFile.RepositoryUrl, Is.Null); return; } + Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); Assert.That(packageFile.RepositoryUrl, Is.Not.Null); Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); } - [TestCase("test.nupkg", "test.nupkg")] - [TestCase("test.snupkg", "test.snupkg")] - [TestCase("test_gpr.nupkg", "test.nupkg")] - [TestCase("test_gpr.snupkg", "test.snupkg")] - [TestCase("test_GPR.nupkg", "test.nupkg")] - [TestCase("test_GPR.snupkg", "test.snupkg")] - [TestCase("test_GPR.nUpkG", "test.nUpkG", Description = "Preserves casing")] - [TestCase("test_GPR.SnuPkg", "test.SnuPkg", Description = "Preserves casing")] - public void PackageFile_FilenameWithoutGprPrefixAndPath(string filename, string expectedFilename) + [TestCase("jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("jcansdale//gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("http://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] + public void BuildOwnerAndRepositoryFromUrlFromNupkg(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) { - var packageFile = new PackageFile + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => { - Filename = filename, - IsNuspecRewritten = true - }; - Assert.That(packageFile.FilenameWithoutGprPrefixAndPath, Is.EqualTo(expectedFilename)); + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = repositoryUrl, + Type = "git" + }; + })); + + packageBuilderContext.Build(); + + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, null); + + Assert.True(NuGetUtilities.BuildOwnerAndRepositoryFromUrlFromNupkg(packageFile)); + + Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); + Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); + Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); } [Test] @@ -223,10 +241,11 @@ public void ShouldRewriteNupkg_Version(string currentVersion, string updatedVers packageBuilderContext.Build(); + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); + Assert.That( - NuGetUtilities.ShouldRewriteNupkg( - packageBuilderContext.NupkgFilename, - repositoryUrl, NuGetVersion.Parse(updatedVersion)), Is.EqualTo(shouldUpdateVersion)); + NuGetUtilities.ShouldRewriteNupkg(packageFile, + NuGetVersion.Parse(updatedVersion)), Is.EqualTo(shouldUpdateVersion)); } [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git", false, Description = "Equals")] @@ -253,10 +272,9 @@ public void ShouldRewriteNupkg_RepositoryUrl(string currentRepositoryUrl, string packageBuilderContext.Build(); - Assert.That( - NuGetUtilities.ShouldRewriteNupkg( - packageBuilderContext.NupkgFilename, - updatedRepositoryUrl), Is.EqualTo(shouldUpdateRepositoryUrl)); + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, updatedRepositoryUrl); + + Assert.That(NuGetUtilities.ShouldRewriteNupkg(packageFile), Is.EqualTo(shouldUpdateRepositoryUrl)); } [TestCase("randomvalue")] @@ -276,26 +294,27 @@ public void ShouldRewriteNupkg_Ignores_RepositoryType(string repositoryType) packageBuilderContext.Build(); - Assert.That( - NuGetUtilities.ShouldRewriteNupkg( - packageBuilderContext.NupkgFilename, - currentRepositoryUrl), Is.EqualTo(false)); + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, currentRepositoryUrl); + + Assert.That(NuGetUtilities.ShouldRewriteNupkg(packageFile), Is.EqualTo(false)); } [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git")] [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo")] public void RewriteNuspec(string repositoryUrl, string expectedRepositoryUrl) { - using var originalPackageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( manifest => { manifest.Metadata.Repository = null; })); - originalPackageBuilderContext.Build(); + packageBuilderContext.Build(); + + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); - var rewrittenNupkgAbsolutePath = NuGetUtilities.RewriteNupkg(originalPackageBuilderContext.NupkgFilename, - repositoryUrl, NuGetVersion.Parse("2.0.0")); - using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(rewrittenNupkgAbsolutePath)); + NuGetUtilities.RewriteNupkg(packageFile, NuGetVersion.Parse("2.0.0")); + + using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(packageFile.FilenameAbsolutePath)); var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); @@ -311,7 +330,7 @@ public void RewriteNuspec(string repositoryUrl, string expectedRepositoryUrl) [Test] public void RewriteNuspec_Overwrites_Existing_Repository_Url() { - using var originalPackageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( manifest => { manifest.Metadata.Repository = new RepositoryMetadata @@ -320,11 +339,13 @@ public void RewriteNuspec_Overwrites_Existing_Repository_Url() Type = "google" }; })); - originalPackageBuilderContext.Build(); + packageBuilderContext.Build(); + + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, "https://github.com/owner/repo"); + + NuGetUtilities.RewriteNupkg(packageFile, NuGetVersion.Parse("2.0.0")); - var rewrittenNupkgAbsolutePath = NuGetUtilities.RewriteNupkg(originalPackageBuilderContext.NupkgFilename, - "https://github.com/owner/repo", NuGetVersion.Parse("2.0.0")); - using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(rewrittenNupkgAbsolutePath)); + using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(packageFile.FilenameAbsolutePath)); var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); From 6daa0cde499a6d33dd027878dad2caf4988fb086 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 16:21:47 +0200 Subject: [PATCH 18/40] Add support for cancellation of all commands and return non-zero exit codes if the command fail. Fixes #50 --- .gitignore | 1 + src/GprTool/EnumerableExtensions.cs | 46 ++++ src/GprTool/GprTool.csproj | 4 +- src/GprTool/NuGetUtilities.cs | 1 + src/GprTool/Program.cs | 343 +++++++++++++++------------- 5 files changed, 240 insertions(+), 155 deletions(-) create mode 100644 src/GprTool/EnumerableExtensions.cs diff --git a/.gitignore b/.gitignore index 61ce391..dcbcc30 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ _NCrunch_*/ *.ncrunchsolution publish/ +src/GprTool/nupkg/ diff --git a/src/GprTool/EnumerableExtensions.cs b/src/GprTool/EnumerableExtensions.cs new file mode 100644 index 0000000..16dde92 --- /dev/null +++ b/src/GprTool/EnumerableExtensions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace GprTool +{ + internal static class EnumerableExtensions + { + public static Task ForEachAsync([NotNull] this IEnumerable source, + [NotNull] Func onExecuteFunc, Action onExceptionAction = null, + CancellationToken cancellationToken = default, int concurrency = 1) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (onExecuteFunc == null) throw new ArgumentNullException(nameof(onExecuteFunc)); + if (concurrency <= 0) throw new ArgumentOutOfRangeException(nameof(concurrency)); + + // https://devblogs.microsoft.com/pfxteam/implementing-a-simple-foreachasync-part-2/ + return Task.WhenAll( + Partitioner + .Create(source) + .GetPartitions(concurrency) + .Select(partition => Task.Run(async delegate + { + using (partition) + { + while (partition.MoveNext()) + { + try + { + await onExecuteFunc(partition.Current, cancellationToken); + } + catch (Exception e) + { + onExceptionAction?.Invoke(partition.Current, e); + throw; + } + } + } + }, default))); + } + } +} \ No newline at end of file diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index e0d0fa6..4b9da83 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -24,11 +24,11 @@ - + - + diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index a2e4231..13f772c 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -16,6 +16,7 @@ public class PackageFile public string RepositoryUrl { get; set; } public bool ShouldRewriteNuspec { get; set; } public bool IsNuspecRewritten { get; set; } + public bool IsUploaded { get; set; } public string Filename { get; set; } public string FilenameAbsolutePath { get; set; } diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 4090fd8..5fc65c3 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -5,11 +5,11 @@ using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; using DotNet.Globbing; using McMaster.Extensions.CommandLineUtils; -using NuGet.Packaging; using NuGet.Versioning; using RestSharp; using RestSharp.Authenticators; @@ -17,7 +17,6 @@ using Octokit.GraphQL.Model; using Octokit.GraphQL.Core; using Polly; -using RestSharp.Extensions; namespace GprTool { @@ -33,93 +32,99 @@ namespace GprTool )] public class Program : GprCommandBase { - public static async Task Main(string[] args) + public static async Task Main(string[] args) { try { - await CommandLineApplication.ExecuteAsync(args); + return await CommandLineApplication.ExecuteAsync(args); } - catch(ApplicationException e) + catch (Exception e) { - Console.WriteLine(e.Message); + Console.WriteLine(e); + return 1; } } - protected override Task OnExecute(CommandLineApplication app) + protected override Task OnExecuteAsyncImpl(CommandLineApplication app, CancellationToken cancellationToken) { // this shows help even if the --help option isn't specified app.ShowHelp(); - return Task.CompletedTask; + return Task.FromResult(1); } } [Command(Description = "List files for a package")] public class FilesCommand : GprCommandBase { - protected override async Task OnExecute(CommandLineApplication app) + protected override async Task OnExecuteAsyncImpl(CommandLineApplication app, + CancellationToken cancellationToken) { var connection = CreateConnection(); - if(PackagesPath is null) + if (PackagesPath is null) { - System.Console.WriteLine("Please include a packages path"); - return; + Console.WriteLine("Please include a packages path"); + return 1; } var packageCollection = await GraphQLUtilities.FindPackageConnection(connection, PackagesPath); - if(packageCollection == null) + if (packageCollection == null) { Console.WriteLine("Couldn't find packages"); - return; + return 1; } - var query = packageCollection.Nodes.Select(p => + var query = packageCollection.Nodes.Select(p => new { - p.Name, p.Statistics.DownloadsTotalCount, + p.Name, + p.Statistics.DownloadsTotalCount, Versions = p.Versions(100, null, null, null, null).Nodes.Select(v => - new + new { - v.Version, v.Statistics.DownloadsTotalCount, + v.Version, + v.Statistics.DownloadsTotalCount, Files = v.Files(40, null, null, null, null).Nodes.Select(f => new { f.Name, f.UpdatedAt, f.Size }).ToList() }).ToList() }).Compile(); - var packages = await connection.Run(query); + var packages = await connection.Run(query, cancellationToken: cancellationToken); long totalStorage = 0; - foreach(var package in packages) + foreach (var package in packages) { Console.WriteLine($"{package.Name} ({package.DownloadsTotalCount} downloads)"); foreach (var version in package.Versions) { - foreach(var file in version.Files) + foreach (var file in version.Files) { - if(file.Size != null) + if (file.Size != null) { totalStorage += (int)file.Size; } } - if(version.Files.Count == 1) + if (version.Files.Count == 1) { var file = version.Files[0]; - if(file.Name.Contains(version.Version)) + if (file.Name.Contains(version.Version)) { - System.Console.WriteLine($" {file.Name} ({file.UpdatedAt:d}, {version.DownloadsTotalCount} downloads, {file.Size} bytes)"); + Console.WriteLine($" {file.Name} ({file.UpdatedAt:d}, {version.DownloadsTotalCount} downloads, {file.Size} bytes)"); continue; } } - System.Console.WriteLine($" {version.Version} ({version.DownloadsTotalCount} downloads)"); - foreach(var file in version.Files) + Console.WriteLine($" {version.Version} ({version.DownloadsTotalCount} downloads)"); + foreach (var file in version.Files) { - System.Console.WriteLine($" {file.Name} ({file.UpdatedAt:d}, {file.Size} bytes)"); + Console.WriteLine($" {file.Name} ({file.UpdatedAt:d}, {file.Size} bytes)"); } } } - Console.WriteLine($"Storage used {totalStorage/(1024*1024)} MB"); + Console.WriteLine($"Storage used {totalStorage / (1024 * 1024)} MB"); + + return 0; } [Argument(0, Description = "Path to packages the form `owner`, `owner/repo` or `owner/repo/package`")] @@ -129,69 +134,77 @@ protected override async Task OnExecute(CommandLineApplication app) [Command(Description = "Delete package versions")] public class DeleteCommand : GprCommandBase { - protected override async Task OnExecute(CommandLineApplication app) + protected override async Task OnExecuteAsyncImpl(CommandLineApplication app, + CancellationToken cancellationToken) { var connection = CreateConnection(); - if(PackagesPath is null) + if (PackagesPath is null) { - System.Console.WriteLine("Please include a packages path"); - return; + Console.WriteLine("Please include a packages path"); + return 1; } var packageCollection = await GraphQLUtilities.FindPackageConnection(connection, PackagesPath); - if(packageCollection == null) + if (packageCollection == null) { Console.WriteLine("Couldn't find packages"); - return; + return 1; } - var query = packageCollection.Nodes.Select(p => + var query = packageCollection.Nodes.Select(p => new { - p.Repository.Url, p.Name, + p.Repository.Url, + p.Name, Versions = p.Versions(100, null, null, null, null).Nodes.Select(v => - new + new { - p.Repository.Url, p.Name, - v.Id, v.Version, v.Statistics.DownloadsTotalCount + p.Repository.Url, + p.Name, + v.Id, + v.Version, + v.Statistics.DownloadsTotalCount }).ToList() }).Compile(); - var packages = await connection.Run(query); + var packages = (await connection.Run(query, cancellationToken: cancellationToken)).ToList(); + var packagesDeleted = 0; if (DockerCleanUp) { - foreach(var package in packages) + foreach (var package in packages) { - if(package.Versions.Count == 1 && package.Versions[0] is var version && version.Version == "docker-base-layer") + if (package.Versions.Count == 1 && package.Versions[0] is var version && version.Version == "docker-base-layer") { Console.WriteLine($"Cleaning up '{package.Name}'"); var versionId = version.Id; - var success = await DeletePackageVersion(connection, versionId); + var success = await DeletePackageVersion(connection, versionId, cancellationToken); if (success) { Console.WriteLine($" Deleted '{version.Version}'"); + packagesDeleted++; } } } Console.WriteLine("Complete"); - return; + return 0; } - foreach(var package in packages) + foreach (var package in packages) { Console.WriteLine(package.Name); - foreach(var version in package.Versions) + foreach (var version in package.Versions) { if (Force) { Console.WriteLine($" Deleting '{version.Version}'"); var versionId = version.Id; - await DeletePackageVersion(connection, versionId); + await DeletePackageVersion(connection, versionId, cancellationToken); + packagesDeleted++; } else { @@ -203,20 +216,22 @@ protected override async Task OnExecute(CommandLineApplication app) if (!Force) { Console.WriteLine(); - Console.WriteLine($"To delete these package versions, use the --force option."); + Console.WriteLine("To delete these package versions, use the --force option."); } + + return packagesDeleted == packages.Count ? 0 : 1; } - async Task DeletePackageVersion(IConnection connection, ID versionId) + async Task DeletePackageVersion(IConnection connection, ID versionId, CancellationToken cancellationToken) { try { var input = new DeletePackageVersionInput { PackageVersionId = versionId, ClientMutationId = "GrpTool" }; var mutation = new Mutation().DeletePackageVersion(input).Select(p => p.Success).Compile(); - var payload = await connection.Run(mutation); - return payload.Value; + var payload = await connection.Run(mutation, cancellationToken: cancellationToken); + return payload != null && payload.Value; } - catch(GraphQLException e) + catch (GraphQLException e) { Console.WriteLine(e.Message); return false; @@ -236,7 +251,8 @@ async Task DeletePackageVersion(IConnection connection, ID versionId) [Command(Description = "List packages for user or org (viewer if not specified)")] public class ListCommand : GprCommandBase { - protected override async Task OnExecute(CommandLineApplication app) + protected override async Task OnExecuteAsyncImpl(CommandLineApplication app, + CancellationToken cancellationToken) { var connection = CreateConnection(); @@ -251,12 +267,14 @@ protected override async Task OnExecute(CommandLineApplication app) Console.WriteLine($" {package.Name} ({package.PackageType}) [{string.Join(", ", package.Versions)}] ({package.DownloadsTotalCount} downloads)"); } } + + return 0; } async Task> GetPackages(IConnection connection) { IEnumerable result; - if (PackageOwner is string packageOwner) + if (PackageOwner is { } packageOwner) { var packageConnection = new Query().User(packageOwner).Packages(first: 100); result = await TryGetPackages(connection, packageConnection); @@ -325,7 +343,7 @@ class PackageInfo [Command(Description = "Publish a package")] public class PushCommand : GprCommandBase { - static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int retrySleepSeconds, int timeoutSeconds, CancellationToken cancellationToken) + static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int retrySleepSeconds, int timeoutSeconds) { if (retryNumber <= 0) { @@ -334,8 +352,7 @@ static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int re var retryPolicy = Policy // http://restsharp.org/usage/exceptions.html - .HandleResult(x => !cancellationToken.IsCancellationRequested - && x.StatusCode != HttpStatusCode.Unauthorized + .HandleResult(x => x.StatusCode != HttpStatusCode.Unauthorized && x.StatusCode != HttpStatusCode.Conflict && x.StatusCode != HttpStatusCode.BadRequest && x.StatusCode != HttpStatusCode.OK) @@ -346,7 +363,8 @@ static IAsyncPolicy BuildRetryAsyncPolicy(int retryNumber, int re return Policy.WrapAsync(retryPolicy, timeoutPolicy); } - protected override async Task OnExecute(CommandLineApplication app) + protected override async Task OnExecuteAsyncImpl(CommandLineApplication app, + CancellationToken cancellationToken) { var packageFiles = new List(); var glob = Glob.Parse(PackageFilename); @@ -356,7 +374,7 @@ protected override async Task OnExecute(CommandLineApplication app) if (Version != null && !NuGetVersion.TryParse(Version, out nuGetVersion)) { Console.WriteLine($"Invalid version: {Version}"); - return; + return 1; } if (isGlobPattern) @@ -375,7 +393,7 @@ protected override async Task OnExecute(CommandLineApplication app) if (!packageFiles.Any()) { Console.WriteLine($"Unable to find any packages in directory {searchDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); - return; + return 1; } } else @@ -388,7 +406,7 @@ protected override async Task OnExecute(CommandLineApplication app) if (!File.Exists(packageFile.FilenameAbsolutePath)) { Console.WriteLine($"Package file was not found: {packageFile}"); - return; + return 1; } if (RepositoryUrl == null) @@ -409,7 +427,7 @@ protected override async Task OnExecute(CommandLineApplication app) $"Package filename: {packageFile.FilenameAbsolutePath} " + "Please use --repository option to set a valid upstream GitHub repository. " + "Additional details are available at: https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#repositoryurl"); - return; + return 1; } packageFile.ShouldRewriteNuspec = NuGetUtilities.ShouldRewriteNupkg(packageFile, nuGetVersion); @@ -418,98 +436,103 @@ protected override async Task OnExecute(CommandLineApplication app) const string user = "GprTool"; var token = GetAccessToken(); - using var concurrencySemaphore = new SemaphoreSlim(Math.Max(1, Concurrency)); - Console.WriteLine($"Found {packageFiles.Count} package{(packageFiles.Count > 1 ? "s" : string.Empty)}."); // Retry X times -> // Sleep for X seconds -> // Timeout if the request takes longer than X seconds. - var retryPolicy = BuildRetryAsyncPolicy(Math.Max(0, Retries), 10, 300, CancellationToken); + var retryPolicy = BuildRetryAsyncPolicy(Math.Max(0, Retries), 10, 300); - var uploadPackageTasks = packageFiles.Select(packageFile => - { - return Task.Run(async () => + await packageFiles.ForEachAsync( + (packageFile, packageCancellationToken) => UploadPackageAsync(packageFile, nuGetVersion, token, retryPolicy, packageCancellationToken), + (packageFile, exception) => { - try - { - await concurrencySemaphore.WaitAsync(CancellationToken); + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {exception.Message}"); + }, cancellationToken, Math.Max(1, Concurrency)); - NuGetVersion packageVersion; - if (packageFile.ShouldRewriteNuspec) - { - NuGetUtilities.RewriteNupkg(packageFile, nuGetVersion); + static async Task UploadPackageAsync(PackageFile packageFile, + NuGetVersion nuGetVersion, string token, IAsyncPolicy retryPolicy, CancellationToken cancellationToken) + { + if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); + if (nuGetVersion == null) throw new ArgumentNullException(nameof(nuGetVersion)); - var manifest = NuGetUtilities.ReadNupkgManifest(packageFile.FilenameAbsolutePath); - packageVersion = manifest.Metadata.Version; - } - else - { - var manifest = NuGetUtilities.ReadNupkgManifest(packageFile.FilenameAbsolutePath); - packageVersion = manifest.Metadata.Version; - } + cancellationToken.ThrowIfCancellationRequested(); - await using var packageStream = packageFile.FilenameAbsolutePath.ReadSharedToStream(); + NuGetVersion packageVersion; + if (packageFile.ShouldRewriteNuspec) + { + NuGetUtilities.RewriteNupkg(packageFile, nuGetVersion); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: " + - $"Repository url: {packageFile.RepositoryUrl}. " + - $"Version: {packageVersion}. " + - $"Size: {packageStream.Length} bytes. "); + var manifest = NuGetUtilities.ReadNupkgManifest(packageFile.FilenameAbsolutePath); + packageVersion = manifest.Metadata.Version; + } + else + { + var manifest = NuGetUtilities.ReadNupkgManifest(packageFile.FilenameAbsolutePath); + packageVersion = manifest.Metadata.Version; + } - - return await retryPolicy.ExecuteAsync(() => UploadPackageAsync(packageStream)); - } - finally - { - concurrencySemaphore.Release(); - } - }); + await using var packageStream = packageFile.FilenameAbsolutePath.ReadSharedToStream(); - async Task UploadPackageAsync(MemoryStream packageStream) - { - if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: " + + $"Repository url: {packageFile.RepositoryUrl}. " + + $"Version: {packageVersion}. " + + $"Size: {packageStream.Length} bytes. "); + + await retryPolicy.ExecuteAndCaptureAsync(retryCancellationToken => + UploadPackageAsyncImpl(packageFile, packageStream, token, retryCancellationToken), cancellationToken); + } - var request = new RestRequest(Method.PUT); + static async Task UploadPackageAsyncImpl(PackageFile packageFile, MemoryStream packageStream, string token, + CancellationToken cancellationToken) + { + if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); + if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); - var client = WithRestClient($"https://nuget.pkg.github.com/{packageFile.Owner}/", x => + var client = WithRestClient($"https://nuget.pkg.github.com/{packageFile.Owner}/", + x => { x.Authenticator = new HttpBasicAuthenticator(user, token); }); - packageStream.Seek(0, SeekOrigin.Begin); + var request = new RestRequest(Method.PUT); - request.AddFile("package", packageStream.CopyTo, packageFile.FilenameWithoutGprPrefix, packageStream.Length); + packageStream.Seek(0, SeekOrigin.Begin); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: Uploading package."); - - var response = await client.ExecuteAsync(request, CancellationToken); + cancellationToken.ThrowIfCancellationRequested(); - if (response.StatusCode == HttpStatusCode.OK) - { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.Content}"); - goto done; - } + request.AddFile("package", packageStream.CopyTo, packageFile.FilenameWithoutGprPrefix, packageStream.Length); - var nugetWarning = response.Headers.FirstOrDefault(h => - h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); - if (nugetWarning != null) - { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {nugetWarning.Value}"); - goto done; - } + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: Uploading package."); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.StatusDescription}"); - foreach (var header in response.Headers) - { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {header.Name}: {header.Value}"); - } + var response = await client.ExecuteAsync(request, cancellationToken); + + packageFile.IsUploaded = response.StatusCode == HttpStatusCode.OK; + + if (packageFile.IsUploaded) + { + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.Content}"); + return response; + } - done: + var nugetWarning = response.Headers.FirstOrDefault(h => + h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); + if (nugetWarning != null) + { + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {nugetWarning.Value}"); return response; } - }); - await Task.WhenAll(uploadPackageTasks); + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.StatusDescription}"); + foreach (var header in response.Headers) + { + Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {header.Name}: {header.Value}"); + } + + return response; + } + + return packageFiles.All(x => x.IsUploaded) ? 0 : 1; } [Argument(0, Description = "Path to the package file")] @@ -531,21 +554,21 @@ async Task UploadPackageAsync(MemoryStream packageStream) [Command(Description = "View package details")] public class DetailsCommand : GprCommandBase { - protected override Task OnExecute(CommandLineApplication app) + protected override async Task OnExecuteAsyncImpl(CommandLineApplication app, CancellationToken cancellationToken) { var user = "GprTool"; var token = GetAccessToken(); var client = WithRestClient($"https://nuget.pkg.github.com/{Owner}/{Name}/{Version}.json"); client.Authenticator = new HttpBasicAuthenticator(user, token); var request = new RestRequest(Method.GET); - var response = client.Execute(request); + var response = await client.ExecuteAsync(request, cancellationToken: cancellationToken); if (response.StatusCode == HttpStatusCode.OK) { var doc = JsonDocument.Parse(response.Content); var json = JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }); Console.WriteLine(json); - return Task.CompletedTask; + return 0; } var nugetWarning = response.Headers.FirstOrDefault(h => @@ -553,7 +576,7 @@ protected override Task OnExecute(CommandLineApplication app) if (nugetWarning != null) { Console.WriteLine(nugetWarning.Value); - return Task.CompletedTask; + return 1; } Console.WriteLine(response.StatusDescription); @@ -561,7 +584,8 @@ protected override Task OnExecute(CommandLineApplication app) { Console.WriteLine($"{header.Name}: {header.Value}"); } - return Task.CompletedTask; + + return 1; } [Argument(0, Description = "Package owner")] @@ -577,7 +601,7 @@ protected override Task OnExecute(CommandLineApplication app) [Command(Name = "setApiKey", Description = "Set GitHub API key/personal access token")] public class SetApiKeyCommand : GprCommandBase { - protected override Task OnExecute(CommandLineApplication app) + protected override async Task OnExecuteAsyncImpl(CommandLineApplication app, CancellationToken cancellationToken) { var configFile = ConfigFile ?? NuGetUtilities.GetDefaultConfigFile(Warning); var source = PackageSource ?? "github"; @@ -589,19 +613,19 @@ protected override Task OnExecute(CommandLineApplication app) Console.WriteLine($"Target confile file is '{configFile}':"); if (File.Exists(configFile)) { - Console.WriteLine(File.ReadAllText(configFile)); + Console.WriteLine(await File.ReadAllTextAsync(configFile, cancellationToken)); } else { - Console.WriteLine($"There is currently no file at this location."); + Console.WriteLine("There is currently no file at this location."); } - return Task.CompletedTask; + return 1; } NuGetUtilities.SetApiKey(configFile, ApiKey, source, Warning); - return Task.CompletedTask; + return 0; } [Argument(0, Description = "Token / API key")] @@ -617,12 +641,12 @@ protected override Task OnExecute(CommandLineApplication app) [Command(Name = "encode", Description = "Encode PAT to prevent it from being automatically deleted by GitHub")] public class EncodeCommand : GprCommandBase { - protected override Task OnExecute(CommandLineApplication app) + protected override Task OnExecuteAsyncImpl(CommandLineApplication app, CancellationToken cancellationToken) { if (Token == null) { Console.WriteLine("No token was specified"); - return Task.CompletedTask; + return Task.FromResult(1); } Console.WriteLine("An encoded token can be included in a public repository without being automatically deleted by GitHub."); @@ -650,14 +674,14 @@ protected override Task OnExecute(CommandLineApplication app) "); var unicodeEncode = UnicodeEncode(Token); - + Console.WriteLine(); Console.WriteLine("An npm `.npmrc` file:"); Console.WriteLine("@OWNER:registry=https://npm.pkg.github.com"); Console.WriteLine($"//npm.pkg.github.com/:_authToken=\"{unicodeEncode}\""); Console.WriteLine(); - return Task.CompletedTask; + return Task.FromResult(0); } static string XmlEncode(string str) @@ -682,22 +706,35 @@ static string UnicodeEncode(string str) [HelpOption("--help")] public abstract class GprCommandBase { - readonly CancellationTokenSource _cancellationTokenSource; + static long SetupCancelKeyPress = 1; - protected string AssemblyProduct => Assembly.GetExecutingAssembly().GetCustomAttribute()?.Product; - protected string AssemblyInformationalVersion => ThisAssembly.AssemblyInformationalVersion; - protected CancellationToken CancellationToken => _cancellationTokenSource.Token; - - protected abstract Task OnExecute(CommandLineApplication app); + protected static string AssemblyProduct => Assembly.GetExecutingAssembly().GetCustomAttribute()?.Product; + protected static string AssemblyInformationalVersion => ThisAssembly.AssemblyInformationalVersion; - protected GprCommandBase() + [SuppressMessage("ReSharper", "UnusedMember.Global")] + protected async Task OnExecuteAsync(CommandLineApplication app, CancellationToken cancellationToken) { - _cancellationTokenSource = new CancellationTokenSource(); - Console.CancelKeyPress += (sender, args) => _cancellationTokenSource.Cancel(); + // Cancel this command's tasks before returning to CommandLineApplication's context. + // Otherwise the application will hang forever. + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + if (Interlocked.Exchange(ref SetupCancelKeyPress, 0) == 1) + { + Console.CancelKeyPress += (sender, args) => + { + cts.Cancel(); + }; + } + + return await OnExecuteAsyncImpl(app, cts.Token); } - protected RestClient WithRestClient(string baseUrl, Action builderAction = null) - { + [SuppressMessage("ReSharper", "UnusedMember.Global")] + protected abstract Task OnExecuteAsyncImpl(CommandLineApplication app, CancellationToken cancellationToken); + + protected static RestClient WithRestClient(string baseUrl, Action builderAction = null) + { var restClient = new RestClient(baseUrl) { UserAgent = $"{AssemblyProduct}/{AssemblyInformationalVersion}" @@ -709,7 +746,7 @@ protected RestClient WithRestClient(string baseUrl, Action builderAc protected IConnection CreateConnection() { var productInformation = new ProductHeaderValue(AssemblyProduct, AssemblyInformationalVersion); - + var token = GetAccessToken(); var connection = new Connection(productInformation, new Uri("https://api.github.com/graphql"), token); From 7125edb1c4c837fa78c1dd7c2a054b95e0c94eee Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 17:27:29 +0200 Subject: [PATCH 19/40] Update src/GprTool/Program.cs Co-authored-by: Jamie Cansdale --- src/GprTool/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 5fc65c3..863f368 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -454,7 +454,6 @@ static async Task UploadPackageAsync(PackageFile packageFile, NuGetVersion nuGetVersion, string token, IAsyncPolicy retryPolicy, CancellationToken cancellationToken) { if (packageFile == null) throw new ArgumentNullException(nameof(packageFile)); - if (nuGetVersion == null) throw new ArgumentNullException(nameof(nuGetVersion)); cancellationToken.ThrowIfCancellationRequested(); From 236e99b185c0a9117321b01c1a0eca323dbbe84b Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 17:46:00 +0200 Subject: [PATCH 20/40] Add environment variable for debugging. The process will wait until debugger is attached. --- src/GprTool/Program.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 863f368..2949050 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; @@ -34,6 +35,22 @@ public class Program : GprCommandBase { public static async Task Main(string[] args) { + var gprWaitDebugger = Environment.GetEnvironmentVariable("GPR_WAIT_DEBUGGER")?.Trim(); + if (string.Equals("1", gprWaitDebugger) || + string.Equals("true", gprWaitDebugger, StringComparison.OrdinalIgnoreCase)) + { + var processId = Process.GetCurrentProcess().Id; + while (!Debugger.IsAttached) + { + Console.WriteLine($"Waiting for debugger to attach. Process id: {processId}."); + Thread.Sleep(1000); + } + + Console.WriteLine("Debugger is attached."); + + Debugger.Break(); + } + try { return await CommandLineApplication.ExecuteAsync(args); From 6b0c32438853a1bb98adbef04e5720e5e21932a9 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 17:47:12 +0200 Subject: [PATCH 21/40] Optimize package search by moving check if nupkg should be written inside UploadPackageAsync task. --- src/GprTool/Program.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 2949050..89c40be 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -418,6 +418,8 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app packageFiles.Add(NuGetUtilities.BuildPackageFile(Path.GetFullPath(PackageFilename), RepositoryUrl)); } + Console.WriteLine($"Found {packageFiles.Count} package{(packageFiles.Count > 1 ? "s" : string.Empty)}."); + foreach (var packageFile in packageFiles) { if (!File.Exists(packageFile.FilenameAbsolutePath)) @@ -446,15 +448,11 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app "Additional details are available at: https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#repositoryurl"); return 1; } - - packageFile.ShouldRewriteNuspec = NuGetUtilities.ShouldRewriteNupkg(packageFile, nuGetVersion); } const string user = "GprTool"; var token = GetAccessToken(); - Console.WriteLine($"Found {packageFiles.Count} package{(packageFiles.Count > 1 ? "s" : string.Empty)}."); - // Retry X times -> // Sleep for X seconds -> // Timeout if the request takes longer than X seconds. @@ -475,7 +473,10 @@ static async Task UploadPackageAsync(PackageFile packageFile, cancellationToken.ThrowIfCancellationRequested(); NuGetVersion packageVersion; - if (packageFile.ShouldRewriteNuspec) + + var shouldRewriteNuspec = NuGetUtilities.ShouldRewriteNupkg(packageFile, nuGetVersion); + + if (shouldRewriteNuspec) { NuGetUtilities.RewriteNupkg(packageFile, nuGetVersion); From 894908615d6b583ca677c97d81ab9f50c9f80639 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 17:47:22 +0200 Subject: [PATCH 22/40] Remove unused variable. --- src/GprTool/NuGetUtilities.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 13f772c..126d4b0 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -14,7 +14,6 @@ public class PackageFile public string Owner { get; set; } public string RepositoryName { get; set; } public string RepositoryUrl { get; set; } - public bool ShouldRewriteNuspec { get; set; } public bool IsNuspecRewritten { get; set; } public bool IsUploaded { get; set; } From 063f22f6acb739f94ac997720477cd67a8d84611 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 17:48:07 +0200 Subject: [PATCH 23/40] R# --- src/GprTool/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 89c40be..dc017f4 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -401,9 +401,9 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app packageFiles.AddRange(Directory .GetFiles(searchDirectory, "*.*", SearchOption.AllDirectories) - .Where(x => - x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) - || x.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) + .Where(filename => + filename.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) + || filename.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) .Where(filename => glob.IsMatch(filename)) .Select(filename => NuGetUtilities.BuildPackageFile(filename, RepositoryUrl))); From daf0551d1acab23a7737479416800fd40e50dd90 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 18:19:27 +0200 Subject: [PATCH 24/40] If PackageFilename is a directory then we should by default upload all .nupkg, .snupkg files found in current working directory. --- src/GprTool/Program.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index dc017f4..c4fcf25 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -386,6 +386,8 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app var packageFiles = new List(); var glob = Glob.Parse(PackageFilename); var isGlobPattern = glob.IsGlobPattern(); + var currentDirectory = Directory.GetCurrentDirectory(); + var isPackageFilenameADirectory = !isGlobPattern && Directory.Exists(PackageFilename); NuGetVersion nuGetVersion = null; if (Version != null && !NuGetVersion.TryParse(Version, out nuGetVersion)) @@ -394,22 +396,21 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app return 1; } - if (isGlobPattern) + if (isGlobPattern || isPackageFilenameADirectory) { - var baseDirectory = Directory.GetCurrentDirectory(); - var searchDirectory = glob.BuildBasePathFromGlob(baseDirectory); - packageFiles.AddRange(Directory - .GetFiles(searchDirectory, "*.*", SearchOption.AllDirectories) + .GetFiles(currentDirectory, "*.*", SearchOption.AllDirectories) .Where(filename => filename.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) || filename.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) - .Where(filename => glob.IsMatch(filename)) + .Where(filename => isPackageFilenameADirectory || glob.IsMatch(filename)) .Select(filename => NuGetUtilities.BuildPackageFile(filename, RepositoryUrl))); if (!packageFiles.Any()) { - Console.WriteLine($"Unable to find any packages in directory {searchDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); + Console.WriteLine(isPackageFilenameADirectory + ? $"Unable to find any packages in directory {currentDirectory}. Valid filename extensions are .nupkg, .snupkg." + : $"Unable to find any packages in directory {currentDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); return 1; } } From 4a8df4c023a595046626b9589e944922084776c6 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 18:34:29 +0200 Subject: [PATCH 25/40] Implicitly cast workingdirectory to string. --- src/GprTool/NuGetUtilities.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 126d4b0..11ce682 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -161,14 +161,14 @@ public static void RewriteNupkg(PackageFile packageFile, NuGetVersion nuGetVersi using var tmpDirectory = new DisposableDirectory(packageFileRewriteWorkingDirectory); - ZipFile.ExtractToDirectory(packageFile.FilenameAbsolutePath, tmpDirectory.WorkingDirectory); + ZipFile.ExtractToDirectory(packageFile.FilenameAbsolutePath, tmpDirectory); - var nuspecDstFilename = Path.Combine(tmpDirectory.WorkingDirectory, $"{packageId}.nuspec"); + var nuspecDstFilename = Path.Combine(tmpDirectory, $"{packageId}.nuspec"); File.WriteAllBytes(nuspecDstFilename, nuspecMemoryStream.ToArray()); using var outputStream = new MemoryStream(); - var packageBuilder = new PackageBuilder(nuspecMemoryStream, tmpDirectory.WorkingDirectory, + var packageBuilder = new PackageBuilder(nuspecMemoryStream, tmpDirectory, propertyProvider => throw new NotImplementedException()); packageBuilder.Save(outputStream); @@ -279,6 +279,8 @@ public class DisposableDirectory : IDisposable { public string WorkingDirectory { get; } + public static implicit operator string (DisposableDirectory directory) => directory.WorkingDirectory; + public DisposableDirectory(string workingDirectory) { WorkingDirectory = workingDirectory; From 623bfceb6c08da3459bf825fe28e29782c71116b Mon Sep 17 00:00:00 2001 From: Jamie Cansdale Date: Tue, 16 Jun 2020 20:45:52 +0100 Subject: [PATCH 26/40] Use full path in glob --- src/GprTool/Program.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index c4fcf25..af7c08c 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -384,7 +384,8 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app CancellationToken cancellationToken) { var packageFiles = new List(); - var glob = Glob.Parse(PackageFilename); + var packageFullPath = Path.GetFullPath(PackageFilename); + var glob = Glob.Parse(packageFullPath); var isGlobPattern = glob.IsGlobPattern(); var currentDirectory = Directory.GetCurrentDirectory(); var isPackageFilenameADirectory = !isGlobPattern && Directory.Exists(PackageFilename); @@ -416,7 +417,7 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app } else { - packageFiles.Add(NuGetUtilities.BuildPackageFile(Path.GetFullPath(PackageFilename), RepositoryUrl)); + packageFiles.Add(NuGetUtilities.BuildPackageFile(packageFullPath, RepositoryUrl)); } Console.WriteLine($"Found {packageFiles.Count} package{(packageFiles.Count > 1 ? "s" : string.Empty)}."); From 04fd2b45cb15bff57ad1e0973a0c4653c7cb3782 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 23:37:05 +0200 Subject: [PATCH 27/40] Refactor: Expand path before parsing glob pattern. Added test cases for expanding both relative and absolute glob patterns. --- src/GprTool/GlobExtensions.cs | 69 ++++++----- src/GprTool/IoExtensions.cs | 37 +++++- src/GprTool/Program.cs | 34 ++---- test/GprTool.Tests/GlobExtensionTests.cs | 41 +++---- test/GprTool.Tests/IoExtensionsTests.cs | 139 +++++++++++++++++++++++ 5 files changed, 243 insertions(+), 77 deletions(-) create mode 100644 test/GprTool.Tests/IoExtensionsTests.cs diff --git a/src/GprTool/GlobExtensions.cs b/src/GprTool/GlobExtensions.cs index eee2bcf..d947652 100644 --- a/src/GprTool/GlobExtensions.cs +++ b/src/GprTool/GlobExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -14,55 +15,69 @@ public static bool IsGlobPattern(this Glob glob) return glob.Tokens.Any(x => !(x is PathSeparatorToken || x is LiteralToken)); } - public static string BuildBasePathFromGlob(this Glob glob, string baseDirectory = null) + public static string BuildBasePathFromGlob(this Glob glob, string baseDirectory) { if (glob == null) throw new ArgumentNullException(nameof(glob)); + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - var tokensLength = glob.Tokens.Length; + var globTokens = glob.Tokens.ToList(); + var pathTokens = new List(); - var path = new StringBuilder(); - - for (var index = 0; index < tokensLength; index++) + for (var index = 0; index < globTokens.Count; index++) { var token = glob.Tokens[index]; - var tokenNext = index + 1 < tokensLength ? glob.Tokens[index + 1] : null; - var tokenPrevious = index - 1 >= 0 ? glob.Tokens[index - 1] : null; - var tokenPreviousPrevious = index - 2 >= 0 ? glob.Tokens[index - 2] : null; - + var tokenNext = index + 1 < globTokens.Count ? glob.Tokens[index + 1] : null; + switch (token) { - case PathSeparatorToken pathSeparatorToken when(tokenNext is LiteralToken): - path.Append(pathSeparatorToken.Value); - break; - case LiteralToken literalToken: - - if (tokenPrevious is WildcardToken - || tokenPreviousPrevious is WildcardDirectoryToken) + case LiteralToken _: + pathTokens.Add(token); + if (tokenNext is AnyCharacterToken + || tokenNext is INegatableToken + && pathTokens.Any()) { + pathTokens.RemoveAt(pathTokens.Count - 1); goto done; } + continue; + case PathSeparatorToken _ when (tokenNext is LiteralToken): + pathTokens.Add(token); + continue; + } - path.Append(literalToken.Value); + goto done; + } - if (tokenNext is WildcardToken - || tokenNext is WildcardDirectoryToken) - { - goto done; - } + done: + + var pathStringBuilder = new StringBuilder(); + foreach (var token in pathTokens) + { + switch (token) + { + case LiteralToken literalToken: + pathStringBuilder.Append(literalToken.Value); break; + case PathSeparatorToken _: // xplat + pathStringBuilder.Append("/"); + break; + default: + throw new NotSupportedException(token.GetType().FullName); } } - done: + var pathStr = pathStringBuilder.ToString(); - var pathStr = path.ToString(); - if (baseDirectory != null && !Path.IsPathRooted(pathStr)) + // Remove trailing backward/forward slash + var lastAppendedGlobToken = pathTokens.LastOrDefault(); + if (lastAppendedGlobToken is PathSeparatorToken + && pathStr.Sum(x => x == '/' ? 1 : 0) > 1) { - return Path.GetFullPath(pathStr, baseDirectory); + pathStr = pathStr.Substring(0, pathStr.Length - 1); } - return Path.GetFullPath(pathStr); + return !Path.IsPathRooted(pathStr) ? Path.GetFullPath(pathStr, baseDirectory) : Path.GetFullPath(pathStr); } } } diff --git a/src/GprTool/IoExtensions.cs b/src/GprTool/IoExtensions.cs index efb3da9..9f65afb 100644 --- a/src/GprTool/IoExtensions.cs +++ b/src/GprTool/IoExtensions.cs @@ -1,9 +1,44 @@ -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DotNet.Globbing; namespace GprTool { public static class IoExtensions { + public static IEnumerable GetFilesByGlobPattern(this string baseDirectory, string globPattern, out Glob outGlob) + { + var baseDirectoryGlobPattern = Path.GetFullPath(Path.Combine(baseDirectory, globPattern)); + + if (string.Equals(".", globPattern)) + { + globPattern = Path.GetFullPath(Path.Combine(baseDirectory, "*.*")); + } else if (Directory.Exists(baseDirectoryGlobPattern)) + { + globPattern = Path.GetFullPath(Path.Combine(baseDirectoryGlobPattern, "*.*")); + } else if (File.Exists(baseDirectoryGlobPattern)) + { + globPattern = Path.GetFullPath(baseDirectoryGlobPattern); + } + + var basePathFromGlob = Path.GetDirectoryName(Glob.Parse(globPattern).BuildBasePathFromGlob(baseDirectory)); + + var glob = Path.IsPathRooted(globPattern) + ? Glob.Parse(globPattern) + : Glob.Parse(baseDirectoryGlobPattern); + + outGlob = glob; + + return Directory + .GetFiles(basePathFromGlob, "*.*", SearchOption.AllDirectories) + .Where(filename => + filename.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) + || filename.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) + .Where(filename => glob.IsMatch(filename)); + } + public static FileStream OpenReadShared(this string filename) { return File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read); diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index af7c08c..13d342b 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -384,11 +384,6 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app CancellationToken cancellationToken) { var packageFiles = new List(); - var packageFullPath = Path.GetFullPath(PackageFilename); - var glob = Glob.Parse(packageFullPath); - var isGlobPattern = glob.IsGlobPattern(); - var currentDirectory = Directory.GetCurrentDirectory(); - var isPackageFilenameADirectory = !isGlobPattern && Directory.Exists(PackageFilename); NuGetVersion nuGetVersion = null; if (Version != null && !NuGetVersion.TryParse(Version, out nuGetVersion)) @@ -397,27 +392,16 @@ protected override async Task OnExecuteAsyncImpl(CommandLineApplication app return 1; } - if (isGlobPattern || isPackageFilenameADirectory) - { - packageFiles.AddRange(Directory - .GetFiles(currentDirectory, "*.*", SearchOption.AllDirectories) - .Where(filename => - filename.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) - || filename.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) - .Where(filename => isPackageFilenameADirectory || glob.IsMatch(filename)) - .Select(filename => NuGetUtilities.BuildPackageFile(filename, RepositoryUrl))); + var currentDirectory = Directory.GetCurrentDirectory(); + packageFiles.AddRange( + currentDirectory + .GetFilesByGlobPattern(GlobPattern, out var glob) + .Select(x => NuGetUtilities.BuildPackageFile(x, RepositoryUrl))); - if (!packageFiles.Any()) - { - Console.WriteLine(isPackageFilenameADirectory - ? $"Unable to find any packages in directory {currentDirectory}. Valid filename extensions are .nupkg, .snupkg." - : $"Unable to find any packages in directory {currentDirectory} matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); - return 1; - } - } - else + if (!packageFiles.Any()) { - packageFiles.Add(NuGetUtilities.BuildPackageFile(packageFullPath, RepositoryUrl)); + Console.WriteLine($"Unable to find any packages matching glob pattern: {glob}. Valid filename extensions are .nupkg, .snupkg."); + return 1; } Console.WriteLine($"Found {packageFiles.Count} package{(packageFiles.Count > 1 ? "s" : string.Empty)}."); @@ -555,7 +539,7 @@ static async Task UploadPackageAsyncImpl(PackageFile packageFile, } [Argument(0, Description = "Path to the package file")] - public string PackageFilename { get; set; } + public string GlobPattern { get; set; } [Option("-r|--repository", Description = "Override current nupkg repository url. Format: owner/repository. E.g: jcansdale/gpr")] public string RepositoryUrl { get; set; } diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index fbde442..c43bc98 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -24,31 +24,24 @@ public void IsGlobPattern(string path, bool isGlobPattern) Assert.That(glob.IsGlobPattern(), Is.EqualTo(isGlobPattern)); } - [TestCase("./packages/**/*.nupkg", "c:\\test\\packages")] - [TestCase(".\\packages/**/*.nupkg", "c:\\test\\packages")] - [TestCase("packages", "c:\\test\\packages")] - [TestCase("c:\\test.nupkg", "c:\\test.nupkg")] - [TestCase("c:\\test", "c:\\test")] - [TestCase("c:\\test?", "c:\\test")] - [TestCase("c:\\test?\\**", "c:\\test")] - [TestCase("c:\\test?\\[abc]\\**", "c:\\test")] - [TestCase("c:\\test\\*.*", "c:\\test")] - [TestCase("c:\\test\\**", "c:\\test")] - [TestCase("c:\\test\\**\\*.nupkg", "c:\\test")] - [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\test\\subdirectory")] - [TestCase("c:\\test\\**\\subdirectory\\**\\*.nupkg", "c:\\test")] - public void BuildBasePathFromGlob(string path, string expectedBaseDirectory) + [TestCase(".", "c:\\", "c:\\", Description = "Relative path is directory")] + [TestCase("packages", "c:\\", "c:\\packages", Description = "Relative path is directory")] + [TestCase("test.nupkg", "c:\\", "c:\\test.nupkg", Description = "Relative path is filename")] + [TestCase("packages\\**\\*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase("packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase("./packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase(".\\packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase("c:\\test?", "c:\\", "c:\\")] + [TestCase("c:\\test?\\**", "c:\\", "c:\\")] + [TestCase("c:\\test?\\[abc]\\**", "c:\\", "c:\\")] + [TestCase("c:\\test\\*.*", "c:\\", "c:\\test")] + [TestCase("c:\\test\\**", "c:\\", "c:\\test")] + [TestCase("c:\\test\\**\\*.nupkg", "c:\\", "c:\\test")] + [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\", "c:\\test\\subdirectory")] + [TestCase("c:\\test\\**\\subdirectory\\**\\*.nupkg", "c:\\", "c:\\test")] + public void BuildBasePathFromGlob(string path, string baseDirectory, string expectedBaseDirectory) { var glob = Glob.Parse(path); - Assert.That(glob.BuildBasePathFromGlob("c:\\test"), Is.EqualTo(expectedBaseDirectory)); - } - - [TestCase("packages/**/*.nupkg", "c:\\test", "c:\\test\\packages")] - [TestCase("packages\\**\\*.nupkg", "c:\\test", "c:\\test\\packages")] - [TestCase("packages", "c:\\test", "c:\\test\\packages")] - public void BuildBasePathFromGlob_Uses_BaseDirectory_When_Path_Is_Not_Rooted(string relativePath, string baseDirectory, string expectedPath) - { - var glob = Glob.Parse(relativePath); - Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedPath)); + Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedBaseDirectory)); } } \ No newline at end of file diff --git a/test/GprTool.Tests/IoExtensionsTests.cs b/test/GprTool.Tests/IoExtensionsTests.cs new file mode 100644 index 0000000..1ce1fde --- /dev/null +++ b/test/GprTool.Tests/IoExtensionsTests.cs @@ -0,0 +1,139 @@ +using System; +using System.IO; +using System.Linq; +using NUnit.Framework; + +namespace GprTool.Tests +{ + [TestFixture] + class IoExtensionsTests + { + [TestCase("nupkg", "*.*", 2)] + [TestCase("nupkg/**/*.nupkg", "**/*.nupkg", 1)] + [TestCase("nupkg/**/*.snupkg", "**/*.snupkg", 1)] + [TestCase("nupkg/**/*.*", "**/*.*", 2)] + [TestCase("nupkg/**/**", "**/**", 2)] + public void GetFilesByGlobPattern_Is_Relative_Directory(string globPattern, string expectedGlobPattern, int expectedFilesCount) + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgDirectory = Path.Combine(tmpDirectory, "nupkg"); + Directory.CreateDirectory(nupkgDirectory); + + var nupkgAbsoluteFilename = Path.Combine(nupkgDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(nupkgDirectory, "test.snupkg"); + + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(globPattern, out var glob).ToList(); + var pattern = glob.ToString().Substring(nupkgDirectory.Length).Replace("\\", "/"); + if (pattern[0] == '/') + { + pattern = pattern.Substring(1); + } + Assert.That(pattern, Is.EqualTo(expectedGlobPattern)); + Assert.That(packages.Count, Is.EqualTo(expectedFilesCount)); + } + + [TestCase("nupkg", "*.*", 2)] + [TestCase("nupkg/**/*.nupkg", "**/*.nupkg", 1)] + [TestCase("nupkg/**/*.snupkg", "**/*.snupkg", 1)] + [TestCase("nupkg/**/*.*", "**/*.*", 2)] + [TestCase("nupkg/**/**", "**/**", 2)] + public void GetFilesByGlobPattern_Is_FullPath_Directory(string globPattern, string expectedGlobPattern, int expectedFilesCount) + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgDirectory = Path.Combine(tmpDirectory, "nupkg"); + Directory.CreateDirectory(nupkgDirectory); + + var nupkgAbsoluteFilename = Path.Combine(nupkgDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(nupkgDirectory, "test.snupkg"); + + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(Path.Combine(tmpDirectory, globPattern), out var glob).ToList(); + var pattern = glob.ToString().Substring(nupkgDirectory.Length).Replace("\\", "/"); + if (pattern[0] == '/') + { + pattern = pattern.Substring(1); + } + Assert.That(pattern, Is.EqualTo(expectedGlobPattern)); + Assert.That(packages.Count, Is.EqualTo(expectedFilesCount)); + } + + [Test] + public void GetFilesByGlobPattern_Is_Dot_Directory() + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.snupkg"); + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(".", out var glob).ToList(); + + var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); + if (globPattern[0] == '/') + { + globPattern = globPattern.Substring(1); + } + + Assert.That(globPattern, Is.EqualTo(globPattern)); + Assert.That(packages.Count, Is.EqualTo(2)); + Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); + } + + [Test] + public void GetFilesByGlobPattern_Is_Relative_Filename() + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.snupkg"); + + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern("test.nupkg", out var glob).ToList(); + + var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); + if (globPattern[0] == '/') + { + globPattern = globPattern.Substring(1); + } + + Assert.That(globPattern, Is.EqualTo("test.nupkg")); + Assert.That(packages.Count, Is.EqualTo(1)); + Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + } + + [Test] + public void GetFilesByGlobPattern_Is_FullPath_Filename() + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.snupkg"); + + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(nupkgAbsoluteFilename, out var glob).ToList(); + + var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); + if (globPattern[0] == '/') + { + globPattern = globPattern.Substring(1); + } + + Assert.That(globPattern, Is.EqualTo("test.nupkg")); + Assert.That(packages.Count, Is.EqualTo(1)); + Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + } + } +} From 8fd11e62f311d385efe71715ece8bef8056f7520 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 23:47:56 +0200 Subject: [PATCH 28/40] Add additional test cases. --- test/GprTool.Tests/IoExtensionsTests.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/GprTool.Tests/IoExtensionsTests.cs b/test/GprTool.Tests/IoExtensionsTests.cs index 1ce1fde..df4c7e3 100644 --- a/test/GprTool.Tests/IoExtensionsTests.cs +++ b/test/GprTool.Tests/IoExtensionsTests.cs @@ -8,6 +8,14 @@ namespace GprTool.Tests [TestFixture] class IoExtensionsTests { + [TestCase("./nupkg", "*.*", 2)] + [TestCase("./nupkg/**/*.*", "**/*.*", 2)] + [TestCase("./nupkg/**/**", "**/**", 2)] + [TestCase("./nupkg/**/*.nupkg", "**/*.nupkg", 1)] + [TestCase(".\\nupkg", "*.*", 2)] + [TestCase(".\\nupkg\\**\\*.*", "**/*.*", 2)] + [TestCase(".\\nupkg\\**\\**", "**/**", 2)] + [TestCase(".\\nupkg\\**\\*.nupkg", "**/*.nupkg", 1)] [TestCase("nupkg", "*.*", 2)] [TestCase("nupkg/**/*.nupkg", "**/*.nupkg", 1)] [TestCase("nupkg/**/*.snupkg", "**/*.snupkg", 1)] @@ -36,6 +44,8 @@ public void GetFilesByGlobPattern_Is_Relative_Directory(string globPattern, stri Assert.That(packages.Count, Is.EqualTo(expectedFilesCount)); } + [TestCase("./nupkg", "*.*", 2)] + [TestCase(".\\nupkg", "*.*", 2)] [TestCase("nupkg", "*.*", 2)] [TestCase("nupkg/**/*.nupkg", "**/*.nupkg", 1)] [TestCase("nupkg/**/*.snupkg", "**/*.snupkg", 1)] @@ -88,8 +98,10 @@ public void GetFilesByGlobPattern_Is_Dot_Directory() Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); } - [Test] - public void GetFilesByGlobPattern_Is_Relative_Filename() + [TestCase("test.nupkg")] + [TestCase("./test.nupkg")] + [TestCase(".\\test.nupkg")] + public void GetFilesByGlobPattern_Is_Relative_Filename(string relativeFilename) { using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); @@ -99,7 +111,7 @@ public void GetFilesByGlobPattern_Is_Relative_Filename() File.WriteAllText(nupkgAbsoluteFilename, string.Empty); File.WriteAllText(snupkgAbsoluteFilename, string.Empty); - var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern("test.nupkg", out var glob).ToList(); + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(relativeFilename, out var glob).ToList(); var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); if (globPattern[0] == '/') From be0e251cbc3287c03d2b90d333d5cbd651aa5b5f Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Tue, 16 Jun 2020 23:57:30 +0200 Subject: [PATCH 29/40] Update nuget packages. Maybe consider enabling depandabot? /cc @jcansdale --- Directory.Build.props | 2 +- src/GprTool/GprTool.csproj | 2 +- test/GprTool.Tests/GprTool.Tests.csproj | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6133196..faca85f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - 3.0.50 + 3.1.91 all diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index 4b9da83..4558c7a 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -27,7 +27,7 @@ - + diff --git a/test/GprTool.Tests/GprTool.Tests.csproj b/test/GprTool.Tests/GprTool.Tests.csproj index e23f756..3fbef84 100644 --- a/test/GprTool.Tests/GprTool.Tests.csproj +++ b/test/GprTool.Tests/GprTool.Tests.csproj @@ -8,8 +8,12 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + From b94c540c581c2118581a743cad1262908e74ebab Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 00:14:10 +0200 Subject: [PATCH 30/40] Enable running tests via commandline: dotnet test . --logger:nunit /p:IsRunningTests=True --- .gitignore | 3 +- Directory.Build.props | 14 +- src/GprTool/GprTool.csproj | 4 + src/GprTool/Program.cs | 4 + test/GprTool.Tests/GlobExtensionTests.cs | 80 +-- test/GprTool.Tests/GprTool.Tests.csproj | 8 +- test/GprTool.Tests/IoExtensionsTests.cs | 2 +- test/GprTool.Tests/NuGetUtilitiesTests.cs | 593 +++++++++++----------- 8 files changed, 364 insertions(+), 344 deletions(-) diff --git a/.gitignore b/.gitignore index dcbcc30..12af354 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -**/.vs/ +**/.vs/ **/bin/ **/obj/ *.user @@ -7,3 +7,4 @@ _NCrunch_*/ *.ncrunchsolution publish/ src/GprTool/nupkg/ +**/TestResults/TestResults.xml diff --git a/Directory.Build.props b/Directory.Build.props index faca85f..2b10632 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,11 @@  - - - 3.1.91 - all - - + + false + + + + $(DefineConstants);IS_RUNNING_TESTS + + \ No newline at end of file diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index 4558c7a..69ac1ad 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -29,6 +29,10 @@ + + 3.1.91 + all + diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 13d342b..0d5402b 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -712,7 +712,11 @@ public abstract class GprCommandBase static long SetupCancelKeyPress = 1; protected static string AssemblyProduct => Assembly.GetExecutingAssembly().GetCustomAttribute()?.Product; + #if !IS_RUNNING_TESTS protected static string AssemblyInformationalVersion => ThisAssembly.AssemblyInformationalVersion; + #else + protected static string AssemblyInformationalVersion => "0.0.0"; + #endif [SuppressMessage("ReSharper", "UnusedMember.Global")] protected async Task OnExecuteAsync(CommandLineApplication app, CancellationToken cancellationToken) diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index c43bc98..8f89e55 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -1,47 +1,49 @@ using DotNet.Globbing; -using GprTool; using NUnit.Framework; -[TestFixture] -class GlobExtensionTests +namespace GprTool.Tests { - [TestCase("c:\\test.nupkg", false)] - [TestCase("c:\\test", false)] - [TestCase("packages", false)] - [TestCase("./packages", false)] - [TestCase(".\\packages", false)] - [TestCase("packages/**/*.nupkg", true)] - [TestCase("packages\\**\\*.nupkg", true)] - [TestCase("c:\\test?", true)] - [TestCase("c:\\test?\\**", true)] - [TestCase("c:\\test?\\[abc]\\**", true)] - [TestCase("c:\\test\\**", true)] - [TestCase("c:\\test\\**\\*.nupkg", true)] - [TestCase("c:\\test\\*.*", true)] - public void IsGlobPattern(string path, bool isGlobPattern) + [TestFixture] + public class GlobExtensionTests { - var glob = Glob.Parse(path); - Assert.That(glob.IsGlobPattern(), Is.EqualTo(isGlobPattern)); - } + [TestCase("c:\\test.nupkg", false)] + [TestCase("c:\\test", false)] + [TestCase("packages", false)] + [TestCase("./packages", false)] + [TestCase(".\\packages", false)] + [TestCase("packages/**/*.nupkg", true)] + [TestCase("packages\\**\\*.nupkg", true)] + [TestCase("c:\\test?", true)] + [TestCase("c:\\test?\\**", true)] + [TestCase("c:\\test?\\[abc]\\**", true)] + [TestCase("c:\\test\\**", true)] + [TestCase("c:\\test\\**\\*.nupkg", true)] + [TestCase("c:\\test\\*.*", true)] + public void IsGlobPattern(string path, bool isGlobPattern) + { + var glob = Glob.Parse(path); + Assert.That(glob.IsGlobPattern(), Is.EqualTo(isGlobPattern)); + } - [TestCase(".", "c:\\", "c:\\", Description = "Relative path is directory")] - [TestCase("packages", "c:\\", "c:\\packages", Description = "Relative path is directory")] - [TestCase("test.nupkg", "c:\\", "c:\\test.nupkg", Description = "Relative path is filename")] - [TestCase("packages\\**\\*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] - [TestCase("packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] - [TestCase("./packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] - [TestCase(".\\packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] - [TestCase("c:\\test?", "c:\\", "c:\\")] - [TestCase("c:\\test?\\**", "c:\\", "c:\\")] - [TestCase("c:\\test?\\[abc]\\**", "c:\\", "c:\\")] - [TestCase("c:\\test\\*.*", "c:\\", "c:\\test")] - [TestCase("c:\\test\\**", "c:\\", "c:\\test")] - [TestCase("c:\\test\\**\\*.nupkg", "c:\\", "c:\\test")] - [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\", "c:\\test\\subdirectory")] - [TestCase("c:\\test\\**\\subdirectory\\**\\*.nupkg", "c:\\", "c:\\test")] - public void BuildBasePathFromGlob(string path, string baseDirectory, string expectedBaseDirectory) - { - var glob = Glob.Parse(path); - Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedBaseDirectory)); + [TestCase(".", "c:\\", "c:\\", Description = "Relative path is directory")] + [TestCase("packages", "c:\\", "c:\\packages", Description = "Relative path is directory")] + [TestCase("test.nupkg", "c:\\", "c:\\test.nupkg", Description = "Relative path is filename")] + [TestCase("packages\\**\\*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase("packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase("./packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase(".\\packages/**/*.nupkg", "c:\\", "c:\\packages", Description = "Relative path")] + [TestCase("c:\\test?", "c:\\", "c:\\")] + [TestCase("c:\\test?\\**", "c:\\", "c:\\")] + [TestCase("c:\\test?\\[abc]\\**", "c:\\", "c:\\")] + [TestCase("c:\\test\\*.*", "c:\\", "c:\\test")] + [TestCase("c:\\test\\**", "c:\\", "c:\\test")] + [TestCase("c:\\test\\**\\*.nupkg", "c:\\", "c:\\test")] + [TestCase("c:\\test\\subdirectory\\**\\*.nupkg", "c:\\", "c:\\test\\subdirectory")] + [TestCase("c:\\test\\**\\subdirectory\\**\\*.nupkg", "c:\\", "c:\\test")] + public void BuildBasePathFromGlob(string path, string baseDirectory, string expectedBaseDirectory) + { + var glob = Glob.Parse(path); + Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedBaseDirectory)); + } } } \ No newline at end of file diff --git a/test/GprTool.Tests/GprTool.Tests.csproj b/test/GprTool.Tests/GprTool.Tests.csproj index 3fbef84..6489b09 100644 --- a/test/GprTool.Tests/GprTool.Tests.csproj +++ b/test/GprTool.Tests/GprTool.Tests.csproj @@ -2,10 +2,16 @@ netcoreapp3.0 - false + true + + + + + + diff --git a/test/GprTool.Tests/IoExtensionsTests.cs b/test/GprTool.Tests/IoExtensionsTests.cs index df4c7e3..9e6dace 100644 --- a/test/GprTool.Tests/IoExtensionsTests.cs +++ b/test/GprTool.Tests/IoExtensionsTests.cs @@ -6,7 +6,7 @@ namespace GprTool.Tests { [TestFixture] - class IoExtensionsTests + public class IoExtensionsTests { [TestCase("./nupkg", "*.*", 2)] [TestCase("./nupkg/**/*.*", "**/*.*", 2)] diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index 261cccd..ac21ae8 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -1,23 +1,23 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using System.Xml; -using NUnit.Framework; -using GprTool; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.Versioning; +using NUnit.Framework; -[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] -public class NuGetUtilitiesTests +namespace GprTool.Tests { - public class TheSetApiKeyMethod + [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] + public class NuGetUtilitiesTests { - public string TmpDirectoryPath => Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N")); + public class TheSetApiKeyMethod + { + public string TmpDirectoryPath => Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N")); - const string NuspecXml = @" + const string NuspecXml = @" test @@ -32,62 +32,62 @@ public class TheSetApiKeyMethod "; - [Test] - public void AddClearTextPassword() - { - var xmlDoc = new XmlDocument(); - var source = "SOURCE"; - var expectToken = "TOKEN"; + [Test] + public void AddClearTextPassword() + { + var xmlDoc = new XmlDocument(); + var source = "SOURCE"; + var expectToken = "TOKEN"; - NuGetUtilities.SetApiKey(xmlDoc, expectToken, source); + NuGetUtilities.SetApiKey(xmlDoc, expectToken, source); - var passwordXpath = $"/configuration/packageSourceCredentials/{source}/add[@key='ClearTextPassword']/@value"; - var token = xmlDoc.SelectSingleNode(passwordXpath)?.Value; - Assert.That(token, Is.EqualTo(expectToken)); - } + var passwordXpath = $"/configuration/packageSourceCredentials/{source}/add[@key='ClearTextPassword']/@value"; + var token = xmlDoc.SelectSingleNode(passwordXpath)?.Value; + Assert.That(token, Is.EqualTo(expectToken)); + } - [Test] - public void AddUsername() - { - var xmlDoc = new XmlDocument(); - var source = "SOURCE"; - var token = "TOKEN"; - var expectUsername = "PersonalAccessToken"; + [Test] + public void AddUsername() + { + var xmlDoc = new XmlDocument(); + var source = "SOURCE"; + var token = "TOKEN"; + var expectUsername = "PersonalAccessToken"; - NuGetUtilities.SetApiKey(xmlDoc, token, source); + NuGetUtilities.SetApiKey(xmlDoc, token, source); - var usernameXpath = $"/configuration/packageSourceCredentials/{source}/add[@key='Username']/@value"; - var username = xmlDoc.SelectSingleNode(usernameXpath)?.Value; - Assert.That(username, Is.EqualTo(expectUsername)); - } + var usernameXpath = $"/configuration/packageSourceCredentials/{source}/add[@key='Username']/@value"; + var username = xmlDoc.SelectSingleNode(usernameXpath)?.Value; + Assert.That(username, Is.EqualTo(expectUsername)); + } - [Test] - public void PreserveExistingPackageSources() - { - var xml = -@" + [Test] + public void PreserveExistingPackageSources() + { + var xml = + @" "; - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(xml); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xml); - NuGetUtilities.SetApiKey(xmlDoc, "TOKEN", "SOURCE"); + NuGetUtilities.SetApiKey(xmlDoc, "TOKEN", "SOURCE"); - var xpath = $"/configuration/packageSources/clear"; - var element = xmlDoc.SelectSingleNode(xpath); - Assert.That(element, Is.Not.Null); - } + var xpath = $"/configuration/packageSources/clear"; + var element = xmlDoc.SelectSingleNode(xpath); + Assert.That(element, Is.Not.Null); + } - [Test] - public void PreserveExistingPackageSourceCredentials() - { - var existingSource = "EXISTING_SOURCE"; - var newSource = "NEW_SOURCE"; - var xml = -$@" + [Test] + public void PreserveExistingPackageSourceCredentials() + { + var existingSource = "EXISTING_SOURCE"; + var newSource = "NEW_SOURCE"; + var xml = + $@" <{existingSource}> @@ -96,27 +96,27 @@ public void PreserveExistingPackageSourceCredentials() "; - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(xml); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xml); - NuGetUtilities.SetApiKey(xmlDoc, "TOKEN", newSource); + NuGetUtilities.SetApiKey(xmlDoc, "TOKEN", newSource); - var existingElement = xmlDoc.SelectSingleNode($"/configuration/packageSourceCredentials/{existingSource}"); - Assert.That(existingElement, Is.Not.Null); - var newElement = xmlDoc.SelectSingleNode($"/configuration/packageSourceCredentials/{newSource}"); - Assert.That(newElement, Is.Not.Null); - var packageSourceCredentialsElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials"); - Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); - } + var existingElement = xmlDoc.SelectSingleNode($"/configuration/packageSourceCredentials/{existingSource}"); + Assert.That(existingElement, Is.Not.Null); + var newElement = xmlDoc.SelectSingleNode($"/configuration/packageSourceCredentials/{newSource}"); + Assert.That(newElement, Is.Not.Null); + var packageSourceCredentialsElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials"); + Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); + } - [Test] - public void UpdatePackageSourceCredentials() - { - var existingSource = "EXISTING_SOURCE"; - var oldToken = "OLD_TOKEN"; - var newToken = "NEW_TOKEN"; - var xml = -$@" + [Test] + public void UpdatePackageSourceCredentials() + { + var existingSource = "EXISTING_SOURCE"; + var oldToken = "OLD_TOKEN"; + var newToken = "NEW_TOKEN"; + var xml = + $@" <{existingSource}> @@ -125,297 +125,298 @@ public void UpdatePackageSourceCredentials() "; - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(xml); - - NuGetUtilities.SetApiKey(xmlDoc, newToken, existingSource); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xml); - var passwordAttribute = xmlDoc.SelectSingleNode($"/configuration/packageSourceCredentials/{existingSource}/add[@key='ClearTextPassword']/@value"); - Assert.That(passwordAttribute?.Value, Is.EqualTo(newToken)); - var addElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials/{existingSource}/add"); - Assert.That(addElements.Count, Is.EqualTo(2)); - var packageSourceCredentialsElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials/*"); - Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); - } - - [TestCase(null, null, null, null)] - [TestCase("jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("jcansdale//gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("http://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] - public void BuildPackageFile(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) - { - var packageFile = NuGetUtilities.BuildPackageFile("c:\\test\\test.nupkg", repositoryUrl); - packageFile.IsNuspecRewritten = true; + NuGetUtilities.SetApiKey(xmlDoc, newToken, existingSource); - Assert.That(packageFile, Is.Not.Null); - Assert.That(packageFile.Filename, Is.EqualTo("test.nupkg")); - Assert.That(packageFile.FilenameWithoutGprPrefix, Is.EqualTo("test.nupkg")); - Assert.That(packageFile.FilenameAbsolutePath, Is.EqualTo("c:\\test\\test.nupkg")); + var passwordAttribute = xmlDoc.SelectSingleNode($"/configuration/packageSourceCredentials/{existingSource}/add[@key='ClearTextPassword']/@value"); + Assert.That(passwordAttribute?.Value, Is.EqualTo(newToken)); + var addElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials/{existingSource}/add"); + Assert.That(addElements.Count, Is.EqualTo(2)); + var packageSourceCredentialsElements = xmlDoc.SelectNodes($"/configuration/packageSourceCredentials/*"); + Assert.That(packageSourceCredentialsElements.Count, Is.EqualTo(1)); + } - if (expectedGithubRepositoryUrl == null) + [TestCase(null, null, null, null)] + [TestCase("jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("jcansdale//gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("http://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] + public void BuildPackageFile(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) { - Assert.Null(packageFile.Owner); - Assert.Null(packageFile.RepositoryName); - Assert.That(packageFile.RepositoryUrl, Is.Null); - return; - } + var packageFile = NuGetUtilities.BuildPackageFile("c:\\test\\test.nupkg", repositoryUrl); + packageFile.IsNuspecRewritten = true; - Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); - Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); - Assert.That(packageFile.RepositoryUrl, Is.Not.Null); - Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); - } + Assert.That(packageFile, Is.Not.Null); + Assert.That(packageFile.Filename, Is.EqualTo("test.nupkg")); + Assert.That(packageFile.FilenameWithoutGprPrefix, Is.EqualTo("test.nupkg")); + Assert.That(packageFile.FilenameAbsolutePath, Is.EqualTo("c:\\test\\test.nupkg")); - [TestCase("jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("jcansdale//gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("http://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] - [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] - public void BuildOwnerAndRepositoryFromUrlFromNupkg(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) - { - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => - { - manifest.Metadata.Repository = new RepositoryMetadata + if (expectedGithubRepositoryUrl == null) { - Url = repositoryUrl, - Type = "git" - }; - })); + Assert.Null(packageFile.Owner); + Assert.Null(packageFile.RepositoryName); + Assert.That(packageFile.RepositoryUrl, Is.Null); + return; + } - packageBuilderContext.Build(); + Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); + Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); + Assert.That(packageFile.RepositoryUrl, Is.Not.Null); + Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); + } - var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, null); + [TestCase("jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("jcansdale//gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("http://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale/gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale\\gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase("https://github.com/jcansdale///////gpr", "jcansdale", "gpr", "https://github.com/jcansdale/gpr")] + [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] + public void BuildOwnerAndRepositoryFromUrlFromNupkg(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) + { + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + { + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = repositoryUrl, + Type = "git" + }; + })); - Assert.True(NuGetUtilities.BuildOwnerAndRepositoryFromUrlFromNupkg(packageFile)); + packageBuilderContext.Build(); - Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); - Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); - Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); - } + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, null); - [Test] - public void ReadNupkgManifest() - { - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => - { - manifest.Metadata.Version = new NuGetVersion("1.0.0"); - manifest.Metadata.Repository = new RepositoryMetadata - { - Url = "https://github.com/jcansdale/gpr", - Type = "git" - }; - })); - - packageBuilderContext.Build(); - - var manifest = NuGetUtilities.ReadNupkgManifest(packageBuilderContext.NupkgFilename); - Assert.That(manifest, Is.Not.Null); - Assert.That(manifest.Metadata.Version, Is.EqualTo(packageBuilderContext.NuspecContext.Manifest.Metadata.Version)); - Assert.That(manifest.Metadata.Repository, Is.Not.Null); - Assert.That(manifest.Metadata.Repository.Url, Is.EqualTo(packageBuilderContext.NuspecContext.Manifest.Metadata.Repository.Url)); - } + Assert.True(NuGetUtilities.BuildOwnerAndRepositoryFromUrlFromNupkg(packageFile)); - [TestCase("1.0.0", "1.0.0", false)] - [TestCase("1.0.0", "1.0.1", true)] - public void ShouldRewriteNupkg_Version(string currentVersion, string updatedVersion, bool shouldUpdateVersion) - { - const string repositoryUrl = "https://github.com/jcansdale/gpr"; + Assert.That(packageFile.Owner, Is.EqualTo(expectedOwner)); + Assert.That(packageFile.RepositoryName, Is.EqualTo(expectedRepositoryName)); + Assert.That(packageFile.RepositoryUrl, Is.EqualTo(expectedGithubRepositoryUrl)); + } - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + [Test] + public void ReadNupkgManifest() { - manifest.Metadata.Version = new NuGetVersion(currentVersion); - manifest.Metadata.Repository = new RepositoryMetadata + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => { - Url = repositoryUrl, - Type = "git" - }; - })); - - packageBuilderContext.Build(); + manifest.Metadata.Version = new NuGetVersion("1.0.0"); + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = "https://github.com/jcansdale/gpr", + Type = "git" + }; + })); - var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); + packageBuilderContext.Build(); - Assert.That( - NuGetUtilities.ShouldRewriteNupkg(packageFile, - NuGetVersion.Parse(updatedVersion)), Is.EqualTo(shouldUpdateVersion)); - } + var manifest = NuGetUtilities.ReadNupkgManifest(packageBuilderContext.NupkgFilename); + Assert.That(manifest, Is.Not.Null); + Assert.That(manifest.Metadata.Version, Is.EqualTo(packageBuilderContext.NuspecContext.Manifest.Metadata.Version)); + Assert.That(manifest.Metadata.Repository, Is.Not.Null); + Assert.That(manifest.Metadata.Repository.Url, Is.EqualTo(packageBuilderContext.NuspecContext.Manifest.Metadata.Repository.Url)); + } - [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git", false, Description = "Equals")] - [TestCase("https://github.com/owner/repo", "https://github.com/owner/REPO", false, Description = "Case insensitive")] - [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo.git", true, Description = "Url ends with .git")] - [TestCase(null, "https://github.com/owner/repo.git", true)] - [TestCase("https://google.com", "https://github.com/owner/repo.git", true)] - public void ShouldRewriteNupkg_RepositoryUrl(string currentRepositoryUrl, string updatedRepositoryUrl, bool shouldUpdateRepositoryUrl) - { - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + [TestCase("1.0.0", "1.0.0", false)] + [TestCase("1.0.0", "1.0.1", true)] + public void ShouldRewriteNupkg_Version(string currentVersion, string updatedVersion, bool shouldUpdateVersion) { - if (currentRepositoryUrl == null) - { - manifest.Metadata.Repository = null; - return; - } + const string repositoryUrl = "https://github.com/jcansdale/gpr"; - manifest.Metadata.Repository = new RepositoryMetadata + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => { - Url = currentRepositoryUrl, - Type = "git" - }; - })); - - packageBuilderContext.Build(); + manifest.Metadata.Version = new NuGetVersion(currentVersion); + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = repositoryUrl, + Type = "git" + }; + })); - var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, updatedRepositoryUrl); + packageBuilderContext.Build(); - Assert.That(NuGetUtilities.ShouldRewriteNupkg(packageFile), Is.EqualTo(shouldUpdateRepositoryUrl)); - } + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); - [TestCase("randomvalue")] - [TestCase("git")] - public void ShouldRewriteNupkg_Ignores_RepositoryType(string repositoryType) - { - const string currentRepositoryUrl = "https://github.com/jcansdale/gpr"; + Assert.That( + NuGetUtilities.ShouldRewriteNupkg(packageFile, + NuGetVersion.Parse(updatedVersion)), Is.EqualTo(shouldUpdateVersion)); + } - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git", false, Description = "Equals")] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/REPO", false, Description = "Case insensitive")] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo.git", true, Description = "Url ends with .git")] + [TestCase(null, "https://github.com/owner/repo.git", true)] + [TestCase("https://google.com", "https://github.com/owner/repo.git", true)] + public void ShouldRewriteNupkg_RepositoryUrl(string currentRepositoryUrl, string updatedRepositoryUrl, bool shouldUpdateRepositoryUrl) { - manifest.Metadata.Repository = new RepositoryMetadata + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => { - Url = currentRepositoryUrl, - Type = repositoryType - }; - })); - - packageBuilderContext.Build(); - - var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, currentRepositoryUrl); - - Assert.That(NuGetUtilities.ShouldRewriteNupkg(packageFile), Is.EqualTo(false)); - } + if (currentRepositoryUrl == null) + { + manifest.Metadata.Repository = null; + return; + } - [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git")] - [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo")] - public void RewriteNuspec(string repositoryUrl, string expectedRepositoryUrl) - { - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( - manifest => - { - manifest.Metadata.Repository = null; + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = currentRepositoryUrl, + Type = "git" + }; })); - packageBuilderContext.Build(); - var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); + packageBuilderContext.Build(); - NuGetUtilities.RewriteNupkg(packageFile, NuGetVersion.Parse("2.0.0")); + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, updatedRepositoryUrl); - using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(packageFile.FilenameAbsolutePath)); - - var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); - var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); + Assert.That(NuGetUtilities.ShouldRewriteNupkg(packageFile), Is.EqualTo(shouldUpdateRepositoryUrl)); + } - Assert.That(rewrittenNupkgPackageIdentity, Is.Not.Null); - Assert.That(rewrittenNupkgPackageIdentity.Id, Is.EqualTo("test")); - Assert.That(rewrittenNupkgPackageIdentity.Version, Is.EqualTo(NuGetVersion.Parse("2.0.0"))); - Assert.That(rewrittenNupkgRepositoryMetadata, Is.Not.Null); - Assert.That(rewrittenNupkgRepositoryMetadata.Url, Is.EqualTo(expectedRepositoryUrl)); - Assert.That(rewrittenNupkgRepositoryMetadata.Type, Is.EqualTo("git")); - } + [TestCase("randomvalue")] + [TestCase("git")] + public void ShouldRewriteNupkg_Ignores_RepositoryType(string repositoryType) + { + const string currentRepositoryUrl = "https://github.com/jcansdale/gpr"; - [Test] - public void RewriteNuspec_Overwrites_Existing_Repository_Url() - { - using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( - manifest => + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => { manifest.Metadata.Repository = new RepositoryMetadata { - Url = "https://google.com", - Type = "google" + Url = currentRepositoryUrl, + Type = repositoryType }; })); - packageBuilderContext.Build(); - var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, "https://github.com/owner/repo"); + packageBuilderContext.Build(); - NuGetUtilities.RewriteNupkg(packageFile, NuGetVersion.Parse("2.0.0")); + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, currentRepositoryUrl); - using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(packageFile.FilenameAbsolutePath)); + Assert.That(NuGetUtilities.ShouldRewriteNupkg(packageFile), Is.EqualTo(false)); + } - var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); - var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); + [TestCase("https://github.com/owner/repo.git", "https://github.com/owner/repo.git")] + [TestCase("https://github.com/owner/repo", "https://github.com/owner/repo")] + public void RewriteNuspec(string repositoryUrl, string expectedRepositoryUrl) + { + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( + manifest => + { + manifest.Metadata.Repository = null; + })); + packageBuilderContext.Build(); - Assert.That(rewrittenNupkgPackageIdentity, Is.Not.Null); - Assert.That(rewrittenNupkgPackageIdentity.Id, Is.EqualTo("test")); - Assert.That(rewrittenNupkgPackageIdentity.Version, Is.EqualTo(NuGetVersion.Parse("2.0.0"))); - Assert.That(rewrittenNupkgRepositoryMetadata, Is.Not.Null); - Assert.That(rewrittenNupkgRepositoryMetadata.Url, Is.EqualTo("https://github.com/owner/repo")); - Assert.That(rewrittenNupkgRepositoryMetadata.Type, Is.EqualTo("git")); - } + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); - [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] - class NuspecContext : IDisposable - { - public Manifest Manifest { get; } - public MemoryStream ManifestStream { get; } + NuGetUtilities.RewriteNupkg(packageFile, NuGetVersion.Parse("2.0.0")); - public NuspecContext(Action manifestBuilder = null) - { - using var nuspecMemoryStream = new MemoryStream(Encoding.UTF8.GetBytes(NuspecXml)); - Manifest = Manifest.ReadFrom(nuspecMemoryStream, true); - manifestBuilder?.Invoke(Manifest); - ManifestStream = new MemoryStream(); - Manifest.Save(ManifestStream, true); - ManifestStream.Seek(0, SeekOrigin.Begin); + using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(packageFile.FilenameAbsolutePath)); + + var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); + var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); + + Assert.That(rewrittenNupkgPackageIdentity, Is.Not.Null); + Assert.That(rewrittenNupkgPackageIdentity.Id, Is.EqualTo("test")); + Assert.That(rewrittenNupkgPackageIdentity.Version, Is.EqualTo(NuGetVersion.Parse("2.0.0"))); + Assert.That(rewrittenNupkgRepositoryMetadata, Is.Not.Null); + Assert.That(rewrittenNupkgRepositoryMetadata.Url, Is.EqualTo(expectedRepositoryUrl)); + Assert.That(rewrittenNupkgRepositoryMetadata.Type, Is.EqualTo("git")); } - public void Dispose() + [Test] + public void RewriteNuspec_Overwrites_Existing_Repository_Url() { - ManifestStream?.Dispose(); - } - } + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext( + manifest => + { + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = "https://google.com", + Type = "google" + }; + })); + packageBuilderContext.Build(); - class PackageBuilderContext : IDisposable - { - readonly DisposableDirectory _disposableDirectory; + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, "https://github.com/owner/repo"); - public string WorkingDirectory => _disposableDirectory.WorkingDirectory; - public string Filename { get; } - public string NupkgFilename => Path.Combine(WorkingDirectory, Filename); - public NuspecContext NuspecContext { get; } + NuGetUtilities.RewriteNupkg(packageFile, NuGetVersion.Parse("2.0.0")); - public PackageBuilderContext(string workingDirectory, NuspecContext nuspecContext) + using var rewrittenNupkgPackageReader = new PackageArchiveReader(File.OpenRead(packageFile.FilenameAbsolutePath)); + + var rewrittenNupkgPackageIdentity = rewrittenNupkgPackageReader.GetIdentity(); + var rewrittenNupkgRepositoryMetadata = rewrittenNupkgPackageReader.NuspecReader.GetRepositoryMetadata(); + + Assert.That(rewrittenNupkgPackageIdentity, Is.Not.Null); + Assert.That(rewrittenNupkgPackageIdentity.Id, Is.EqualTo("test")); + Assert.That(rewrittenNupkgPackageIdentity.Version, Is.EqualTo(NuGetVersion.Parse("2.0.0"))); + Assert.That(rewrittenNupkgRepositoryMetadata, Is.Not.Null); + Assert.That(rewrittenNupkgRepositoryMetadata.Url, Is.EqualTo("https://github.com/owner/repo")); + Assert.That(rewrittenNupkgRepositoryMetadata.Type, Is.EqualTo("git")); + } + + [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")] + class NuspecContext : IDisposable { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - if (nuspecContext == null) throw new ArgumentNullException(nameof(nuspecContext)); - _disposableDirectory = new DisposableDirectory(workingDirectory); + public Manifest Manifest { get; } + public MemoryStream ManifestStream { get; } + + public NuspecContext(Action manifestBuilder = null) + { + using var nuspecMemoryStream = new MemoryStream(Encoding.UTF8.GetBytes(NuspecXml)); + Manifest = Manifest.ReadFrom(nuspecMemoryStream, true); + manifestBuilder?.Invoke(Manifest); + ManifestStream = new MemoryStream(); + Manifest.Save(ManifestStream, true); + ManifestStream.Seek(0, SeekOrigin.Begin); + } - Filename = $"{nuspecContext.Manifest.Metadata.Id}.{nuspecContext.Manifest.Metadata.Version}.nupkg"; - NuspecContext = nuspecContext; + public void Dispose() + { + ManifestStream?.Dispose(); + } } - public void Build(Action builder = null) + class PackageBuilderContext : IDisposable { - using var packageBuilderOutputStream = new MemoryStream(); + readonly DisposableDirectory _disposableDirectory; - var nupkgPath = Path.Combine(WorkingDirectory, Filename); - var packageBuilder = new PackageBuilder(NuspecContext.ManifestStream, WorkingDirectory, s => throw new NotImplementedException()); + public string WorkingDirectory => _disposableDirectory.WorkingDirectory; + public string Filename { get; } + public string NupkgFilename => Path.Combine(WorkingDirectory, Filename); + public NuspecContext NuspecContext { get; } - builder?.Invoke(packageBuilder); + public PackageBuilderContext(string workingDirectory, NuspecContext nuspecContext) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (nuspecContext == null) throw new ArgumentNullException(nameof(nuspecContext)); + _disposableDirectory = new DisposableDirectory(workingDirectory); - packageBuilder.Save(packageBuilderOutputStream); + Filename = $"{nuspecContext.Manifest.Metadata.Id}.{nuspecContext.Manifest.Metadata.Version}.nupkg"; + NuspecContext = nuspecContext; + } - File.WriteAllBytes(nupkgPath, packageBuilderOutputStream.ToArray()); - } + public void Build(Action builder = null) + { + using var packageBuilderOutputStream = new MemoryStream(); - public void Dispose() - { - NuspecContext.Dispose(); + var nupkgPath = Path.Combine(WorkingDirectory, Filename); + var packageBuilder = new PackageBuilder(NuspecContext.ManifestStream, WorkingDirectory, s => throw new NotImplementedException()); + + builder?.Invoke(packageBuilder); + + packageBuilder.Save(packageBuilderOutputStream); + + File.WriteAllBytes(nupkgPath, packageBuilderOutputStream.ToArray()); + } + + public void Dispose() + { + NuspecContext.Dispose(); + } } } } From 409289893ca5d8093fc72fc9bb52c0ba8e4c636a Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 00:29:36 +0200 Subject: [PATCH 31/40] Change package output path root solution directory. --- Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Directory.Build.props b/Directory.Build.props index 2b10632..5308044 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,6 +2,7 @@ false + $(MSBuildThisFileDirectory)nupkgs From 9b823ad569ee579bc41d5c7c9e105890f6f323cd Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 00:29:43 +0200 Subject: [PATCH 32/40] Add global json. --- global.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 0000000..c120c81 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "3.1.301" + } +} \ No newline at end of file From 38b72eee907c47b54f120e699322d20cd3188aa1 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 02:14:59 +0200 Subject: [PATCH 33/40] Fix failing test cases on Unix. --- Directory.Build.props | 11 ++++++ src/GprTool/GlobExtensions.cs | 3 ++ test/GprTool.Tests/GlobExtensionTests.cs | 42 +++++++++++++++++++++++ test/GprTool.Tests/IoExtensionsTests.cs | 16 ++++++--- test/GprTool.Tests/NuGetUtilitiesTests.cs | 19 +++++++--- 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5308044..2ab3560 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,10 +3,21 @@ false $(MSBuildThisFileDirectory)nupkgs + Windows + Unix + Unknown $(DefineConstants);IS_RUNNING_TESTS + + $(DefineConstants);PLATFORM_WINDOWS + + + + $(DefineConstants);PLATFORM_UNIX + + \ No newline at end of file diff --git a/src/GprTool/GlobExtensions.cs b/src/GprTool/GlobExtensions.cs index d947652..205d487 100644 --- a/src/GprTool/GlobExtensions.cs +++ b/src/GprTool/GlobExtensions.cs @@ -75,6 +75,9 @@ public static string BuildBasePathFromGlob(this Glob glob, string baseDirectory) && pathStr.Sum(x => x == '/' ? 1 : 0) > 1) { pathStr = pathStr.Substring(0, pathStr.Length - 1); + } else if (lastAppendedGlobToken is LiteralToken literalToken && literalToken.Value == ".") + { + return Path.GetFullPath(baseDirectory); } return !Path.IsPathRooted(pathStr) ? Path.GetFullPath(pathStr, baseDirectory) : Path.GetFullPath(pathStr); diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index 8f89e55..7834faa 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -6,6 +6,7 @@ namespace GprTool.Tests [TestFixture] public class GlobExtensionTests { +#if PLATFORM_WINDOWS [TestCase("c:\\test.nupkg", false)] [TestCase("c:\\test", false)] [TestCase("packages", false)] @@ -45,5 +46,46 @@ public void BuildBasePathFromGlob(string path, string baseDirectory, string expe var glob = Glob.Parse(path); Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedBaseDirectory)); } +#elif PLATFORM_UNIX + [TestCase("/mnt/c/test.nupkg", false)] + [TestCase("/mnt/c/test", false)] + [TestCase("packages", false)] + [TestCase("./packages", false)] + [TestCase(".\\packages", false)] + [TestCase("packages/**/*.nupkg", true)] + [TestCase("packages//**/*.nupkg", true)] + [TestCase("/mnt/c/test?", true)] + [TestCase("/mnt/c/test?/**", true)] + [TestCase("/mnt/c/test?/[abc]/**", true)] + [TestCase("/mnt/c/test/**", true)] + [TestCase("/mnt/c/test/**/*.nupkg", true)] + [TestCase("/mnt/c/test/*.*", true)] + public void IsGlobPattern(string path, bool isGlobPattern) + { + var glob = Glob.Parse(path); + Assert.That(glob.IsGlobPattern(), Is.EqualTo(isGlobPattern)); + } + + [TestCase(".", "/mnt/c", "/mnt/c", Description = "Relative path is directory")] + [TestCase("packages", "/mnt/c", "/mnt/c/packages", Description = "Relative path is directory")] + [TestCase("test.nupkg", "/mnt/c", "/mnt/c/test.nupkg", Description = "Relative path is filename")] + [TestCase("packages/**/*.nupkg", "/mnt/c", "/mnt/c/packages", Description = "Relative path")] + [TestCase("packages/**/*.nupkg", "/mnt/c", "/mnt/c/packages", Description = "Relative path")] + [TestCase("./packages/**/*.nupkg", "/mnt/c", "/mnt/c/packages", Description = "Relative path")] + [TestCase(".\\packages/**/*.nupkg", "/mnt/c", "/mnt/c/packages", Description = "Relative path")] + [TestCase("/mnt/c/test?", "/mnt/c", "/mnt/c")] + [TestCase("/mnt/c/test?/**", "/mnt/c", "/mnt/c")] + [TestCase("/mnt/c/test?/[abc]/**", "/mnt/c", "/mnt/c")] + [TestCase("/mnt/c/test/*.*", "/mnt/c", "/mnt/c/test")] + [TestCase("/mnt/c/test/**", "/mnt/c", "/mnt/c/test")] + [TestCase("/mnt/c/test/**/*.nupkg", "/mnt/c", "/mnt/c/test")] + [TestCase("/mnt/c/test/subdirectory/**/*.nupkg", "/mnt/c", "/mnt/c/test/subdirectory")] + [TestCase("/mnt/c/test/**/subdirectory/**/*.nupkg", "/mnt/c", "/mnt/c/test")] + public void BuildBasePathFromGlob(string path, string baseDirectory, string expectedBaseDirectory) + { + var glob = Glob.Parse(path); + Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedBaseDirectory)); + } +#endif } } \ No newline at end of file diff --git a/test/GprTool.Tests/IoExtensionsTests.cs b/test/GprTool.Tests/IoExtensionsTests.cs index 9e6dace..ddb48bb 100644 --- a/test/GprTool.Tests/IoExtensionsTests.cs +++ b/test/GprTool.Tests/IoExtensionsTests.cs @@ -12,10 +12,12 @@ public class IoExtensionsTests [TestCase("./nupkg/**/*.*", "**/*.*", 2)] [TestCase("./nupkg/**/**", "**/**", 2)] [TestCase("./nupkg/**/*.nupkg", "**/*.nupkg", 1)] + #if PLATFORM_WINDOWS [TestCase(".\\nupkg", "*.*", 2)] [TestCase(".\\nupkg\\**\\*.*", "**/*.*", 2)] [TestCase(".\\nupkg\\**\\**", "**/**", 2)] [TestCase(".\\nupkg\\**\\*.nupkg", "**/*.nupkg", 1)] + #endif [TestCase("nupkg", "*.*", 2)] [TestCase("nupkg/**/*.nupkg", "**/*.nupkg", 1)] [TestCase("nupkg/**/*.snupkg", "**/*.snupkg", 1)] @@ -36,7 +38,7 @@ public void GetFilesByGlobPattern_Is_Relative_Directory(string globPattern, stri var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(globPattern, out var glob).ToList(); var pattern = glob.ToString().Substring(nupkgDirectory.Length).Replace("\\", "/"); - if (pattern[0] == '/') + if (pattern[0] == '/' || pattern[0] == '\\') { pattern = pattern.Substring(1); } @@ -45,7 +47,9 @@ public void GetFilesByGlobPattern_Is_Relative_Directory(string globPattern, stri } [TestCase("./nupkg", "*.*", 2)] + #if PLATFORM_WINDOWS [TestCase(".\\nupkg", "*.*", 2)] + #endif [TestCase("nupkg", "*.*", 2)] [TestCase("nupkg/**/*.nupkg", "**/*.nupkg", 1)] [TestCase("nupkg/**/*.snupkg", "**/*.snupkg", 1)] @@ -66,7 +70,7 @@ public void GetFilesByGlobPattern_Is_FullPath_Directory(string globPattern, stri var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(Path.Combine(tmpDirectory, globPattern), out var glob).ToList(); var pattern = glob.ToString().Substring(nupkgDirectory.Length).Replace("\\", "/"); - if (pattern[0] == '/') + if (pattern[0] == '/' || pattern[0] == '\\') { pattern = pattern.Substring(1); } @@ -87,7 +91,7 @@ public void GetFilesByGlobPattern_Is_Dot_Directory() var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(".", out var glob).ToList(); var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); - if (globPattern[0] == '/') + if (globPattern[0] == '/' || globPattern[0] == '\\') { globPattern = globPattern.Substring(1); } @@ -100,7 +104,9 @@ public void GetFilesByGlobPattern_Is_Dot_Directory() [TestCase("test.nupkg")] [TestCase("./test.nupkg")] + #if PLATFORM_WINDOWS [TestCase(".\\test.nupkg")] + #endif public void GetFilesByGlobPattern_Is_Relative_Filename(string relativeFilename) { using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); @@ -114,7 +120,7 @@ public void GetFilesByGlobPattern_Is_Relative_Filename(string relativeFilename) var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(relativeFilename, out var glob).ToList(); var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); - if (globPattern[0] == '/') + if (globPattern[0] == '/' || globPattern[0] == '\\') { globPattern = globPattern.Substring(1); } @@ -138,7 +144,7 @@ public void GetFilesByGlobPattern_Is_FullPath_Filename() var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(nupkgAbsoluteFilename, out var glob).ToList(); var globPattern = glob.ToString().Substring(tmpDirectory.WorkingDirectory.Length).Replace("\\", "/"); - if (globPattern[0] == '/') + if (globPattern[0] == '/' || globPattern[0] == '\\') { globPattern = globPattern.Substring(1); } diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index ac21ae8..a8ed506 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -149,13 +149,24 @@ public void UpdatePackageSourceCredentials() [TestCase(" https://github.com/jcansdale/gpr ", "jcansdale", "gpr", "https://github.com/jcansdale/gpr", Description = "Whitespace")] public void BuildPackageFile(string repositoryUrl, string expectedOwner, string expectedRepositoryName, string expectedGithubRepositoryUrl) { - var packageFile = NuGetUtilities.BuildPackageFile("c:\\test\\test.nupkg", repositoryUrl); + using var packageBuilderContext = new PackageBuilderContext(TmpDirectoryPath, new NuspecContext(manifest => + { + manifest.Metadata.Repository = new RepositoryMetadata + { + Url = repositoryUrl, + Type = "git" + }; + })); + + packageBuilderContext.Build(); + + var packageFile = NuGetUtilities.BuildPackageFile(packageBuilderContext.NupkgFilename, repositoryUrl); packageFile.IsNuspecRewritten = true; Assert.That(packageFile, Is.Not.Null); - Assert.That(packageFile.Filename, Is.EqualTo("test.nupkg")); - Assert.That(packageFile.FilenameWithoutGprPrefix, Is.EqualTo("test.nupkg")); - Assert.That(packageFile.FilenameAbsolutePath, Is.EqualTo("c:\\test\\test.nupkg")); + Assert.That(packageFile.Filename, Is.EqualTo("test.1.0.0.nupkg")); + Assert.That(packageFile.FilenameWithoutGprPrefix, Is.EqualTo("test.1.0.0.nupkg")); + Assert.That(packageFile.FilenameAbsolutePath, Is.EqualTo(packageBuilderContext.NupkgFilename)); if (expectedGithubRepositoryUrl == null) { From 9523c16c526f824472614fd6f411d3f7f4d47ad6 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 02:18:45 +0200 Subject: [PATCH 34/40] PackageOutputPath is already defined in Directory.Build.Props. --- src/GprTool/GprTool.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GprTool/GprTool.csproj b/src/GprTool/GprTool.csproj index 69ac1ad..fc05aab 100644 --- a/src/GprTool/GprTool.csproj +++ b/src/GprTool/GprTool.csproj @@ -16,7 +16,6 @@ https://github.com/jcansdale/gpr GPR Tool A .NET Core tool for working the GitHub Package Registry. - ./nupkg gpr Apache-2.0 Mutant Design Limited From 8fac9108fe6e6ef35d39ae088474045cba75bd59 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 02:32:36 +0200 Subject: [PATCH 35/40] Update src/GprTool/NuGetUtilities.cs Co-authored-by: Jamie Cansdale --- src/GprTool/NuGetUtilities.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 11ce682..8e5d373 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -172,8 +172,7 @@ public static void RewriteNupkg(PackageFile packageFile, NuGetVersion nuGetVersi propertyProvider => throw new NotImplementedException()); packageBuilder.Save(outputStream); - packageFile.FilenameAbsolutePath = Path.Combine(packageFileWorkingDirectoryAbsolutePath, - $"{packageId}.{nuGetVersion}_{randomId}_gpr.nupkg"); + packageFile.FilenameAbsolutePath = $"{packageFile.FilenameAbsolutePath}.zip"); packageFile.Filename = Path.GetFileName(packageFile.FilenameAbsolutePath); packageFile.FilenameWithoutGprPrefix = $"{packageId}.{nuGetVersion}.nupkg"; packageFile.IsNuspecRewritten = true; From ec536b0174255789fa5a196a22151c4f12bb36ca Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 02:32:57 +0200 Subject: [PATCH 36/40] Ignore nupkgs directory. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12af354..adba243 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ _NCrunch_*/ *.ncrunchsolution publish/ +nupkgs/ src/GprTool/nupkg/ **/TestResults/TestResults.xml From 47f04097cc3357e00bb8cbcd4da01648d8400de1 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 02:41:29 +0200 Subject: [PATCH 37/40] Remove FilenameWithoutGprPrefix and use ".zip" extension when rewriting nupkg in order to avoid uploading previously written packages. --- src/GprTool/NuGetUtilities.cs | 7 ++----- src/GprTool/Program.cs | 16 ++++++++-------- test/GprTool.Tests/NuGetUtilitiesTests.cs | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/GprTool/NuGetUtilities.cs b/src/GprTool/NuGetUtilities.cs index 8e5d373..b97ac03 100644 --- a/src/GprTool/NuGetUtilities.cs +++ b/src/GprTool/NuGetUtilities.cs @@ -19,7 +19,6 @@ public class PackageFile public string Filename { get; set; } public string FilenameAbsolutePath { get; set; } - public string FilenameWithoutGprPrefix { get; set; } } public class NuGetUtilities @@ -83,7 +82,6 @@ public static PackageFile BuildPackageFile(string filename, string repositoryUrl var packageFile = new PackageFile { Filename = Path.GetFileName(filename), - FilenameWithoutGprPrefix = Path.GetFileName(filename), FilenameAbsolutePath = Path.GetFullPath(filename) }; @@ -172,9 +170,8 @@ public static void RewriteNupkg(PackageFile packageFile, NuGetVersion nuGetVersi propertyProvider => throw new NotImplementedException()); packageBuilder.Save(outputStream); - packageFile.FilenameAbsolutePath = $"{packageFile.FilenameAbsolutePath}.zip"); - packageFile.Filename = Path.GetFileName(packageFile.FilenameAbsolutePath); - packageFile.FilenameWithoutGprPrefix = $"{packageId}.{nuGetVersion}.nupkg"; + packageFile.Filename = $"{packageId}.{nuGetVersion}.nupkg"; + packageFile.FilenameAbsolutePath = Path.Combine(packageFileWorkingDirectoryAbsolutePath, Path.ChangeExtension(packageFile.Filename, ".zip")); packageFile.IsNuspecRewritten = true; File.WriteAllBytes(packageFile.FilenameAbsolutePath, outputStream.ToArray()); diff --git a/src/GprTool/Program.cs b/src/GprTool/Program.cs index 0d5402b..55894e9 100644 --- a/src/GprTool/Program.cs +++ b/src/GprTool/Program.cs @@ -448,7 +448,7 @@ await packageFiles.ForEachAsync( (packageFile, packageCancellationToken) => UploadPackageAsync(packageFile, nuGetVersion, token, retryPolicy, packageCancellationToken), (packageFile, exception) => { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {exception.Message}"); + Console.WriteLine($"[{packageFile.Filename}]: {exception.Message}"); }, cancellationToken, Math.Max(1, Concurrency)); static async Task UploadPackageAsync(PackageFile packageFile, @@ -477,7 +477,7 @@ static async Task UploadPackageAsync(PackageFile packageFile, await using var packageStream = packageFile.FilenameAbsolutePath.ReadSharedToStream(); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: " + + Console.WriteLine($"[{packageFile.Filename}]: " + $"Repository url: {packageFile.RepositoryUrl}. " + $"Version: {packageVersion}. " + $"Size: {packageStream.Length} bytes. "); @@ -504,9 +504,9 @@ static async Task UploadPackageAsyncImpl(PackageFile packageFile, cancellationToken.ThrowIfCancellationRequested(); - request.AddFile("package", packageStream.CopyTo, packageFile.FilenameWithoutGprPrefix, packageStream.Length); + request.AddFile("package", packageStream.CopyTo, packageFile.Filename, packageStream.Length); - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: Uploading package."); + Console.WriteLine($"[{packageFile.Filename}]: Uploading package."); var response = await client.ExecuteAsync(request, cancellationToken); @@ -514,7 +514,7 @@ static async Task UploadPackageAsyncImpl(PackageFile packageFile, if (packageFile.IsUploaded) { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.Content}"); + Console.WriteLine($"[{packageFile.Filename}]: {response.Content}"); return response; } @@ -522,14 +522,14 @@ static async Task UploadPackageAsyncImpl(PackageFile packageFile, h.Name.Equals("X-Nuget-Warning", StringComparison.OrdinalIgnoreCase)); if (nugetWarning != null) { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {nugetWarning.Value}"); + Console.WriteLine($"[{packageFile.Filename}]: {nugetWarning.Value}"); return response; } - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {response.StatusDescription}"); + Console.WriteLine($"[{packageFile.Filename}]: {response.StatusDescription}"); foreach (var header in response.Headers) { - Console.WriteLine($"[{packageFile.FilenameWithoutGprPrefix}]: {header.Name}: {header.Value}"); + Console.WriteLine($"[{packageFile.Filename}]: {header.Name}: {header.Value}"); } return response; diff --git a/test/GprTool.Tests/NuGetUtilitiesTests.cs b/test/GprTool.Tests/NuGetUtilitiesTests.cs index a8ed506..f43f30a 100644 --- a/test/GprTool.Tests/NuGetUtilitiesTests.cs +++ b/test/GprTool.Tests/NuGetUtilitiesTests.cs @@ -165,7 +165,7 @@ public void BuildPackageFile(string repositoryUrl, string expectedOwner, string Assert.That(packageFile, Is.Not.Null); Assert.That(packageFile.Filename, Is.EqualTo("test.1.0.0.nupkg")); - Assert.That(packageFile.FilenameWithoutGprPrefix, Is.EqualTo("test.1.0.0.nupkg")); + Assert.That(packageFile.Filename, Is.EqualTo("test.1.0.0.nupkg")); Assert.That(packageFile.FilenameAbsolutePath, Is.EqualTo(packageBuilderContext.NupkgFilename)); if (expectedGithubRepositoryUrl == null) From 8af6bda46a3c69b8a337d52c55436ce7a7f05313 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 03:08:50 +0200 Subject: [PATCH 38/40] Add support for accepting multiple filenames (relative or absolute) as a glob pattern. Maybe also fixes #57 ? --- src/GprTool/IoExtensions.cs | 21 ++++++++-- test/GprTool.Tests/IoExtensionsTests.cs | 51 ++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/GprTool/IoExtensions.cs b/src/GprTool/IoExtensions.cs index 9f65afb..5997db3 100644 --- a/src/GprTool/IoExtensions.cs +++ b/src/GprTool/IoExtensions.cs @@ -11,7 +11,8 @@ public static class IoExtensions public static IEnumerable GetFilesByGlobPattern(this string baseDirectory, string globPattern, out Glob outGlob) { var baseDirectoryGlobPattern = Path.GetFullPath(Path.Combine(baseDirectory, globPattern)); - + var fileNames = new List(); + if (string.Equals(".", globPattern)) { globPattern = Path.GetFullPath(Path.Combine(baseDirectory, "*.*")); @@ -21,14 +22,26 @@ public static IEnumerable GetFilesByGlobPattern(this string baseDirector } else if (File.Exists(baseDirectoryGlobPattern)) { globPattern = Path.GetFullPath(baseDirectoryGlobPattern); - } + } else if (globPattern.Contains(" ")) + { + baseDirectoryGlobPattern = baseDirectory; - var basePathFromGlob = Path.GetDirectoryName(Glob.Parse(globPattern).BuildBasePathFromGlob(baseDirectory)); + fileNames.AddRange(globPattern + .Split(" ", StringSplitOptions.RemoveEmptyEntries) + .Select(x => Path.IsPathRooted(x) + ? Path.GetFullPath(x) + : Path.GetFullPath(Path.Combine(baseDirectoryGlobPattern, x))) + .Where(x => !Directory.Exists(x))); + + globPattern = string.Empty; + } var glob = Path.IsPathRooted(globPattern) ? Glob.Parse(globPattern) : Glob.Parse(baseDirectoryGlobPattern); + var basePathFromGlob = Path.GetDirectoryName(glob.BuildBasePathFromGlob(baseDirectory)); + outGlob = glob; return Directory @@ -36,7 +49,7 @@ public static IEnumerable GetFilesByGlobPattern(this string baseDirector .Where(filename => filename.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase) || filename.EndsWith(".snupkg", StringComparison.OrdinalIgnoreCase)) - .Where(filename => glob.IsMatch(filename)); + .Where(filename => fileNames.Contains(filename, StringComparer.Ordinal) || glob.IsMatch(filename)); } public static FileStream OpenReadShared(this string filename) diff --git a/test/GprTool.Tests/IoExtensionsTests.cs b/test/GprTool.Tests/IoExtensionsTests.cs index ddb48bb..662a136 100644 --- a/test/GprTool.Tests/IoExtensionsTests.cs +++ b/test/GprTool.Tests/IoExtensionsTests.cs @@ -104,7 +104,8 @@ public void GetFilesByGlobPattern_Is_Dot_Directory() [TestCase("test.nupkg")] [TestCase("./test.nupkg")] - #if PLATFORM_WINDOWS + [TestCase("test.nupkg")] +#if PLATFORM_WINDOWS [TestCase(".\\test.nupkg")] #endif public void GetFilesByGlobPattern_Is_Relative_Filename(string relativeFilename) @@ -130,6 +131,32 @@ public void GetFilesByGlobPattern_Is_Relative_Filename(string relativeFilename) Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); } + [TestCase("test.nupkg test.snupkg")] + [TestCase("./test.nupkg ./test.snupkg")] + [TestCase("./test.nupkg ./test.snupkg", Description = "Whitespace")] + #if PLATFORM_WINDOWS + [TestCase(".\\test.nupkg .\\test.snupkg")] + #endif + public void GetFilesByGlobPattern_Is_Multiple_Relative_Filenames(string relativeFilename) + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.snupkg"); + var bogusNupkgAbsoluteFilename = Path.Combine(tmpDirectory, "testbogus.snupkg"); + + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + File.WriteAllText(bogusNupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(relativeFilename, out var glob).ToList(); + + Assert.That(glob.ToString(), Is.EqualTo(tmpDirectory.WorkingDirectory)); + Assert.That(packages.Count, Is.EqualTo(2)); + Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); + } + [Test] public void GetFilesByGlobPattern_Is_FullPath_Filename() { @@ -153,5 +180,27 @@ public void GetFilesByGlobPattern_Is_FullPath_Filename() Assert.That(packages.Count, Is.EqualTo(1)); Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); } + + [Test] + public void GetFilesByGlobPattern_Is_Multiple_FullPath_Filenames() + { + using var tmpDirectory = new DisposableDirectory(Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString("N"))); + + var nupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.nupkg"); + var snupkgAbsoluteFilename = Path.Combine(tmpDirectory, "test.snupkg"); + var bogusNupkgAbsoluteFilename = Path.Combine(tmpDirectory, "testbogus.snupkg"); + + File.WriteAllText(nupkgAbsoluteFilename, string.Empty); + File.WriteAllText(snupkgAbsoluteFilename, string.Empty); + File.WriteAllText(bogusNupkgAbsoluteFilename, string.Empty); + + var packages = tmpDirectory.WorkingDirectory.GetFilesByGlobPattern(nupkgAbsoluteFilename + " " + snupkgAbsoluteFilename, out var glob).ToList(); + + Assert.That(glob.ToString(), Is.EqualTo(tmpDirectory.WorkingDirectory)); + + Assert.That(packages.Count, Is.EqualTo(2)); + Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); + } } } From 237eef7ebb341efc2fb08ec8fab39a05e78e22d0 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 03:24:59 +0200 Subject: [PATCH 39/40] Enable OSX support. --- Directory.Build.props | 5 +++++ test/GprTool.Tests/GlobExtensionTests.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2ab3560..1e90f9c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,6 +5,7 @@ $(MSBuildThisFileDirectory)nupkgs Windows Unix + MACOS Unknown @@ -20,4 +21,8 @@ $(DefineConstants);PLATFORM_UNIX + + $(DefineConstants);PLATFORM_MACOS + + \ No newline at end of file diff --git a/test/GprTool.Tests/GlobExtensionTests.cs b/test/GprTool.Tests/GlobExtensionTests.cs index 7834faa..5bcfe00 100644 --- a/test/GprTool.Tests/GlobExtensionTests.cs +++ b/test/GprTool.Tests/GlobExtensionTests.cs @@ -46,7 +46,7 @@ public void BuildBasePathFromGlob(string path, string baseDirectory, string expe var glob = Glob.Parse(path); Assert.That(glob.BuildBasePathFromGlob(baseDirectory), Is.EqualTo(expectedBaseDirectory)); } -#elif PLATFORM_UNIX +#elif PLATFORM_UNIX || PLATFORM_MACOS [TestCase("/mnt/c/test.nupkg", false)] [TestCase("/mnt/c/test", false)] [TestCase("packages", false)] From 0294e2caae3aef02d608ebcc7153ba04a389eaa5 Mon Sep 17 00:00:00 2001 From: Peter Rekdal Sunde Date: Wed, 17 Jun 2020 03:31:01 +0200 Subject: [PATCH 40/40] Update test syntax because packages list may be sorted differently based on the underlying OS. --- test/GprTool.Tests/IoExtensionsTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/GprTool.Tests/IoExtensionsTests.cs b/test/GprTool.Tests/IoExtensionsTests.cs index 662a136..51aa37b 100644 --- a/test/GprTool.Tests/IoExtensionsTests.cs +++ b/test/GprTool.Tests/IoExtensionsTests.cs @@ -98,8 +98,8 @@ public void GetFilesByGlobPattern_Is_Dot_Directory() Assert.That(globPattern, Is.EqualTo(globPattern)); Assert.That(packages.Count, Is.EqualTo(2)); - Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); - Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(nupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(snupkgAbsoluteFilename)); } [TestCase("test.nupkg")] @@ -128,7 +128,7 @@ public void GetFilesByGlobPattern_Is_Relative_Filename(string relativeFilename) Assert.That(globPattern, Is.EqualTo("test.nupkg")); Assert.That(packages.Count, Is.EqualTo(1)); - Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(nupkgAbsoluteFilename)); } [TestCase("test.nupkg test.snupkg")] @@ -153,8 +153,8 @@ public void GetFilesByGlobPattern_Is_Multiple_Relative_Filenames(string relative Assert.That(glob.ToString(), Is.EqualTo(tmpDirectory.WorkingDirectory)); Assert.That(packages.Count, Is.EqualTo(2)); - Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); - Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(nupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(snupkgAbsoluteFilename)); } [Test] @@ -178,7 +178,7 @@ public void GetFilesByGlobPattern_Is_FullPath_Filename() Assert.That(globPattern, Is.EqualTo("test.nupkg")); Assert.That(packages.Count, Is.EqualTo(1)); - Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(nupkgAbsoluteFilename)); } [Test] @@ -199,8 +199,8 @@ public void GetFilesByGlobPattern_Is_Multiple_FullPath_Filenames() Assert.That(glob.ToString(), Is.EqualTo(tmpDirectory.WorkingDirectory)); Assert.That(packages.Count, Is.EqualTo(2)); - Assert.That(packages[0], Is.EqualTo(nupkgAbsoluteFilename)); - Assert.That(packages[1], Is.EqualTo(snupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(nupkgAbsoluteFilename)); + Assert.That(packages, Does.Contain(snupkgAbsoluteFilename)); } } }