From d4bd23d788090431ecc702369df8c8e36489daaf Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 12:37:30 -0400 Subject: [PATCH 001/201] Creation of the project --- LocalProvider.cs | 21 ++++ NuGetUpdater.cs | 222 +++++++++++++++++++++++++++++++++++++ NuGetUpdaterTask.cs | 25 +++++ Nuget.Updater.csproj | 80 +++++++++++++ Nuget.Updater.targets | 11 ++ Properties/AssemblyInfo.cs | 36 ++++++ 6 files changed, 395 insertions(+) create mode 100644 LocalProvider.cs create mode 100644 NuGetUpdater.cs create mode 100644 NuGetUpdaterTask.cs create mode 100644 Nuget.Updater.csproj create mode 100644 Nuget.Updater.targets create mode 100644 Properties/AssemblyInfo.cs diff --git a/LocalProvider.cs b/LocalProvider.cs new file mode 100644 index 0000000..982212e --- /dev/null +++ b/LocalProvider.cs @@ -0,0 +1,21 @@ +using System; +using System.Net; +using NuGet; + +namespace Nuget.Updater +{ + internal class LocalNugetProvider : ICredentialProvider + { + private readonly string PAT; + private readonly string Email; + + public LocalNugetProvider(string email, string pat) + { + Email = email; + PAT = pat; + } + + public ICredentials GetCredentials(Uri uri, IWebProxy proxy, CredentialType credentialType, bool retrying) + => new System.Net.NetworkCredential(Email, PAT); + } +} \ No newline at end of file diff --git a/NuGetUpdater.cs b/NuGetUpdater.cs new file mode 100644 index 0000000..f4c4d97 --- /dev/null +++ b/NuGetUpdater.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using Microsoft.Build.Utilities; +using NuGet; +using Uno.Extensions; + +namespace Nuget.Updater +{ + public class NuGetUpdater + { + private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + + public static bool Execute(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag = "", string PAT = "") + { + if (excludeTag.IsNullOrWhiteSpace()) + { + excludeTag = "clear"; + } + + NuGet.HttpClient.DefaultCredentialProvider = new LocalNugetProvider("jerome.laban@nventive.com", PAT); + + UpdateProjectJson(log, solutionRoot, packageSources, packages, specialVersion, excludeTag); + UpdateProject(log, solutionRoot, packageSources, packages, specialVersion, excludeTag); + UpdateNuSpecs(log, solutionRoot, packageSources, packages, specialVersion, excludeTag); + + return true; + } + + private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag) + { + var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); + + log?.LogMessage($"Updating nuspec files..."); + + foreach (var package in packages) + { + var latestVersion = GetPackagesVersion(packageSources, package, specialVersion, excludeTag).FirstOrDefault(); + + log?.LogMessage($"Latest version for [{package}] is [{latestVersion}]"); + +#if DEBUG + Console.WriteLine($"Latest version for [{package}] is [{latestVersion}]"); +#endif + + if (latestVersion != null) + { + foreach (var nuspecFile in originalNuSpecFiles) + { + var doc = new XmlDocument() + { + PreserveWhitespace = true + }; + doc.Load(nuspecFile); + + + XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable); + mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); + + var nodes = doc.SelectNodes($"//x:dependency[@id='{package}']", mgr).OfType(); + + if (nodes.Any()) + { + foreach (var node in nodes) + { + if (!node.GetAttribute("version").Contains("{")) + { + // only nodes with explicit version, skip expansion. + node.SetAttribute("version", latestVersion.Version.ToString()); + } + } + + doc.Save(nuspecFile); + } + } + + } + } + } + + private static void UpdateProjectJson(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag) + { + var originalFiles = Directory.GetFiles(solutionRoot, "project.json", SearchOption.AllDirectories); + var originalContent = originalFiles.Select(File.ReadAllText).ToArray(); + var filesContent = originalContent.ToArray(); + + log?.LogMessage($"Updating project.json files..."); + + foreach (var package in packages) + { + var latestVersion = GetPackagesVersion(packageSources, package, specialVersion, excludeTag).FirstOrDefault(); + + log?.LogMessage($"Latest version for [{package}] is [{latestVersion}]"); + + if (latestVersion != null) + { + for (int i = 0; i < filesContent.Length; i++) + { + var originalMatch = $@"\""{latestVersion.Id}\"".*?\:.*?\"".*?\"""; + var replaced = $@"""{latestVersion.Id}"": ""{latestVersion.Version}"""; + + var newContent = Regex.Replace( + filesContent[i], + originalMatch, + replaced + , RegexOptions.IgnoreCase + ); + + filesContent[i] = newContent; + } + } + } + + for (int i = 0; i < originalFiles.Length; i++) + { + if (!filesContent[i].Equals(originalContent[i])) + { + log?.LogMessage($"Updating [{originalFiles[i]}] with [{specialVersion}] releases"); + + File.WriteAllText(originalFiles[i], filesContent[i], Encoding.UTF8); + } + } + } + private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag) + { + var originalFiles = Directory.GetFiles(solutionRoot, "*.csproj", SearchOption.AllDirectories); + + log?.LogMessage($"Updating PackageReference nodes..."); + + foreach (var package in packages) + { + var latestVersion = GetPackagesVersion(packageSources, package, specialVersion, excludeTag).FirstOrDefault(); + + log?.LogMessage($"Latest version for [{package}] is [{latestVersion}]"); + + if (latestVersion != null) + { + for (int i = 0; i < originalFiles.Length; i++) + { + var modified = false; + + var doc = new XmlDocument() + { + PreserveWhitespace = true + }; + doc.Load(originalFiles[i]); + + var nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("d", MsBuildNamespace); + + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{latestVersion.Id}']", nsmgr)) + { + if (packageReference.HasAttribute("Version", MsBuildNamespace)) + { + packageReference.SetAttribute("Version", MsBuildNamespace, latestVersion.Version.ToString()); + modified = true; + } + else + { + var node = packageReference.SelectSingleNode("d:Version", nsmgr); + + if (node != null) + { + node.InnerText = latestVersion.Version.ToString(); + modified = true; + } + } + } + + if (modified) + { + doc.Save(originalFiles[i]); + } + } + } + } + } + + private static IEnumerable GetPackagesVersion(string[] packageSources, string packageId, string specialVersion, string excludeTag) + { + foreach (var source in packageSources) + { + var repo = PackageRepositoryFactory.Default.CreateRepository("https://nventive.pkgs.visualstudio.com/_packaging/nventive/nuget/v2"); + + var packages = repo.FindPackagesById(packageId); + + packages = packages + .Where(item => IsSpecialVersion(specialVersion, item) && !ContainsTag(excludeTag, item)) + .OrderByDescending(item => item.Version); + + foreach (IPackage p in packages) + { + yield return p; + } + } + } + + private static bool ContainsTag(string tag, IPackage item) + { + if (tag.Equals("")) + { + return true; + } + + return item.Version.SpecialVersion?.Contains(tag) ?? false; + } + + private static bool IsSpecialVersion(string specialVersion, IPackage item) + { + if (item.Version.SpecialVersion.HasValue()) + { + return Regex.IsMatch(item.Version.SpecialVersion, specialVersion, RegexOptions.IgnoreCase); + } + + return false; + } + } +} diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs new file mode 100644 index 0000000..5009e3b --- /dev/null +++ b/NuGetUpdaterTask.cs @@ -0,0 +1,25 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Nuget.Updater +{ + public class NuGetUpdaterTask : Task + { + [Required] + public string SpecialVersion { get; set; } + + [Required] + public string[] Packages { get; set; } + + [Required] + public string SolutionRoot { get; set; } + + [Required] + public string[] PackageSources { get; set; } + + public override bool Execute() + { + return NuGetUpdater.Execute(Log, SolutionRoot, PackageSources, Packages, SpecialVersion); + } + } +} diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj new file mode 100644 index 0000000..b08a008 --- /dev/null +++ b/Nuget.Updater.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {29277270-8EFE-41A3-8CD8-D54EBF514C41} + Library + Properties + Nuget.Updater + Nuget.Updater + v4.6 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + 15.1.1012 + + + 15.1.1012 + + + 15.1.1012 + + + 2.1.1 + + + 2.14.0 + + + 4.0.0 + + + 1.7.0 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Nuget.Updater.targets b/Nuget.Updater.targets new file mode 100644 index 0000000..535c501 --- /dev/null +++ b/Nuget.Updater.targets @@ -0,0 +1,11 @@ + + + + + Nuget.Updater.dll + true + + + + + \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d566396 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Nuget.Updater")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Nuget.Updater")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("29277270-8efe-41a3-8cd8-d54ebf514c41")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] From 64f0add6fd78a7f6976ec3357e55d6bfff17ea1d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 12:50:58 -0400 Subject: [PATCH 002/201] Updated csproj for cross targeting --- Nuget.Updater.csproj | 109 ++++++++++--------------------------- Properties/AssemblyInfo.cs | 36 ------------ 2 files changed, 30 insertions(+), 115 deletions(-) delete mode 100644 Properties/AssemblyInfo.cs diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index b08a008..755b006 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,80 +1,31 @@ - - - - - Debug - AnyCPU - {29277270-8EFE-41A3-8CD8-D54EBF514C41} - Library - Properties - Nuget.Updater - Nuget.Updater - v4.6 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - 15.1.1012 - - - 15.1.1012 - - - 15.1.1012 - - - 2.1.1 - - - 2.14.0 - - - 4.0.0 - - - 1.7.0 - - - - - - - - - - - - - - - - - - - - - - - - + + + net46 + + + + 15.1.1012 + + + 15.1.1012 + + + 15.1.1012 + + + 2.1.1 + + + 2.14.0 + + + 4.0.0 + + + 1.7.0 + + + + + \ No newline at end of file diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index d566396..0000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Nuget.Updater")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Nuget.Updater")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("29277270-8efe-41a3-8cd8-d54ebf514c41")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] From cb6360c1350c440c84f4430262bae8bde63b2226 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 12:57:30 -0400 Subject: [PATCH 003/201] Configured Nuget package creation --- Nuget.Updater.csproj | 5 ++++- Nuget.Updater.targets => build/Nuget.Updater.targets | 0 2 files changed, 4 insertions(+), 1 deletion(-) rename Nuget.Updater.targets => build/Nuget.Updater.targets (100%) diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 755b006..77c9fd5 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,6 +1,7 @@  net46 + true @@ -26,6 +27,8 @@ - + + build\$(TargetFramework) + \ No newline at end of file diff --git a/Nuget.Updater.targets b/build/Nuget.Updater.targets similarity index 100% rename from Nuget.Updater.targets rename to build/Nuget.Updater.targets From 10401f4b4e79a95d93d83428fc1bb0cb6e27b169 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 14:24:47 -0400 Subject: [PATCH 004/201] Updated metadata for nuget updater --- Nuget.Updater.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 77c9fd5..ed35992 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -2,6 +2,8 @@ net46 true + nventive + Nuget Updater allows to automatically update the NuGet packages in a solution at build time From e6520da5bab3c7cc722a4fc70b97c39185cee1e1 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 14:50:12 -0400 Subject: [PATCH 005/201] Fixed binary path --- build/Nuget.Updater.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index 535c501..db1a47b 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -2,7 +2,7 @@ - Nuget.Updater.dll + ../lib/net46/Nuget.Updater.dll true From 2e40bab02ad54a1d9e8f0ba4d74928f04e1809f5 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 14:54:30 -0400 Subject: [PATCH 006/201] Fixed binary path --- build/Nuget.Updater.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index db1a47b..28f5fb3 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -2,7 +2,7 @@ - ../lib/net46/Nuget.Updater.dll + ..\..\lib\net46\Nuget.Updater.dll true From de17211e97bb07ef297f629daeb01c8dc6af18b8 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 14 Jun 2017 16:15:25 -0400 Subject: [PATCH 007/201] Ensured dependencies are included in the nuget package --- Nuget.Updater.csproj | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index ed35992..cc3b5c7 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,36 +1,30 @@  net46 - true + True nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time - - 15.1.1012 - - - 15.1.1012 - - - 15.1.1012 - - - 2.1.1 - - - 2.14.0 - - - 4.0.0 - - - 1.7.0 - + + + + + + + build\$(TargetFramework) + + + + true + lib\$(TargetFramework) + + + \ No newline at end of file From a6c64b77e80a3e99b19be5e02d4a1e720a99211f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 15 Jun 2017 11:29:14 -0400 Subject: [PATCH 008/201] Added PAT as required parameter for the task --- NuGetUpdaterTask.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs index 5009e3b..235e8b9 100644 --- a/NuGetUpdaterTask.cs +++ b/NuGetUpdaterTask.cs @@ -17,9 +17,12 @@ public class NuGetUpdaterTask : Task [Required] public string[] PackageSources { get; set; } + [Required] + public string PAT { get; set; } + public override bool Execute() { - return NuGetUpdater.Execute(Log, SolutionRoot, PackageSources, Packages, SpecialVersion); + return NuGetUpdater.Execute(Log, SolutionRoot, PackageSources, Packages, SpecialVersion, PAT: PAT); } } } From 461333882530ec2f526c4f70ed3d37a4463d925b Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 15 Jun 2017 15:35:44 -0400 Subject: [PATCH 009/201] Renamed tasks to avoid conflicts --- NuGetUpdater.cs => NuGetUpdaterExecution.cs | 2 +- NuGetUpdaterTask.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename NuGetUpdater.cs => NuGetUpdaterExecution.cs (99%) diff --git a/NuGetUpdater.cs b/NuGetUpdaterExecution.cs similarity index 99% rename from NuGetUpdater.cs rename to NuGetUpdaterExecution.cs index f4c4d97..bd51acb 100644 --- a/NuGetUpdater.cs +++ b/NuGetUpdaterExecution.cs @@ -11,7 +11,7 @@ namespace Nuget.Updater { - public class NuGetUpdater + public class NuGetUpdaterExecution { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs index 235e8b9..2dfc6d7 100644 --- a/NuGetUpdaterTask.cs +++ b/NuGetUpdaterTask.cs @@ -3,7 +3,7 @@ namespace Nuget.Updater { - public class NuGetUpdaterTask : Task + public class NuGetUpdater : Task { [Required] public string SpecialVersion { get; set; } @@ -22,7 +22,7 @@ public class NuGetUpdaterTask : Task public override bool Execute() { - return NuGetUpdater.Execute(Log, SolutionRoot, PackageSources, Packages, SpecialVersion, PAT: PAT); + return NuGetUpdaterExecution.Execute(Log, SolutionRoot, PackageSources, Packages, SpecialVersion, PAT: PAT); } } } From c2b10706c4e30dc30bb9a9b8f8fd8b536e275d41 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 15 Jun 2017 16:16:53 -0400 Subject: [PATCH 010/201] Fixed task name --- build/Nuget.Updater.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index 28f5fb3..93f3e49 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -6,6 +6,6 @@ true - + \ No newline at end of file From 32ba1bd69296439474888110112ad1611104e36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Thu, 22 Jun 2017 15:09:58 -0400 Subject: [PATCH 011/201] Add support for x-targeted projects updates --- NuGetUpdaterExecution.cs | 46 ++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index bd51acb..766df6d 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -151,25 +151,11 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, st var nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); + modified |= UpdateProjectReferenceVersions(latestVersion, modified, doc, nsmgr); - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{latestVersion.Id}']", nsmgr)) - { - if (packageReference.HasAttribute("Version", MsBuildNamespace)) - { - packageReference.SetAttribute("Version", MsBuildNamespace, latestVersion.Version.ToString()); - modified = true; - } - else - { - var node = packageReference.SelectSingleNode("d:Version", nsmgr); - - if (node != null) - { - node.InnerText = latestVersion.Version.ToString(); - modified = true; - } - } - } + var nsmgr2 = new XmlNamespaceManager(doc.NameTable); + nsmgr2.AddNamespace("d", ""); + modified |= UpdateProjectReferenceVersions(latestVersion, modified, doc, nsmgr2); if (modified) { @@ -180,6 +166,30 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, st } } + private static bool UpdateProjectReferenceVersions(IPackage latestVersion, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr) + { + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{latestVersion.Id}']", nsmgr)) + { + if (packageReference.HasAttribute("Version", MsBuildNamespace)) + { + packageReference.SetAttribute("Version", MsBuildNamespace, latestVersion.Version.ToString()); + modified = true; + } + else + { + var node = packageReference.SelectSingleNode("d:Version", nsmgr); + + if (node != null) + { + node.InnerText = latestVersion.Version.ToString(); + modified = true; + } + } + } + + return modified; + } + private static IEnumerable GetPackagesVersion(string[] packageSources, string packageId, string specialVersion, string excludeTag) { foreach (var source in packageSources) From 5d72bebb307c6359e92a71ae83884be4f9858f14 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 22 Jun 2017 17:08:35 -0400 Subject: [PATCH 012/201] Removed dependency to Uno.Core --- NuGetUpdaterExecution.cs | 5 ++--- Nuget.Updater.csproj | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 766df6d..0bee54c 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -7,7 +7,6 @@ using System.Xml; using Microsoft.Build.Utilities; using NuGet; -using Uno.Extensions; namespace Nuget.Updater { @@ -17,7 +16,7 @@ public class NuGetUpdaterExecution public static bool Execute(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag = "", string PAT = "") { - if (excludeTag.IsNullOrWhiteSpace()) + if (excludeTag == null || excludeTag.Trim() == "") { excludeTag = "clear"; } @@ -221,7 +220,7 @@ private static bool ContainsTag(string tag, IPackage item) private static bool IsSpecialVersion(string specialVersion, IPackage item) { - if (item.Version.SpecialVersion.HasValue()) + if (item.Version.SpecialVersion != null) { return Regex.IsMatch(item.Version.SpecialVersion, specialVersion, RegexOptions.IgnoreCase); } diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index cc3b5c7..f9a9159 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -12,7 +12,6 @@ - From 44dbb29646ea639f55ec5c65fe93a97dbce38a5a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 27 Jun 2017 11:25:57 -0400 Subject: [PATCH 013/201] Fixed nuget package structure --- Nuget.Updater.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index f9a9159..33aa285 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -4,6 +4,7 @@ True nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time + False @@ -15,14 +16,14 @@ - build\$(TargetFramework) + build true - lib\$(TargetFramework) + tools From 8e730c97d074e9999d076a70081794b0ffd0d710 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 27 Jun 2017 14:28:22 -0400 Subject: [PATCH 014/201] Optimized creation of package --- Nuget.Updater.csproj | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 33aa285..0a42eb2 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -4,7 +4,7 @@ True nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time - False + True @@ -19,12 +19,4 @@ build - - - - true - tools - - - \ No newline at end of file From 2925efcfbef0e4064fd5b003b075645d83e1f5a2 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 28 Jun 2017 13:59:38 -0400 Subject: [PATCH 015/201] Fixed task path --- build/Nuget.Updater.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index 93f3e49..3b12275 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -2,7 +2,7 @@ - ..\..\lib\net46\Nuget.Updater.dll + ..\tools\Nuget.Updater.dll true From 30e8b515aeef69df81be62917a30bb5ced8767c0 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 28 Jun 2017 15:07:39 -0400 Subject: [PATCH 016/201] Included dependencies in the nuget package --- Nuget.Updater.csproj | 22 +++++++++++++++------- build/Nuget.Updater.targets | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 0a42eb2..bff2b5d 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -4,19 +4,27 @@ True nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time - True + False - - - - - - + + all + + + all + build + + + + true + lib + + + \ No newline at end of file diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index 3b12275..21ef50e 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -2,7 +2,7 @@ - ..\tools\Nuget.Updater.dll + ..\lib\Nuget.Updater.dll true From 161e0163a415e9040de5ca2eefeedb122e4e33aa Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 4 Jul 2017 13:52:56 -0400 Subject: [PATCH 017/201] Ensured all nventive packages are taken in consideration when updating them --- LocalProvider.cs | 21 ------- NuGetUpdaterExecution.cs | 124 ++++++++++++++++++++++++--------------- NuGetUpdaterTask.cs | 8 +-- Nuget.Updater.csproj | 4 +- 4 files changed, 78 insertions(+), 79 deletions(-) delete mode 100644 LocalProvider.cs diff --git a/LocalProvider.cs b/LocalProvider.cs deleted file mode 100644 index 982212e..0000000 --- a/LocalProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Net; -using NuGet; - -namespace Nuget.Updater -{ - internal class LocalNugetProvider : ICredentialProvider - { - private readonly string PAT; - private readonly string Email; - - public LocalNugetProvider(string email, string pat) - { - Email = email; - PAT = pat; - } - - public ICredentials GetCredentials(Uri uri, IWebProxy proxy, CredentialType credentialType, bool retrying) - => new System.Net.NetworkCredential(Email, PAT); - } -} \ No newline at end of file diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 0bee54c..fd10a00 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -4,9 +4,16 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Xml; using Microsoft.Build.Utilities; using NuGet; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Packaging.Core; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; namespace Nuget.Updater { @@ -14,23 +21,42 @@ public class NuGetUpdaterExecution { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - public static bool Execute(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag = "", string PAT = "") + public static bool Execute(TaskLoggingHelper log, string solutionRoot, string specialVersion, string excludeTag = "", string PAT = "") { if (excludeTag == null || excludeTag.Trim() == "") { excludeTag = "clear"; } - NuGet.HttpClient.DefaultCredentialProvider = new LocalNugetProvider("jerome.laban@nventive.com", PAT); + var packages = GetPackages(PAT); - UpdateProjectJson(log, solutionRoot, packageSources, packages, specialVersion, excludeTag); - UpdateProject(log, solutionRoot, packageSources, packages, specialVersion, excludeTag); - UpdateNuSpecs(log, solutionRoot, packageSources, packages, specialVersion, excludeTag); + UpdateProjectJson(log, solutionRoot, packages, specialVersion, excludeTag); + UpdateProject(log, solutionRoot, packages, specialVersion, excludeTag); + UpdateNuSpecs(log, solutionRoot, packages, specialVersion, excludeTag); return true; } - private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag) + private static IPackageSearchMetadata[] GetPackages(string PAT) + { + var settings = Settings.LoadDefaultSettings(null); + var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + + var source = new PackageSource("https://nventive.pkgs.visualstudio.com/_packaging/nventive/nuget/v3/index.json", "nventive") + { + Credentials = PackageSourceCredential.FromUserInput("nventive", "it@nventive.com", PAT, false) + }; + var repository = repositoryProvider.CreateRepository(source); + + var searchResource = repository.GetResource(); + + return searchResource + .SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) + .Result + .ToArray(); + } + + private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) { var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); @@ -38,12 +64,17 @@ private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, st foreach (var package in packages) { - var latestVersion = GetPackagesVersion(packageSources, package, specialVersion, excludeTag).FirstOrDefault(); + var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); - log?.LogMessage($"Latest version for [{package}] is [{latestVersion}]"); + if(latestVersion == null) + { + continue; + } + + log?.LogMessage($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); #if DEBUG - Console.WriteLine($"Latest version for [{package}] is [{latestVersion}]"); + Console.WriteLine($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); #endif if (latestVersion != null) @@ -60,7 +91,7 @@ private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, st XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable); mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); - var nodes = doc.SelectNodes($"//x:dependency[@id='{package}']", mgr).OfType(); + var nodes = doc.SelectNodes($"//x:dependency[@id='{package.Title}']", mgr).OfType(); if (nodes.Any()) { @@ -81,7 +112,7 @@ private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, st } } - private static void UpdateProjectJson(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag) + private static void UpdateProjectJson(TaskLoggingHelper log, string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) { var originalFiles = Directory.GetFiles(solutionRoot, "project.json", SearchOption.AllDirectories); var originalContent = originalFiles.Select(File.ReadAllText).ToArray(); @@ -91,16 +122,21 @@ private static void UpdateProjectJson(TaskLoggingHelper log, string solutionRoot foreach (var package in packages) { - var latestVersion = GetPackagesVersion(packageSources, package, specialVersion, excludeTag).FirstOrDefault(); + var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); + + if (latestVersion == null) + { + continue; + } - log?.LogMessage($"Latest version for [{package}] is [{latestVersion}]"); + log?.LogMessage($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); if (latestVersion != null) { for (int i = 0; i < filesContent.Length; i++) { - var originalMatch = $@"\""{latestVersion.Id}\"".*?\:.*?\"".*?\"""; - var replaced = $@"""{latestVersion.Id}"": ""{latestVersion.Version}"""; + var originalMatch = $@"\""{package.Title}\"".*?\:.*?\"".*?\"""; + var replaced = $@"""{package.Title}"": ""{latestVersion.Version}"""; var newContent = Regex.Replace( filesContent[i], @@ -124,7 +160,8 @@ private static void UpdateProjectJson(TaskLoggingHelper log, string solutionRoot } } } - private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, string[] packageSources, string[] packages, string specialVersion, string excludeTag) + + private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) { var originalFiles = Directory.GetFiles(solutionRoot, "*.csproj", SearchOption.AllDirectories); @@ -132,9 +169,14 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, st foreach (var package in packages) { - var latestVersion = GetPackagesVersion(packageSources, package, specialVersion, excludeTag).FirstOrDefault(); + var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); + + if (latestVersion == null) + { + continue; + } - log?.LogMessage($"Latest version for [{package}] is [{latestVersion}]"); + log?.LogMessage($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); if (latestVersion != null) { @@ -150,11 +192,11 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, st var nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(latestVersion, modified, doc, nsmgr); + modified |= UpdateProjectReferenceVersions(package.Title, latestVersion.Version, modified, doc, nsmgr); var nsmgr2 = new XmlNamespaceManager(doc.NameTable); nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(latestVersion, modified, doc, nsmgr2); + modified |= UpdateProjectReferenceVersions(package.Title, latestVersion.Version, modified, doc, nsmgr2); if (modified) { @@ -165,13 +207,13 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, st } } - private static bool UpdateProjectReferenceVersions(IPackage latestVersion, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr) + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr) { - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{latestVersion.Id}']", nsmgr)) + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", nsmgr)) { if (packageReference.HasAttribute("Version", MsBuildNamespace)) { - packageReference.SetAttribute("Version", MsBuildNamespace, latestVersion.Version.ToString()); + packageReference.SetAttribute("Version", MsBuildNamespace, version.ToString()); modified = true; } else @@ -180,7 +222,7 @@ private static bool UpdateProjectReferenceVersions(IPackage latestVersion, bool if (node != null) { - node.InnerText = latestVersion.Version.ToString(); + node.InnerText = version.ToString(); modified = true; } } @@ -189,43 +231,29 @@ private static bool UpdateProjectReferenceVersions(IPackage latestVersion, bool return modified; } - private static IEnumerable GetPackagesVersion(string[] packageSources, string packageId, string specialVersion, string excludeTag) + private static VersionInfo GetLatestVersion(IPackageSearchMetadata package, string specialVersion, string excludeTag) { - foreach (var source in packageSources) - { - var repo = PackageRepositoryFactory.Default.CreateRepository("https://nventive.pkgs.visualstudio.com/_packaging/nventive/nuget/v2"); - - var packages = repo.FindPackagesById(packageId); - - packages = packages - .Where(item => IsSpecialVersion(specialVersion, item) && !ContainsTag(excludeTag, item)) - .OrderByDescending(item => item.Version); + var versions = package.GetVersionsAsync().Result; - foreach (IPackage p in packages) - { - yield return p; - } - } + return versions + .Where(v => IsSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) + .OrderByDescending(v => v.Version.Version) + .FirstOrDefault(); } - private static bool ContainsTag(string tag, IPackage item) + private static bool ContainsTag(string tag, VersionInfo version) { if (tag.Equals("")) { return true; } - return item.Version.SpecialVersion?.Contains(tag) ?? false; + return version.Version?.ReleaseLabels?.Contains(tag) ?? false; } - private static bool IsSpecialVersion(string specialVersion, IPackage item) + private static bool IsSpecialVersion(string specialVersion, VersionInfo version) { - if (item.Version.SpecialVersion != null) - { - return Regex.IsMatch(item.Version.SpecialVersion, specialVersion, RegexOptions.IgnoreCase); - } - - return false; + return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; } } } diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs index 2dfc6d7..3979b21 100644 --- a/NuGetUpdaterTask.cs +++ b/NuGetUpdaterTask.cs @@ -8,21 +8,15 @@ public class NuGetUpdater : Task [Required] public string SpecialVersion { get; set; } - [Required] - public string[] Packages { get; set; } - [Required] public string SolutionRoot { get; set; } - [Required] - public string[] PackageSources { get; set; } - [Required] public string PAT { get; set; } public override bool Execute() { - return NuGetUpdaterExecution.Execute(Log, SolutionRoot, PackageSources, Packages, SpecialVersion, PAT: PAT); + return NuGetUpdaterExecution.Execute(Log, SolutionRoot, SpecialVersion, PAT: PAT); } } } diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index bff2b5d..b4a0af0 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -10,9 +10,7 @@ all - - all - + From 3910686cff6048d9f6c4ada4a26b48dd728184d0 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 6 Jul 2017 10:08:11 -0400 Subject: [PATCH 018/201] Added logging --- NuGetUpdaterExecution.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index fd10a00..017efad 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -200,6 +200,7 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, IP if (modified) { + log?.LogMessage($"Updating [{originalFiles[i]}] with [{specialVersion}] releases"); doc.Save(originalFiles[i]); } } From 267637fc258525db204ae6322e98fcd3ce283bb6 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 6 Jul 2017 13:16:44 -0400 Subject: [PATCH 019/201] Fixed issue where PackageReference version was not updated if declared as an attribute --- NuGetUpdaterExecution.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 017efad..c22d5e3 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -192,7 +192,7 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, IP var nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(package.Title, latestVersion.Version, modified, doc, nsmgr); + modified |= UpdateProjectReferenceVersions(package.Title, latestVersion.Version, modified, doc, nsmgr, MsBuildNamespace); var nsmgr2 = new XmlNamespaceManager(doc.NameTable); nsmgr2.AddNamespace("d", ""); @@ -208,13 +208,13 @@ private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, IP } } - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr) + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr, string namespaceURI = "") { foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", nsmgr)) { - if (packageReference.HasAttribute("Version", MsBuildNamespace)) + if (packageReference.HasAttribute("Version", namespaceURI)) { - packageReference.SetAttribute("Version", MsBuildNamespace, version.ToString()); + packageReference.SetAttribute("Version", namespaceURI, version.ToString()); modified = true; } else From 6f13b1703a37c7d7115d0c3f62fdb427cbc67411 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 13 Jul 2017 12:28:00 -0400 Subject: [PATCH 020/201] Improved process and added more logging --- NuGetUpdaterExecution.cs | 253 ++++++++++++++++++++++----------------- 1 file changed, 141 insertions(+), 112 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index c22d5e3..054114f 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -7,10 +6,8 @@ using System.Threading; using System.Xml; using Microsoft.Build.Utilities; -using NuGet; using NuGet.Common; using NuGet.Configuration; -using NuGet.Packaging.Core; using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.Versioning; @@ -21,8 +18,12 @@ public class NuGetUpdaterExecution { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + private static TaskLoggingHelper _log; + public static bool Execute(TaskLoggingHelper log, string solutionRoot, string specialVersion, string excludeTag = "", string PAT = "") { + _log = log; + if (excludeTag == null || excludeTag.Trim() == "") { excludeTag = "clear"; @@ -30,9 +31,7 @@ public static bool Execute(TaskLoggingHelper log, string solutionRoot, string sp var packages = GetPackages(PAT); - UpdateProjectJson(log, solutionRoot, packages, specialVersion, excludeTag); - UpdateProject(log, solutionRoot, packages, specialVersion, excludeTag); - UpdateNuSpecs(log, solutionRoot, packages, specialVersion, excludeTag); + UpdatePackages(solutionRoot, packages, specialVersion, excludeTag); return true; } @@ -50,160 +49,153 @@ private static IPackageSearchMetadata[] GetPackages(string PAT) var searchResource = repository.GetResource(); + + _log?.LogMessage($"Pulling NuGet packages from {source.SourceUri}"); +#if DEBUG + Console.WriteLine($"Pulling NuGet packages from {source.SourceUri}"); +#endif + return searchResource .SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) .Result .ToArray(); } - private static void UpdateNuSpecs(TaskLoggingHelper log, string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) + private static void UpdatePackages(string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) { var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); - log?.LogMessage($"Updating nuspec files..."); + var originalJsonFiles = Directory.GetFiles(solutionRoot, "project.json", SearchOption.AllDirectories); + + var originalProjectFiles = Directory.GetFiles(solutionRoot, "*.csproj", SearchOption.AllDirectories); foreach (var package in packages) { var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); - if(latestVersion == null) + if (latestVersion == null) { continue; } - log?.LogMessage($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); - + _log?.LogMessage($"Latest {specialVersion} version for [{package.Title}] is [{latestVersion}]"); #if DEBUG - Console.WriteLine($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); + Console.WriteLine($"Latest {specialVersion} version for [{package.Title}] is [{latestVersion}]"); #endif - if (latestVersion != null) + UpdateNuSpecs(package.Title, latestVersion, originalNuSpecFiles); + + UpdateProjectJson(package.Title, latestVersion, originalJsonFiles); + + UpdateProjects(package.Title, latestVersion, originalProjectFiles); + } + } + + private static void UpdateNuSpecs(string packageName, NuGetVersion latestVersion, string[] nuspecFiles) + { + foreach (var nuspecFile in nuspecFiles) + { + var doc = new XmlDocument() { - foreach (var nuspecFile in originalNuSpecFiles) - { - var doc = new XmlDocument() - { - PreserveWhitespace = true - }; - doc.Load(nuspecFile); + PreserveWhitespace = true + }; + doc.Load(nuspecFile); + + var mgr = new XmlNamespaceManager(doc.NameTable); + mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); - XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable); - mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); + var nodes = doc + .SelectNodes($"//x:dependency[@id='{packageName}']", mgr) + .OfType(); + + foreach (var node in nodes) + { + var versionNodeValue = node.GetAttribute("version"); - var nodes = doc.SelectNodes($"//x:dependency[@id='{package.Title}']", mgr).OfType(); + // only nodes with explicit version, skip expansion. + if (!versionNodeValue.Contains("{")) + { + var currentVersion = new NuGetVersion(versionNodeValue); - if (nodes.Any()) + if (currentVersion < latestVersion) + { + node.SetAttribute("version", latestVersion.ToString()); + LogUpdate(packageName, currentVersion, latestVersion, nuspecFile); + } + else { - foreach (var node in nodes) - { - if (!node.GetAttribute("version").Contains("{")) - { - // only nodes with explicit version, skip expansion. - node.SetAttribute("version", latestVersion.Version.ToString()); - } - } - - doc.Save(nuspecFile); + LogNoUpdate(packageName, currentVersion, nuspecFile); } } - + } + + if (nodes.Any()) + { + doc.Save(nuspecFile); } } } - private static void UpdateProjectJson(TaskLoggingHelper log, string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) + private static void UpdateProjectJson(string packageName, NuGetVersion latestVersion, string[] jsonFiles) { - var originalFiles = Directory.GetFiles(solutionRoot, "project.json", SearchOption.AllDirectories); - var originalContent = originalFiles.Select(File.ReadAllText).ToArray(); - var filesContent = originalContent.ToArray(); + var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; + var replaced = $@"""{packageName}"": ""{latestVersion}"""; - log?.LogMessage($"Updating project.json files..."); - - foreach (var package in packages) + for (int i = 0; i < jsonFiles.Length; i++) { - var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); + var file = jsonFiles[i]; + var fileContent = File.ReadAllText(file); - if (latestVersion == null) + var match = Regex.Match(fileContent, originalMatch, RegexOptions.IgnoreCase); + if (match?.Success ?? false) { - continue; - } - - log?.LogMessage($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); + var currentVersion = new NuGetVersion(match.Groups[1].Value); - if (latestVersion != null) - { - for (int i = 0; i < filesContent.Length; i++) + if (currentVersion < latestVersion) { - var originalMatch = $@"\""{package.Title}\"".*?\:.*?\"".*?\"""; - var replaced = $@"""{package.Title}"": ""{latestVersion.Version}"""; - var newContent = Regex.Replace( - filesContent[i], + fileContent, originalMatch, - replaced - , RegexOptions.IgnoreCase + replaced, + RegexOptions.IgnoreCase ); - filesContent[i] = newContent; - } - } - } - - for (int i = 0; i < originalFiles.Length; i++) - { - if (!filesContent[i].Equals(originalContent[i])) - { - log?.LogMessage($"Updating [{originalFiles[i]}] with [{specialVersion}] releases"); + File.WriteAllText(file, newContent, Encoding.UTF8); - File.WriteAllText(originalFiles[i], filesContent[i], Encoding.UTF8); + LogUpdate(packageName, currentVersion, latestVersion, file); + } + else + { + LogNoUpdate(packageName, currentVersion, file); + } } } } - private static void UpdateProject(TaskLoggingHelper log, string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) + private static void UpdateProjects(string packageName, NuGetVersion latestVersion, string[] projectFiles) { - var originalFiles = Directory.GetFiles(solutionRoot, "*.csproj", SearchOption.AllDirectories); - - log?.LogMessage($"Updating PackageReference nodes..."); - - foreach (var package in packages) + for (int i = 0; i < projectFiles.Length; i++) { - var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); + var modified = false; - if (latestVersion == null) + var doc = new XmlDocument() { - continue; - } + PreserveWhitespace = true + }; + doc.Load(projectFiles[i]); - log?.LogMessage($"Latest version for [{package.Title}] is [{latestVersion.Version}]"); + var nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("d", MsBuildNamespace); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, nsmgr); - if (latestVersion != null) - { - for (int i = 0; i < originalFiles.Length; i++) - { - var modified = false; - - var doc = new XmlDocument() - { - PreserveWhitespace = true - }; - doc.Load(originalFiles[i]); - - var nsmgr = new XmlNamespaceManager(doc.NameTable); - nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(package.Title, latestVersion.Version, modified, doc, nsmgr, MsBuildNamespace); - - var nsmgr2 = new XmlNamespaceManager(doc.NameTable); - nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(package.Title, latestVersion.Version, modified, doc, nsmgr2); + var nsmgr2 = new XmlNamespaceManager(doc.NameTable); + nsmgr2.AddNamespace("d", ""); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, nsmgr2); - if (modified) - { - log?.LogMessage($"Updating [{originalFiles[i]}] with [{specialVersion}] releases"); - doc.Save(originalFiles[i]); - } - } + if (modified) + { + doc.Save(projectFiles[i]); } } } @@ -214,8 +206,18 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { if (packageReference.HasAttribute("Version", namespaceURI)) { - packageReference.SetAttribute("Version", namespaceURI, version.ToString()); - modified = true; + var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); + + if (currentVersion < version) + { + packageReference.SetAttribute("Version", namespaceURI, version.ToString()); + modified = true; + LogUpdate(packageName, currentVersion, version, doc.BaseURI); + } + else + { + LogNoUpdate(packageName, currentVersion, doc.BaseURI); + } } else { @@ -223,8 +225,18 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers if (node != null) { - node.InnerText = version.ToString(); - modified = true; + var currentVersion = new NuGetVersion(node.InnerText); + + if (currentVersion < version) + { + node.InnerText = version.ToString(); + modified = true; + LogUpdate(packageName, currentVersion, version, doc.BaseURI); + } + else + { + LogNoUpdate(packageName, currentVersion, doc.BaseURI); + } } } } @@ -232,14 +244,15 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers return modified; } - private static VersionInfo GetLatestVersion(IPackageSearchMetadata package, string specialVersion, string excludeTag) + private static NuGetVersion GetLatestVersion(IPackageSearchMetadata package, string specialVersion, string excludeTag) { - var versions = package.GetVersionsAsync().Result; + var versions = package.GetVersionsAsync().Result.OrderByDescending(v => v.Version); return versions .Where(v => IsSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) - .OrderByDescending(v => v.Version.Version) - .FirstOrDefault(); + .OrderByDescending(v => v.Version) + .FirstOrDefault() + ?.Version; } private static bool ContainsTag(string tag, VersionInfo version) @@ -256,5 +269,21 @@ private static bool IsSpecialVersion(string specialVersion, VersionInfo version) { return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; } + + private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) + { + _log?.LogMessage($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); +#if DEBUG + Console.WriteLine($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); +#endif + } + + private static void LogNoUpdate(string packageName, NuGetVersion currentVersion, string file) + { + _log?.LogMessage($"Found higher version of [{packageName}] ([{currentVersion}]) in [{file}]"); +#if DEBUG + Console.WriteLine($"Found higher version of [{packageName}] ([{currentVersion}]) in [{file}]"); +#endif + } } } From 6eef64ba77de9aec370da9528babf4ff8fd7e615 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 28 Jul 2017 10:00:23 -0400 Subject: [PATCH 021/201] Prevented "clear" packages from being excluded from update by default Added documentation on how to run the nuget updater --- NuGetUpdaterExecution.cs | 5 ----- NuGetUpdaterTask.cs | 4 +++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 054114f..a176dc2 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -24,11 +24,6 @@ public static bool Execute(TaskLoggingHelper log, string solutionRoot, string sp { _log = log; - if (excludeTag == null || excludeTag.Trim() == "") - { - excludeTag = "clear"; - } - var packages = GetPackages(PAT); UpdatePackages(solutionRoot, packages, specialVersion, excludeTag); diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs index 3979b21..96bca8f 100644 --- a/NuGetUpdaterTask.cs +++ b/NuGetUpdaterTask.cs @@ -8,6 +8,8 @@ public class NuGetUpdater : Task [Required] public string SpecialVersion { get; set; } + public string ExcludeTag { get; set; } + [Required] public string SolutionRoot { get; set; } @@ -16,7 +18,7 @@ public class NuGetUpdater : Task public override bool Execute() { - return NuGetUpdaterExecution.Execute(Log, SolutionRoot, SpecialVersion, PAT: PAT); + return NuGetUpdaterExecution.Execute(Log, SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT); } } } From 1580f1b201410f4c31229860178c108f9e894344 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 28 Jul 2017 12:23:32 -0400 Subject: [PATCH 022/201] Fixed null reff when not setting the exclude tag --- NuGetUpdaterExecution.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index a176dc2..74424c2 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -252,9 +252,9 @@ private static NuGetVersion GetLatestVersion(IPackageSearchMetadata package, str private static bool ContainsTag(string tag, VersionInfo version) { - if (tag.Equals("")) + if (tag?.Equals("") ?? true) { - return true; + return false; } return version.Version?.ReleaseLabels?.Contains(tag) ?? false; From 0bbe0d113ef2b440239ea660ab25a921a32d0996 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 28 Jul 2017 12:35:42 -0400 Subject: [PATCH 023/201] Added more null ref checks --- NuGetUpdaterExecution.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 74424c2..be37199 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -257,7 +257,7 @@ private static bool ContainsTag(string tag, VersionInfo version) return false; } - return version.Version?.ReleaseLabels?.Contains(tag) ?? false; + return version?.Version?.ReleaseLabels?.Contains(tag) ?? false; } private static bool IsSpecialVersion(string specialVersion, VersionInfo version) From 31fa54e37fc8a7af90a25f2f5b6a8a36399eccfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Mon, 30 Oct 2017 10:41:17 -0400 Subject: [PATCH 024/201] Add support for nuget branch switch. --- NuGetBranchSwitch.cs | 26 +++++++ NuGetBranchSwitchExection.cs | 133 +++++++++++++++++++++++++++++++++++ Nuget.Updater.csproj | 3 + build/Nuget.Updater.targets | 1 + 4 files changed, 163 insertions(+) create mode 100644 NuGetBranchSwitch.cs create mode 100644 NuGetBranchSwitchExection.cs diff --git a/NuGetBranchSwitch.cs b/NuGetBranchSwitch.cs new file mode 100644 index 0000000..7f31d8c --- /dev/null +++ b/NuGetBranchSwitch.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Nuget.Updater +{ + public class NuGetBranchSwitch : Task + { + [Required] + public string SolutionRoot { get; set; } + + [Required] + public string[] Packages { get; set; } + + [Required] + public string SourceBranch { get; set; } + + [Required] + public string TargetBranch { get; set; } + + public override bool Execute() => new NuGetBranchSwitchExection(Log, SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); + } +} diff --git a/NuGetBranchSwitchExection.cs b/NuGetBranchSwitchExection.cs new file mode 100644 index 0000000..9ef1c17 --- /dev/null +++ b/NuGetBranchSwitchExection.cs @@ -0,0 +1,133 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml; +using Microsoft.Build.Utilities; +using NuGet.Versioning; + +namespace Nuget.Updater +{ + public class NuGetBranchSwitchExection + { + private static TaskLoggingHelper _log; + + + private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + + private readonly string[] _packages; + private readonly string _sourceBranch; + private readonly string _targetBranch; + private readonly string _solutionRoot; + + public NuGetBranchSwitchExection(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) + { + _packages = packages; + _sourceBranch = sourceBranch; + _targetBranch = targetBranch; + _solutionRoot = solutionRoot; + } + + public bool Execute() + { + var projectFiles = Directory.GetFiles(_solutionRoot, "*.csproj", SearchOption.AllDirectories); + + foreach(var package in _packages) + { + UpdateProjects(package, projectFiles); + } + + return true; + } + + private void UpdateProjects(string packageName, string[] projectFiles) + { + for (int i = 0; i < projectFiles.Length; i++) + { + var modified = false; + + var doc = new XmlDocument() + { + PreserveWhitespace = true + }; + doc.Load(projectFiles[i]); + + var nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("d", MsBuildNamespace); + modified |= UpdateProjectReferenceVersions(packageName, modified, doc, nsmgr); + + var nsmgr2 = new XmlNamespaceManager(doc.NameTable); + nsmgr2.AddNamespace("d", ""); + modified |= UpdateProjectReferenceVersions(packageName, modified, doc, nsmgr2); + + if (modified) + { + doc.Save(projectFiles[i]); + } + } + } + + private bool UpdateProjectReferenceVersions(string packageName, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr, string namespaceURI = "") + { + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", nsmgr)) + { + (string version, Action updater) GetVersion() + { + if (packageReference.HasAttribute("Version", namespaceURI)) + { + return ( + packageReference.Attributes["Version"].Value, + v => packageReference.SetAttribute("Version", namespaceURI, v) + ); + } + else if(packageReference.SelectSingleNode("d:Version", nsmgr) is XmlNode node) + { + return ( + node.InnerText, + v => node.InnerText = v + ); + } + else + { + return (null, null); + } + } + + var (version, updater) = GetVersion(); + + var currentVersion = new NuGetVersion(version); + + var hasUpdateableLabel = currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); + + if (hasUpdateableLabel ?? false) + { + var updatedLabels = currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); + + var newVersion = new NuGetVersion( + major: currentVersion.Major, + minor: currentVersion.Minor, + patch: currentVersion.Patch, + revision: currentVersion.Revision, + releaseLabels: updatedLabels, + metadata: currentVersion.Metadata + ); + + updater(newVersion.ToFullString()); + + LogUpdate(packageName, currentVersion, newVersion, doc.BaseURI); + + modified = true; + } + } + + return modified; + } + + private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) + { + _log?.LogMessage($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); +#if DEBUG + Console.WriteLine($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); +#endif + } + } +} \ No newline at end of file diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index b4a0af0..7677774 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -11,6 +11,9 @@ all + + 4.4.0 + diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index 21ef50e..c3a60bd 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -7,5 +7,6 @@ + \ No newline at end of file From f768d8801607154020889c92a98bfae81d331ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Mon, 30 Oct 2017 11:22:33 -0400 Subject: [PATCH 025/201] Add switch branch for nuspec files. --- NuGetBranchSwitch.cs | 2 +- ...ection.cs => NuGetBranchSwitchExecution.cs | 68 ++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-) rename NuGetBranchSwitchExection.cs => NuGetBranchSwitchExecution.cs (64%) diff --git a/NuGetBranchSwitch.cs b/NuGetBranchSwitch.cs index 7f31d8c..5979118 100644 --- a/NuGetBranchSwitch.cs +++ b/NuGetBranchSwitch.cs @@ -21,6 +21,6 @@ public class NuGetBranchSwitch : Task [Required] public string TargetBranch { get; set; } - public override bool Execute() => new NuGetBranchSwitchExection(Log, SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); + public override bool Execute() => new NuGetBranchSwitchExecution(Log, SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); } } diff --git a/NuGetBranchSwitchExection.cs b/NuGetBranchSwitchExecution.cs similarity index 64% rename from NuGetBranchSwitchExection.cs rename to NuGetBranchSwitchExecution.cs index 9ef1c17..eb0958c 100644 --- a/NuGetBranchSwitchExection.cs +++ b/NuGetBranchSwitchExecution.cs @@ -7,7 +7,7 @@ namespace Nuget.Updater { - public class NuGetBranchSwitchExection + public class NuGetBranchSwitchExecution { private static TaskLoggingHelper _log; @@ -19,7 +19,7 @@ public class NuGetBranchSwitchExection private readonly string _targetBranch; private readonly string _solutionRoot; - public NuGetBranchSwitchExection(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) + public NuGetBranchSwitchExecution(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) { _packages = packages; _sourceBranch = sourceBranch; @@ -30,12 +30,18 @@ public NuGetBranchSwitchExection(TaskLoggingHelper log, string solutionRoot, str public bool Execute() { var projectFiles = Directory.GetFiles(_solutionRoot, "*.csproj", SearchOption.AllDirectories); + var nuspecFiles = Directory.GetFiles(_solutionRoot, "*.nuspec", SearchOption.AllDirectories); - foreach(var package in _packages) + foreach (var package in _packages) { UpdateProjects(package, projectFiles); } + foreach (var package in _packages) + { + UpdateNuSpecs(package, nuspecFiles); + } + return true; } @@ -122,6 +128,62 @@ private bool UpdateProjectReferenceVersions(string packageName, bool modified, X return modified; } + private void UpdateNuSpecs(string packageName, string[] nuspecFiles) + { + foreach (var nuspecFile in nuspecFiles) + { + var doc = new XmlDocument() + { + PreserveWhitespace = true + }; + doc.Load(nuspecFile); + + + var mgr = new XmlNamespaceManager(doc.NameTable); + mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); + + var nodes = doc + .SelectNodes($"//x:dependency[@id='{packageName}']", mgr) + .OfType(); + + foreach (var node in nodes) + { + var versionNodeValue = node.GetAttribute("version"); + + // only nodes with explicit version, skip expansion. + if (!versionNodeValue.Contains("{")) + { + var currentVersion = new NuGetVersion(versionNodeValue); + + var hasUpdateableLabel = currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); + + if (hasUpdateableLabel ?? false) + { + var updatedLabels = currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); + + var newVersion = new NuGetVersion( + major: currentVersion.Major, + minor: currentVersion.Minor, + patch: currentVersion.Patch, + revision: currentVersion.Revision, + releaseLabels: updatedLabels, + metadata: currentVersion.Metadata + ); + + node.SetAttribute("version", newVersion.ToFullString()); + + LogUpdate(packageName, currentVersion, newVersion, doc.BaseURI); + } + } + } + + if (nodes.Any()) + { + doc.Save(nuspecFile); + } + } + } + private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) { _log?.LogMessage($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); From c3dcf011da12cfc08f1557c1b2b76e09d133f4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 1 Nov 2017 20:57:38 -0400 Subject: [PATCH 026/201] Add support for regex package matching for nuget switcher --- NuGetBranchSwitchExecution.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index eb0958c..02df830 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Xml; using Microsoft.Build.Utilities; using NuGet.Versioning; @@ -74,7 +75,11 @@ private void UpdateProjects(string packageName, string[] projectFiles) private bool UpdateProjectReferenceVersions(string packageName, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr, string namespaceURI = "") { - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", nsmgr)) + var nodes = doc.SelectNodes($"//d:PackageReference", nsmgr) + .OfType() + .Where(e => Regex.Match(e.GetAttribute("Include"), $"^{packageName}$").Success); + + foreach (var packageReference in nodes) { (string version, Action updater) GetVersion() { @@ -119,7 +124,7 @@ private bool UpdateProjectReferenceVersions(string packageName, bool modified, X updater(newVersion.ToFullString()); - LogUpdate(packageName, currentVersion, newVersion, doc.BaseURI); + LogUpdate(packageReference.GetAttribute("Include"), currentVersion, newVersion, doc.BaseURI); modified = true; } @@ -143,8 +148,9 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); var nodes = doc - .SelectNodes($"//x:dependency[@id='{packageName}']", mgr) - .OfType(); + .SelectNodes($"//x:dependency", mgr) + .OfType() + .Where(e => Regex.Match(e.GetAttribute("id"), $"^{packageName}$").Success); foreach (var node in nodes) { @@ -172,7 +178,7 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) node.SetAttribute("version", newVersion.ToFullString()); - LogUpdate(packageName, currentVersion, newVersion, doc.BaseURI); + LogUpdate(node.GetAttribute("id"), currentVersion, newVersion, doc.BaseURI); } } } From fa426fb724f7cf94bdddf7e93c2db8d8c638929a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 10 Nov 2017 14:14:54 -0500 Subject: [PATCH 027/201] Add support for stable switching. --- NuGetBranchSwitchExecution.cs | 142 ++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index 02df830..f013290 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -23,9 +23,9 @@ public class NuGetBranchSwitchExecution public NuGetBranchSwitchExecution(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) { _packages = packages; - _sourceBranch = sourceBranch; - _targetBranch = targetBranch; - _solutionRoot = solutionRoot; + _sourceBranch = sourceBranch?.Trim() ?? ""; + _targetBranch = targetBranch?.Trim() ?? ""; + _solutionRoot = solutionRoot; } public bool Execute() @@ -80,60 +80,81 @@ private bool UpdateProjectReferenceVersions(string packageName, bool modified, X .Where(e => Regex.Match(e.GetAttribute("Include"), $"^{packageName}$").Success); foreach (var packageReference in nodes) - { - (string version, Action updater) GetVersion() - { - if (packageReference.HasAttribute("Version", namespaceURI)) - { - return ( - packageReference.Attributes["Version"].Value, - v => packageReference.SetAttribute("Version", namespaceURI, v) - ); - } - else if(packageReference.SelectSingleNode("d:Version", nsmgr) is XmlNode node) - { - return ( - node.InnerText, - v => node.InnerText = v - ); - } - else - { - return (null, null); - } - } - - var (version, updater) = GetVersion(); - - var currentVersion = new NuGetVersion(version); - - var hasUpdateableLabel = currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); - - if (hasUpdateableLabel ?? false) - { - var updatedLabels = currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); - - var newVersion = new NuGetVersion( - major: currentVersion.Major, - minor: currentVersion.Minor, - patch: currentVersion.Patch, - revision: currentVersion.Revision, - releaseLabels: updatedLabels, - metadata: currentVersion.Metadata - ); - - updater(newVersion.ToFullString()); - - LogUpdate(packageReference.GetAttribute("Include"), currentVersion, newVersion, doc.BaseURI); - - modified = true; - } - } - - return modified; + { + (string version, Action updater) GetVersion() + { + if (packageReference.HasAttribute("Version", namespaceURI)) + { + return ( + packageReference.Attributes["Version"].Value, + v => packageReference.SetAttribute("Version", namespaceURI, v) + ); + } + else if (packageReference.SelectSingleNode("d:Version", nsmgr) is XmlNode node) + { + return ( + node.InnerText, + v => node.InnerText = v + ); + } + else + { + return (null, null); + } + } + + var (version, updater) = GetVersion(); + + var currentVersion = new NuGetVersion(version); + + var hasUpdateableLabel = GetHasUpdateableLabel(currentVersion); + + if (hasUpdateableLabel ?? false) + { + var newVersion = UpdateVersion(currentVersion); + + updater(newVersion.ToFullString()); + + LogUpdate(packageReference.GetAttribute("Include"), currentVersion, newVersion, doc.BaseURI); + + modified = true; + } + } + + return modified; } - private void UpdateNuSpecs(string packageName, string[] nuspecFiles) + private bool? GetHasUpdateableLabel(NuGetVersion currentVersion) + { + if (currentVersion.ReleaseLabels?.Any() ?? false) + { + return currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); + } + else + { + return string.IsNullOrEmpty(_sourceBranch); + } + } + + private NuGetVersion UpdateVersion(NuGetVersion currentVersion) + { + var updatedLabels = string.IsNullOrEmpty(_sourceBranch) + ? new[] { _targetBranch } + : currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); + + var newVersion = new NuGetVersion( + major: currentVersion.Major, + minor: currentVersion.Minor, + patch: currentVersion.Patch, + revision: currentVersion.Revision, + releaseLabels: updatedLabels, + metadata: currentVersion.Metadata + ); + + return newVersion; + } + + private void UpdateNuSpecs(string packageName, string[] nuspecFiles) { foreach (var nuspecFile in nuspecFiles) { @@ -165,16 +186,7 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) if (hasUpdateableLabel ?? false) { - var updatedLabels = currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); - - var newVersion = new NuGetVersion( - major: currentVersion.Major, - minor: currentVersion.Minor, - patch: currentVersion.Patch, - revision: currentVersion.Revision, - releaseLabels: updatedLabels, - metadata: currentVersion.Metadata - ); + var newVersion = UpdateVersion(currentVersion); node.SetAttribute("version", newVersion.ToFullString()); From ba85c0cce1841b47ab21d77dd2d2d28dedd7f520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 10 Nov 2017 14:29:24 -0500 Subject: [PATCH 028/201] Fixed support for update to stable. --- NuGetUpdaterExecution.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index be37199..49f8dec 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -244,7 +244,7 @@ private static NuGetVersion GetLatestVersion(IPackageSearchMetadata package, str var versions = package.GetVersionsAsync().Result.OrderByDescending(v => v.Version); return versions - .Where(v => IsSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) + .Where(v => IsMatchingSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) .OrderByDescending(v => v.Version) .FirstOrDefault() ?.Version; @@ -260,10 +260,17 @@ private static bool ContainsTag(string tag, VersionInfo version) return version?.Version?.ReleaseLabels?.Contains(tag) ?? false; } - private static bool IsSpecialVersion(string specialVersion, VersionInfo version) + private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo version) { - return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; - } + if(string.IsNullOrEmpty(specialVersion)) + { + return !version.Version?.ReleaseLabels?.Any() ?? true; + } + else + { + return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + } + } private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) { From c12ba4005c9d67e7e79e46b93bb74fd4f005596c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 10 Nov 2017 15:01:37 -0500 Subject: [PATCH 029/201] Source branch is not mandatory --- NuGetBranchSwitch.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NuGetBranchSwitch.cs b/NuGetBranchSwitch.cs index 5979118..5178c73 100644 --- a/NuGetBranchSwitch.cs +++ b/NuGetBranchSwitch.cs @@ -15,7 +15,6 @@ public class NuGetBranchSwitch : Task [Required] public string[] Packages { get; set; } - [Required] public string SourceBranch { get; set; } [Required] From 6bfd37bca1aeb48f81aeaa893a21f192bfacc6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 10 Nov 2017 16:05:26 -0500 Subject: [PATCH 030/201] Fixed missing nuspec update for stable switch --- NuGetBranchSwitchExecution.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index f013290..b0badc3 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -182,7 +182,7 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) { var currentVersion = new NuGetVersion(versionNodeValue); - var hasUpdateableLabel = currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); + var hasUpdateableLabel = GetHasUpdateableLabel(currentVersion); if (hasUpdateableLabel ?? false) { From 77938e465fff601822580e913bf2b2c2e8d86c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 31 Jan 2018 16:27:24 -0500 Subject: [PATCH 031/201] Add support for nventive packages auto-update --- NuGetUpdaterExecution.cs | 61 ++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 49f8dec..8e5a898 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -31,7 +32,41 @@ public static bool Execute(TaskLoggingHelper log, string solutionRoot, string sp return true; } - private static IPackageSearchMetadata[] GetPackages(string PAT) + private static (string, IPackageSearchMetadata[])[] GetPackages(string PAT) + { + var q = from package in GetVSTSPackages(PAT) + .Concat(GetNuGetOrgPackages()) + group package by package.Identity.Id into p + select ( + Name: p.Key, + Sources: p.ToArray() + ); + + return q.ToArray(); + } + + private static IEnumerable GetNuGetOrgPackages() + { + var settings = Settings.LoadDefaultSettings(null); + var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + + var source = new PackageSource("https://api.nuget.org/v3/index.json"); + var repository = repositoryProvider.CreateRepository(source); + + _log?.LogMessage($"Pulling NuGet packages from {source.SourceUri}"); +#if DEBUG + Console.WriteLine($"Pulling NuGet packages from {source.SourceUri}"); +#endif + + var searchResource = repository.GetResource(); + + return searchResource + .SearchAsync("author:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) + .Result + .ToArray(); + } + + private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); @@ -56,7 +91,7 @@ private static IPackageSearchMetadata[] GetPackages(string PAT) .ToArray(); } - private static void UpdatePackages(string solutionRoot, IPackageSearchMetadata[] packages, string specialVersion, string excludeTag) + private static void UpdatePackages(string solutionRoot, (string title, IPackageSearchMetadata[] sources)[] packages, string specialVersion, string excludeTag) { var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); @@ -66,6 +101,11 @@ private static void UpdatePackages(string solutionRoot, IPackageSearchMetadata[] foreach (var package in packages) { + if (package.title != "Uno.SourceGeneration") + { + continue; + } + var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); if (latestVersion == null) @@ -73,16 +113,16 @@ private static void UpdatePackages(string solutionRoot, IPackageSearchMetadata[] continue; } - _log?.LogMessage($"Latest {specialVersion} version for [{package.Title}] is [{latestVersion}]"); + _log?.LogMessage($"Latest {specialVersion} version for [{package.title}] is [{latestVersion}]"); #if DEBUG - Console.WriteLine($"Latest {specialVersion} version for [{package.Title}] is [{latestVersion}]"); + Console.WriteLine($"Latest {specialVersion} version for [{package.title}] is [{latestVersion}]"); #endif - UpdateNuSpecs(package.Title, latestVersion, originalNuSpecFiles); + UpdateNuSpecs(package.title, latestVersion, originalNuSpecFiles); - UpdateProjectJson(package.Title, latestVersion, originalJsonFiles); + UpdateProjectJson(package.title, latestVersion, originalJsonFiles); - UpdateProjects(package.Title, latestVersion, originalProjectFiles); + UpdateProjects(package.title, latestVersion, originalProjectFiles); } } @@ -239,9 +279,12 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers return modified; } - private static NuGetVersion GetLatestVersion(IPackageSearchMetadata package, string specialVersion, string excludeTag) + private static NuGetVersion GetLatestVersion((string, IPackageSearchMetadata[]) package, string specialVersion, string excludeTag) { - var versions = package.GetVersionsAsync().Result.OrderByDescending(v => v.Version); + var versions = package + .Item2 + .SelectMany(s => s.GetVersionsAsync().Result) + .OrderByDescending(v => v.Version); return versions .Where(v => IsMatchingSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) From 730b2b78e5387334608f144f02b568bcb2ccc2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 9 Feb 2018 16:01:53 -0500 Subject: [PATCH 032/201] Remove invalid filter --- NuGetUpdaterExecution.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 8e5a898..09d0028 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -101,11 +101,6 @@ private static void UpdatePackages(string solutionRoot, (string title, IPackageS foreach (var package in packages) { - if (package.title != "Uno.SourceGeneration") - { - continue; - } - var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); if (latestVersion == null) From 1a4e3389d5fafbf54b05d7992cb47fc4fcd5adb4 Mon Sep 17 00:00:00 2001 From: Denis Railan Date: Tue, 13 Mar 2018 08:31:39 -0400 Subject: [PATCH 033/201] Adding a parameter that allows to downgrade to a version ( from latest dev to latest stable for example ) --- NuGetUpdaterExecution.cs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 09d0028..c7a2f78 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -21,9 +21,12 @@ public class NuGetUpdaterExecution private static TaskLoggingHelper _log; - public static bool Execute(TaskLoggingHelper log, string solutionRoot, string specialVersion, string excludeTag = "", string PAT = "") + private static bool _allowDowngrade; + + public static bool Execute(TaskLoggingHelper log, string solutionRoot, string specialVersion, string excludeTag = "", string PAT = "", bool allowDowngrade = false) { _log = log; + _allowDowngrade = allowDowngrade; var packages = GetPackages(PAT); @@ -148,7 +151,7 @@ private static void UpdateNuSpecs(string packageName, NuGetVersion latestVersion { var currentVersion = new NuGetVersion(versionNodeValue); - if (currentVersion < latestVersion) + if (currentVersion < latestVersion || _allowDowngrade) { node.SetAttribute("version", latestVersion.ToString()); LogUpdate(packageName, currentVersion, latestVersion, nuspecFile); @@ -182,7 +185,7 @@ private static void UpdateProjectJson(string packageName, NuGetVersion latestVer { var currentVersion = new NuGetVersion(match.Groups[1].Value); - if (currentVersion < latestVersion) + if (currentVersion < latestVersion || _allowDowngrade) { var newContent = Regex.Replace( fileContent, @@ -238,7 +241,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - if (currentVersion < version) + if (currentVersion < version || _allowDowngrade) { packageReference.SetAttribute("Version", namespaceURI, version.ToString()); modified = true; @@ -257,7 +260,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(node.InnerText); - if (currentVersion < version) + if (currentVersion < version || _allowDowngrade) { node.InnerText = version.ToString(); modified = true; @@ -300,15 +303,15 @@ private static bool ContainsTag(string tag, VersionInfo version) private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo version) { - if(string.IsNullOrEmpty(specialVersion)) - { - return !version.Version?.ReleaseLabels?.Any() ?? true; - } - else - { - return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; - } - } + if (string.IsNullOrEmpty(specialVersion)) + { + return !version.Version?.ReleaseLabels?.Any() ?? true; + } + else + { + return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + } + } private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) { From 18548f0f052f6ed8e255eec70398b590768d4f41 Mon Sep 17 00:00:00 2001 From: Denis Railan Date: Thu, 15 Mar 2018 23:30:27 -0400 Subject: [PATCH 034/201] UI first pass --- NuGetBranchSwitch.cs | 2 +- NuGetBranchSwitchExecution.cs | 148 +++++++++++++++++----------------- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/NuGetBranchSwitch.cs b/NuGetBranchSwitch.cs index 5178c73..38a232c 100644 --- a/NuGetBranchSwitch.cs +++ b/NuGetBranchSwitch.cs @@ -7,7 +7,7 @@ namespace Nuget.Updater { - public class NuGetBranchSwitch : Task + public class NuGetBranchSwitch : Task { [Required] public string SolutionRoot { get; set; } diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index b0badc3..906c056 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -25,7 +25,7 @@ public NuGetBranchSwitchExecution(TaskLoggingHelper log, string solutionRoot, st _packages = packages; _sourceBranch = sourceBranch?.Trim() ?? ""; _targetBranch = targetBranch?.Trim() ?? ""; - _solutionRoot = solutionRoot; + _solutionRoot = solutionRoot; } public bool Execute() @@ -80,81 +80,81 @@ private bool UpdateProjectReferenceVersions(string packageName, bool modified, X .Where(e => Regex.Match(e.GetAttribute("Include"), $"^{packageName}$").Success); foreach (var packageReference in nodes) - { - (string version, Action updater) GetVersion() - { - if (packageReference.HasAttribute("Version", namespaceURI)) - { - return ( - packageReference.Attributes["Version"].Value, - v => packageReference.SetAttribute("Version", namespaceURI, v) - ); - } - else if (packageReference.SelectSingleNode("d:Version", nsmgr) is XmlNode node) - { - return ( - node.InnerText, - v => node.InnerText = v - ); - } - else - { - return (null, null); - } - } - - var (version, updater) = GetVersion(); - - var currentVersion = new NuGetVersion(version); - - var hasUpdateableLabel = GetHasUpdateableLabel(currentVersion); - - if (hasUpdateableLabel ?? false) - { - var newVersion = UpdateVersion(currentVersion); - - updater(newVersion.ToFullString()); - - LogUpdate(packageReference.GetAttribute("Include"), currentVersion, newVersion, doc.BaseURI); - - modified = true; - } - } - - return modified; + { + (string version, Action updater) GetVersion() + { + if (packageReference.HasAttribute("Version", namespaceURI)) + { + return ( + packageReference.Attributes["Version"].Value, + v => packageReference.SetAttribute("Version", namespaceURI, v) + ); + } + else if (packageReference.SelectSingleNode("d:Version", nsmgr) is XmlNode node) + { + return ( + node.InnerText, + v => node.InnerText = v + ); + } + else + { + return (null, null); + } + } + + var (version, updater) = GetVersion(); + + var currentVersion = new NuGetVersion(version); + + var hasUpdateableLabel = GetHasUpdateableLabel(currentVersion); + + if (hasUpdateableLabel ?? false) + { + var newVersion = UpdateVersion(currentVersion); + + updater(newVersion.ToFullString()); + + LogUpdate(packageReference.GetAttribute("Include"), currentVersion, newVersion, doc.BaseURI); + + modified = true; + } + } + + return modified; + } + + private bool? GetHasUpdateableLabel(NuGetVersion currentVersion) + { + if (currentVersion.ReleaseLabels?.Any() ?? false) + { + return currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); + } + else + { + return string.IsNullOrEmpty(_sourceBranch); + } + } + + private NuGetVersion UpdateVersion(NuGetVersion currentVersion) + { + var updatedLabels = string.IsNullOrEmpty(_sourceBranch) + ? new[] { _targetBranch } + : currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); + + var newVersion = new NuGetVersion( + major: currentVersion.Major, + minor: currentVersion.Minor, + patch: currentVersion.Patch, + revision: currentVersion.Revision, + releaseLabels: updatedLabels, + metadata: currentVersion.Metadata + ); + + return newVersion; } - private bool? GetHasUpdateableLabel(NuGetVersion currentVersion) - { - if (currentVersion.ReleaseLabels?.Any() ?? false) - { - return currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); - } - else - { - return string.IsNullOrEmpty(_sourceBranch); - } - } - - private NuGetVersion UpdateVersion(NuGetVersion currentVersion) - { - var updatedLabels = string.IsNullOrEmpty(_sourceBranch) - ? new[] { _targetBranch } - : currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); - - var newVersion = new NuGetVersion( - major: currentVersion.Major, - minor: currentVersion.Minor, - patch: currentVersion.Patch, - revision: currentVersion.Revision, - releaseLabels: updatedLabels, - metadata: currentVersion.Metadata - ); - - return newVersion; - } - - private void UpdateNuSpecs(string packageName, string[] nuspecFiles) + private void UpdateNuSpecs(string packageName, string[] nuspecFiles) { foreach (var nuspecFile in nuspecFiles) { From 552bcd709b88ae6a39e09571e5fdf1af0103d237 Mon Sep 17 00:00:00 2001 From: Denis Railan Date: Sat, 17 Mar 2018 20:42:18 -0400 Subject: [PATCH 035/201] Correcting logs and adding ability to force latest dev on specific packages --- NuGetUpdaterExecution.cs | 61 ++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index c7a2f78..e097dc8 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -23,14 +24,21 @@ public class NuGetUpdaterExecution private static bool _allowDowngrade; - public static bool Execute(TaskLoggingHelper log, string solutionRoot, string specialVersion, string excludeTag = "", string PAT = "", bool allowDowngrade = false) + public static bool Execute( + TaskLoggingHelper log, + string solutionRoot, + string targetVersion, + string excludeTag = "", + string PAT = "", + bool allowDowngrade = false, + IEnumerable keepLatestDev = null) { _log = log; _allowDowngrade = allowDowngrade; var packages = GetPackages(PAT); - UpdatePackages(solutionRoot, packages, specialVersion, excludeTag); + UpdatePackages(solutionRoot, packages, targetVersion, excludeTag, keepLatestDev); return true; } @@ -94,7 +102,12 @@ private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) .ToArray(); } - private static void UpdatePackages(string solutionRoot, (string title, IPackageSearchMetadata[] sources)[] packages, string specialVersion, string excludeTag) + private static void UpdatePackages( + string solutionRoot, + (string title, IPackageSearchMetadata[] sources)[] packages, + string targetVersion, + string excludeTag, + IEnumerable keepLatestDev = null) { var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); @@ -104,16 +117,16 @@ private static void UpdatePackages(string solutionRoot, (string title, IPackageS foreach (var package in packages) { - var latestVersion = GetLatestVersion(package, specialVersion, excludeTag); + var latestVersion = GetLatestVersion(package, targetVersion, excludeTag, keepLatestDev); if (latestVersion == null) { continue; } - _log?.LogMessage($"Latest {specialVersion} version for [{package.title}] is [{latestVersion}]"); + _log?.LogMessage($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); #if DEBUG - Console.WriteLine($"Latest {specialVersion} version for [{package.title}] is [{latestVersion}]"); + Console.WriteLine($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); #endif UpdateNuSpecs(package.title, latestVersion, originalNuSpecFiles); @@ -151,7 +164,11 @@ private static void UpdateNuSpecs(string packageName, NuGetVersion latestVersion { var currentVersion = new NuGetVersion(versionNodeValue); - if (currentVersion < latestVersion || _allowDowngrade) + if (currentVersion == latestVersion) + { + LogNoUpdate(packageName, currentVersion, nuspecFile, isLatest: true); + } + else if (currentVersion < latestVersion || _allowDowngrade) { node.SetAttribute("version", latestVersion.ToString()); LogUpdate(packageName, currentVersion, latestVersion, nuspecFile); @@ -185,7 +202,11 @@ private static void UpdateProjectJson(string packageName, NuGetVersion latestVer { var currentVersion = new NuGetVersion(match.Groups[1].Value); - if (currentVersion < latestVersion || _allowDowngrade) + if (currentVersion == latestVersion) + { + LogNoUpdate(packageName, currentVersion, file, isLatest: true); + } + else if (currentVersion < latestVersion || _allowDowngrade) { var newContent = Regex.Replace( fileContent, @@ -241,7 +262,11 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - if (currentVersion < version || _allowDowngrade) + if (currentVersion == version) + { + LogNoUpdate(packageName, currentVersion, doc.BaseURI, isLatest: true); + } + else if (currentVersion < version || _allowDowngrade) { packageReference.SetAttribute("Version", namespaceURI, version.ToString()); modified = true; @@ -260,7 +285,11 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(node.InnerText); - if (currentVersion < version || _allowDowngrade) + if (currentVersion == version) + { + LogNoUpdate(packageName, currentVersion, doc.BaseURI, isLatest: true); + } + else if (currentVersion < version || _allowDowngrade) { node.InnerText = version.ToString(); modified = true; @@ -277,13 +306,15 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers return modified; } - private static NuGetVersion GetLatestVersion((string, IPackageSearchMetadata[]) package, string specialVersion, string excludeTag) + private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetadata[] sources) package, string targetVersion, string excludeTag, IEnumerable keepLatestDev = null) { var versions = package - .Item2 + .sources .SelectMany(s => s.GetVersionsAsync().Result) .OrderByDescending(v => v.Version); + var specialVersion = keepLatestDev.Contains(package.title, StringComparer.OrdinalIgnoreCase) ? "dev" : targetVersion; + return versions .Where(v => IsMatchingSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) .OrderByDescending(v => v.Version) @@ -321,11 +352,11 @@ private static void LogUpdate(string packageName, NuGetVersion currentVersion, N #endif } - private static void LogNoUpdate(string packageName, NuGetVersion currentVersion, string file) + private static void LogNoUpdate(string packageName, NuGetVersion currentVersion, string file, bool isLatest = false) { - _log?.LogMessage($"Found higher version of [{packageName}] ([{currentVersion}]) in [{file}]"); + _log?.LogMessage($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); #if DEBUG - Console.WriteLine($"Found higher version of [{packageName}] ([{currentVersion}]) in [{file}]"); + Console.WriteLine($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); #endif } } From 504beff34a3119ca390c9a758039bf677a0e7c75 Mon Sep 17 00:00:00 2001 From: Denis Railan Date: Sat, 17 Mar 2018 20:44:56 -0400 Subject: [PATCH 036/201] Adding ability to set packages that should be always on latest dev. PAT is now a password box. Avoiding a null ref --- NuGetUpdaterExecution.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index e097dc8..12d53b0 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -313,7 +313,7 @@ private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetada .SelectMany(s => s.GetVersionsAsync().Result) .OrderByDescending(v => v.Version); - var specialVersion = keepLatestDev.Contains(package.title, StringComparer.OrdinalIgnoreCase) ? "dev" : targetVersion; + var specialVersion = (keepLatestDev?.Contains(package.title, StringComparer.OrdinalIgnoreCase) ?? false) ? "dev" : targetVersion; return versions .Where(v => IsMatchingSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) From 4e7bdb319975fb1a9e365c53ae94ae310769491f Mon Sep 17 00:00:00 2001 From: Jeremie Thibeault Date: Wed, 25 Apr 2018 15:52:06 -0400 Subject: [PATCH 037/201] Allows packages ignore to avoid updating some packages --- NuGetUpdaterExecution.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 12d53b0..2c2b168 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -31,14 +31,15 @@ public static bool Execute( string excludeTag = "", string PAT = "", bool allowDowngrade = false, - IEnumerable keepLatestDev = null) + IEnumerable keepLatestDev = null, + IEnumerable ignorePackages = null) { _log = log; _allowDowngrade = allowDowngrade; var packages = GetPackages(PAT); - UpdatePackages(solutionRoot, packages, targetVersion, excludeTag, keepLatestDev); + UpdatePackages(solutionRoot, packages, targetVersion, excludeTag, keepLatestDev, ignorePackages); return true; } @@ -107,7 +108,8 @@ private static void UpdatePackages( (string title, IPackageSearchMetadata[] sources)[] packages, string targetVersion, string excludeTag, - IEnumerable keepLatestDev = null) + IEnumerable keepLatestDev = null, + IEnumerable ignoredPackages = null) { var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); @@ -117,6 +119,11 @@ private static void UpdatePackages( foreach (var package in packages) { + if (ignoredPackages != null && ignoredPackages.Contains(package.title)) + { + continue; + } + var latestVersion = GetLatestVersion(package, targetVersion, excludeTag, keepLatestDev); if (latestVersion == null) From 71797398286fef3ed99d17e897515a931b2049c6 Mon Sep 17 00:00:00 2001 From: Denis Railan Date: Tue, 1 May 2018 09:05:06 -0400 Subject: [PATCH 038/201] Adding 'strict' option --- NuGetUpdaterExecution.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index 2c2b168..dab3804 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -31,6 +30,7 @@ public static bool Execute( string excludeTag = "", string PAT = "", bool allowDowngrade = false, + bool strict = true, IEnumerable keepLatestDev = null, IEnumerable ignorePackages = null) { @@ -39,7 +39,7 @@ public static bool Execute( var packages = GetPackages(PAT); - UpdatePackages(solutionRoot, packages, targetVersion, excludeTag, keepLatestDev, ignorePackages); + UpdatePackages(solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages); return true; } @@ -108,6 +108,7 @@ private static void UpdatePackages( (string title, IPackageSearchMetadata[] sources)[] packages, string targetVersion, string excludeTag, + bool strict, IEnumerable keepLatestDev = null, IEnumerable ignoredPackages = null) { @@ -124,7 +125,7 @@ private static void UpdatePackages( continue; } - var latestVersion = GetLatestVersion(package, targetVersion, excludeTag, keepLatestDev); + var latestVersion = GetLatestVersion(package, targetVersion, excludeTag, strict, keepLatestDev); if (latestVersion == null) { @@ -313,7 +314,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers return modified; } - private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetadata[] sources) package, string targetVersion, string excludeTag, IEnumerable keepLatestDev = null) + private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetadata[] sources) package, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null) { var versions = package .sources @@ -323,7 +324,7 @@ private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetada var specialVersion = (keepLatestDev?.Contains(package.title, StringComparer.OrdinalIgnoreCase) ?? false) ? "dev" : targetVersion; return versions - .Where(v => IsMatchingSpecialVersion(specialVersion, v) && !ContainsTag(excludeTag, v)) + .Where(v => IsMatchingSpecialVersion(specialVersion, v, strict) && !ContainsTag(excludeTag, v)) .OrderByDescending(v => v.Version) .FirstOrDefault() ?.Version; @@ -339,7 +340,7 @@ private static bool ContainsTag(string tag, VersionInfo version) return version?.Version?.ReleaseLabels?.Contains(tag) ?? false; } - private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo version) + private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo version, bool strict) { if (string.IsNullOrEmpty(specialVersion)) { @@ -347,7 +348,12 @@ private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo } else { - return version.Version?.ReleaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + var releaseLabels = version.Version?.ReleaseLabels; + var isMathingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + + return strict + ? releaseLabels?.Count() == 2 && isMathingSpecialVersion // Check strictly for packages with versions "dev.XXXX" + : isMathingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" } } From 6025d73c79c3f4a7c404d9fae94ec7739d395a80 Mon Sep 17 00:00:00 2001 From: Denis Railan Date: Tue, 1 May 2018 09:06:32 -0400 Subject: [PATCH 039/201] Typo --- NuGetUpdaterExecution.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index dab3804..a57b7ef 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -349,11 +349,11 @@ private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo else { var releaseLabels = version.Version?.ReleaseLabels; - var isMathingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; return strict - ? releaseLabels?.Count() == 2 && isMathingSpecialVersion // Check strictly for packages with versions "dev.XXXX" - : isMathingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" + ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" + : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" } } From 8b93702198b4f1a6ff3f77631a58cd924c906eaf Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 7 Jun 2018 14:55:08 +0000 Subject: [PATCH 040/201] Ensured we get all nventive owned packages --- NuGetUpdaterExecution.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdaterExecution.cs index a57b7ef..a6beca4 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdaterExecution.cs @@ -73,7 +73,7 @@ private static IEnumerable GetNuGetOrgPackages() var searchResource = repository.GetResource(); return searchResource - .SearchAsync("author:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) + .SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) .Result .ToArray(); } From 6f6106e7760d07ebf10428329d4a50dab3f4c07a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 23 Apr 2018 14:41:58 -0400 Subject: [PATCH 041/201] Updated nuget updater to target .Net Standard 2.0 --- NuGetBranchSwitchExecution.cs | 10 +++++----- Nuget.Updater.csproj | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index 906c056..a94c1fa 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -12,7 +12,6 @@ public class NuGetBranchSwitchExecution { private static TaskLoggingHelper _log; - private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; private readonly string[] _packages; @@ -22,6 +21,7 @@ public class NuGetBranchSwitchExecution public NuGetBranchSwitchExecution(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) { + _log = log; _packages = packages; _sourceBranch = sourceBranch?.Trim() ?? ""; _targetBranch = targetBranch?.Trim() ?? ""; @@ -56,7 +56,7 @@ private void UpdateProjects(string packageName, string[] projectFiles) { PreserveWhitespace = true }; - doc.Load(projectFiles[i]); + doc.Load(File.OpenRead(projectFiles[i])); var nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); @@ -68,7 +68,7 @@ private void UpdateProjects(string packageName, string[] projectFiles) if (modified) { - doc.Save(projectFiles[i]); + doc.Save(File.OpenWrite(projectFiles[i])); } } } @@ -162,7 +162,7 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) { PreserveWhitespace = true }; - doc.Load(nuspecFile); + doc.Load(File.OpenRead(nuspecFile)); var mgr = new XmlNamespaceManager(doc.NameTable); @@ -197,7 +197,7 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) if (nodes.Any()) { - doc.Save(nuspecFile); + doc.Save(File.OpenWrite(nuspecFile)); } } } diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 7677774..2d4b607 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,6 +1,6 @@  - net46 + netstandard20 True nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time @@ -10,10 +10,8 @@ all - - - 4.4.0 - + + From d522d5069f8c328512f26f78f57f5fa8048c8d78 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 27 Jun 2018 12:00:28 -0400 Subject: [PATCH 042/201] Code clean-up --- NuGetBranchSwitchExecution.cs | 4 +-- ...ranchSwitch.cs => NuGetBranchSwitchTask.cs | 4 +-- NuGetUpdaterExecution.cs => NuGetUpdater.cs | 33 +++++++++++++------ NuGetUpdaterTask.cs | 4 +-- Nuget.Updater.csproj | 13 +++----- build/Nuget.Updater.targets | 6 ++-- 6 files changed, 36 insertions(+), 28 deletions(-) rename NuGetBranchSwitch.cs => NuGetBranchSwitchTask.cs (69%) rename NuGetUpdaterExecution.cs => NuGetUpdater.cs (92%) diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index a94c1fa..62d2181 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -8,7 +8,7 @@ namespace Nuget.Updater { - public class NuGetBranchSwitchExecution + public class NuGetBranchSwitch { private static TaskLoggingHelper _log; @@ -19,7 +19,7 @@ public class NuGetBranchSwitchExecution private readonly string _targetBranch; private readonly string _solutionRoot; - public NuGetBranchSwitchExecution(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) + public NuGetBranchSwitch(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) { _log = log; _packages = packages; diff --git a/NuGetBranchSwitch.cs b/NuGetBranchSwitchTask.cs similarity index 69% rename from NuGetBranchSwitch.cs rename to NuGetBranchSwitchTask.cs index 38a232c..3fdab4f 100644 --- a/NuGetBranchSwitch.cs +++ b/NuGetBranchSwitchTask.cs @@ -7,7 +7,7 @@ namespace Nuget.Updater { - public class NuGetBranchSwitch : Task + public class NuGetBranchSwitchTask : Task { [Required] public string SolutionRoot { get; set; } @@ -20,6 +20,6 @@ public class NuGetBranchSwitch : Task [Required] public string TargetBranch { get; set; } - public override bool Execute() => new NuGetBranchSwitchExecution(Log, SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); + public override bool Execute() => new NuGetBranchSwitch(Log, SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); } } diff --git a/NuGetUpdaterExecution.cs b/NuGetUpdater.cs similarity index 92% rename from NuGetUpdaterExecution.cs rename to NuGetUpdater.cs index a6beca4..1e9cb45 100644 --- a/NuGetUpdaterExecution.cs +++ b/NuGetUpdater.cs @@ -15,16 +15,16 @@ namespace Nuget.Updater { - public class NuGetUpdaterExecution + public class NuGetUpdater { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - private static TaskLoggingHelper _log; + private static Action _logAction; private static bool _allowDowngrade; - public static bool Execute( - TaskLoggingHelper log, + public static bool Update( + Action logAction, string solutionRoot, string targetVersion, string excludeTag = "", @@ -34,7 +34,20 @@ public static bool Execute( IEnumerable keepLatestDev = null, IEnumerable ignorePackages = null) { - _log = log; + _logAction = logAction; + + return Update(solutionRoot, targetVersion, excludeTag, PAT, allowDowngrade, keepLatestDev); + } + + public static bool Update( + string solutionRoot, + string targetVersion, + string excludeTag = "", + string PAT = "", + bool allowDowngrade = false, + IEnumerable keepLatestDev = null + ) + { _allowDowngrade = allowDowngrade; var packages = GetPackages(PAT); @@ -65,7 +78,7 @@ private static IEnumerable GetNuGetOrgPackages() var source = new PackageSource("https://api.nuget.org/v3/index.json"); var repository = repositoryProvider.CreateRepository(source); - _log?.LogMessage($"Pulling NuGet packages from {source.SourceUri}"); + _logAction($"Pulling NuGet packages from {source.SourceUri}"); #if DEBUG Console.WriteLine($"Pulling NuGet packages from {source.SourceUri}"); #endif @@ -92,7 +105,7 @@ private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) var searchResource = repository.GetResource(); - _log?.LogMessage($"Pulling NuGet packages from {source.SourceUri}"); + _logAction($"Pulling NuGet packages from {source.SourceUri}"); #if DEBUG Console.WriteLine($"Pulling NuGet packages from {source.SourceUri}"); #endif @@ -132,7 +145,7 @@ private static void UpdatePackages( continue; } - _log?.LogMessage($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); + _logAction($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); #if DEBUG Console.WriteLine($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); #endif @@ -359,7 +372,7 @@ private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) { - _log?.LogMessage($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); + _logAction($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); #if DEBUG Console.WriteLine($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); #endif @@ -367,7 +380,7 @@ private static void LogUpdate(string packageName, NuGetVersion currentVersion, N private static void LogNoUpdate(string packageName, NuGetVersion currentVersion, string file, bool isLatest = false) { - _log?.LogMessage($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); + _logAction($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); #if DEBUG Console.WriteLine($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); #endif diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs index 96bca8f..80dcf7e 100644 --- a/NuGetUpdaterTask.cs +++ b/NuGetUpdaterTask.cs @@ -3,7 +3,7 @@ namespace Nuget.Updater { - public class NuGetUpdater : Task + public class NuGetUpdaterTask : Task { [Required] public string SpecialVersion { get; set; } @@ -18,7 +18,7 @@ public class NuGetUpdater : Task public override bool Execute() { - return NuGetUpdaterExecution.Execute(Log, SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT); + return NuGetUpdater.Update(message => Log.LogMessage(message), SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT); } } } diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 2d4b607..bdc6907 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -4,9 +4,12 @@ True nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time - False + + all + runtime; build; native; contentfiles; analyzers + all @@ -18,12 +21,4 @@ build - - - - true - lib - - - \ No newline at end of file diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets index c3a60bd..f8a9b3f 100644 --- a/build/Nuget.Updater.targets +++ b/build/Nuget.Updater.targets @@ -2,11 +2,11 @@ - ..\lib\Nuget.Updater.dll + ..\lib\netstandard2.0\Nuget.Updater.dll true - - + + \ No newline at end of file From 563daea8fd492386acb80df48a774e1910e9d7cc Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 27 Jun 2018 12:30:27 -0400 Subject: [PATCH 043/201] Fixed rebase issue --- NuGetUpdater.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NuGetUpdater.cs b/NuGetUpdater.cs index 1e9cb45..522e684 100644 --- a/NuGetUpdater.cs +++ b/NuGetUpdater.cs @@ -36,7 +36,7 @@ public static bool Update( { _logAction = logAction; - return Update(solutionRoot, targetVersion, excludeTag, PAT, allowDowngrade, keepLatestDev); + return Update(solutionRoot, targetVersion, excludeTag, PAT, strict, allowDowngrade, keepLatestDev, ignorePackages); } public static bool Update( @@ -45,7 +45,9 @@ public static bool Update( string excludeTag = "", string PAT = "", bool allowDowngrade = false, - IEnumerable keepLatestDev = null + bool strict = true, + IEnumerable keepLatestDev = null, + IEnumerable ignorePackages = null ) { _allowDowngrade = allowDowngrade; From 3c42ee98892e3850b87e868c54fb170fe4c6a23d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 16 Jul 2018 10:09:53 -0400 Subject: [PATCH 044/201] Fixed null reference exception when using the UI --- NuGetUpdater.cs | 20 +++----------------- NuGetUpdaterTask.cs | 2 +- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/NuGetUpdater.cs b/NuGetUpdater.cs index 522e684..1a62b67 100644 --- a/NuGetUpdater.cs +++ b/NuGetUpdater.cs @@ -24,7 +24,6 @@ public class NuGetUpdater private static bool _allowDowngrade; public static bool Update( - Action logAction, string solutionRoot, string targetVersion, string excludeTag = "", @@ -32,24 +31,11 @@ public static bool Update( bool allowDowngrade = false, bool strict = true, IEnumerable keepLatestDev = null, - IEnumerable ignorePackages = null) - { - _logAction = logAction; - - return Update(solutionRoot, targetVersion, excludeTag, PAT, strict, allowDowngrade, keepLatestDev, ignorePackages); - } - - public static bool Update( - string solutionRoot, - string targetVersion, - string excludeTag = "", - string PAT = "", - bool allowDowngrade = false, - bool strict = true, - IEnumerable keepLatestDev = null, - IEnumerable ignorePackages = null + IEnumerable ignorePackages = null, + Action logAction = null ) { + _logAction = logAction ?? new Action(_ => { }); _allowDowngrade = allowDowngrade; var packages = GetPackages(PAT); diff --git a/NuGetUpdaterTask.cs b/NuGetUpdaterTask.cs index 80dcf7e..16de325 100644 --- a/NuGetUpdaterTask.cs +++ b/NuGetUpdaterTask.cs @@ -18,7 +18,7 @@ public class NuGetUpdaterTask : Task public override bool Execute() { - return NuGetUpdater.Update(message => Log.LogMessage(message), SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT); + return NuGetUpdater.Update(SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT, logAction: message => Log.LogMessage(message)); } } } From 7c4f590d6fcbd87db25e5722e013ed13fe2f4f30 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 19 Jul 2018 12:27:56 -0400 Subject: [PATCH 045/201] Added uap version of nuget updater --- NuGetUpdater/NuGetUpdater.Net.cs | 58 ++++++ NuGetUpdater/NuGetUpdater.UAP.cs | 65 +++++++ .../NuGetUpdater.cs | 172 ++++++++++-------- Nuget.Updater.csproj | 45 ++++- UpdateTarget.cs | 17 ++ 5 files changed, 271 insertions(+), 86 deletions(-) create mode 100644 NuGetUpdater/NuGetUpdater.Net.cs create mode 100644 NuGetUpdater/NuGetUpdater.UAP.cs rename NuGetUpdater.cs => NuGetUpdater/NuGetUpdater.cs (69%) create mode 100644 UpdateTarget.cs diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs new file mode 100644 index 0000000..ebdbfa1 --- /dev/null +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -0,0 +1,58 @@ +#if !UAP +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace Nuget.Updater +{ + partial class NuGetUpdater + { + + private static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) + { + var filter = extensionFilter != null + ? "*" + extensionFilter + : null; + + if (nameFilter != null && filter == null) + { + filter = nameFilter; + } + + return Directory.GetFiles(path, filter, SearchOption.AllDirectories); + } + + private static async Task GetDocument(CancellationToken ct, string path) + { + var document = new XmlDocument() + { + PreserveWhitespace = true + }; + + document.Load(path); + + return document; + } + + private static async Task SaveDocument(CancellationToken ct, XmlDocument document, string path) + { + document.Save(path); + } + + private static async Task ReadFileContent(CancellationToken ct, string path) + { + return File.ReadAllText(path); + } + + private static async Task SetFileContent(CancellationToken ct, string path, string content) + { + File.WriteAllText(path, content, Encoding.UTF8); + } + } +} +#endif diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs new file mode 100644 index 0000000..0b540bf --- /dev/null +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -0,0 +1,65 @@ +#if UAP +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Windows.Data.Xml.Dom; +using Windows.Storage; + +namespace Nuget.Updater +{ + partial class NuGetUpdater + { + private static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) + { + var folder = await StorageFolder.GetFolderFromPathAsync(path); + + var subFoldersContent = new List(); + var folders = await folder.GetFoldersAsync(); + + if (folders.Any()) + { + foreach (var f in folders) + { + subFoldersContent.AddRange(await GetFiles(ct, f.Path, extensionFilter, nameFilter)); + } + } + + var files = await folder.GetFilesAsync(); + + return files + .Where(f => (extensionFilter == null && nameFilter == null) || (extensionFilter != null && f.FileType == extensionFilter) || (nameFilter != null && f.Name == nameFilter)) + .Select(f => f.Path) + .Concat(subFoldersContent) + .ToArray(); + } + + private static async Task GetDocument(CancellationToken ct, string path) + { + return await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path)); + } + + private static async Task SaveDocument(CancellationToken ct, XmlDocument document, string path) + { + await document.SaveToFileAsync(await StorageFile.GetFileFromPathAsync(path)); + } + + private static async Task ReadFileContent(CancellationToken ct, string path) + { + var file = await StorageFile.GetFileFromPathAsync(path); + var lines = await FileIO.ReadLinesAsync(file); + var content = string.Join(Environment.NewLine, lines); + + return content; + } + + private static async Task SetFileContent(CancellationToken ct, string path, string content) + { + var file = await StorageFile.GetFileFromPathAsync(path); + await FileIO.WriteTextAsync(file, content); + } + } +} +#endif \ No newline at end of file diff --git a/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs similarity index 69% rename from NuGetUpdater.cs rename to NuGetUpdater/NuGetUpdater.cs index 1a62b67..84c67e4 100644 --- a/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -5,17 +5,25 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; -using System.Xml; -using Microsoft.Build.Utilities; +using System.Threading.Tasks; using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.Versioning; +#if UAP +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = System.Xml.XmlElement; +#else +using XmlDocument = System.Xml.XmlDocument; +using XmlElement = System.Xml.XmlElement; +using XmlNamespaceManager = System.Xml.XmlNamespaceManager; +#endif + namespace Nuget.Updater { - public class NuGetUpdater + public partial class NuGetUpdater { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; @@ -35,7 +43,11 @@ public static bool Update( Action logAction = null ) { +#if DEBUG + _logAction = Console.WriteLine; +#else _logAction = logAction ?? new Action(_ => { }); +#endif _allowDowngrade = allowDowngrade; var packages = GetPackages(PAT); @@ -67,9 +79,6 @@ private static IEnumerable GetNuGetOrgPackages() var repository = repositoryProvider.CreateRepository(source); _logAction($"Pulling NuGet packages from {source.SourceUri}"); -#if DEBUG - Console.WriteLine($"Pulling NuGet packages from {source.SourceUri}"); -#endif var searchResource = repository.GetResource(); @@ -92,11 +101,7 @@ private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) var searchResource = repository.GetResource(); - _logAction($"Pulling NuGet packages from {source.SourceUri}"); -#if DEBUG - Console.WriteLine($"Pulling NuGet packages from {source.SourceUri}"); -#endif return searchResource .SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) @@ -104,7 +109,7 @@ private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) .ToArray(); } - private static void UpdatePackages( + private static async void UpdatePackages( string solutionRoot, (string title, IPackageSearchMetadata[] sources)[] packages, string targetVersion, @@ -113,11 +118,13 @@ private static void UpdatePackages( IEnumerable keepLatestDev = null, IEnumerable ignoredPackages = null) { - var originalNuSpecFiles = Directory.GetFiles(solutionRoot, "*.nuspec", SearchOption.AllDirectories); + var ct = CancellationToken.None; - var originalJsonFiles = Directory.GetFiles(solutionRoot, "project.json", SearchOption.AllDirectories); + var originalNuSpecFiles = await GetFiles(ct, solutionRoot, extensionFilter: ".nuspec"); - var originalProjectFiles = Directory.GetFiles(solutionRoot, "*.csproj", SearchOption.AllDirectories); + var originalJsonFiles = await GetFiles(ct, solutionRoot, nameFilter: "project.json"); + + var originalProjectFiles = await GetFiles(ct, solutionRoot, extensionFilter: ".csproj"); foreach (var package in packages) { @@ -134,27 +141,26 @@ private static void UpdatePackages( } _logAction($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); -#if DEBUG - Console.WriteLine($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); -#endif - UpdateNuSpecs(package.title, latestVersion, originalNuSpecFiles); + await UpdateNuSpecs(ct, package.title, latestVersion, originalNuSpecFiles); - UpdateProjectJson(package.title, latestVersion, originalJsonFiles); + await UpdateProjectJson(ct, package.title, latestVersion, originalJsonFiles); - UpdateProjects(package.title, latestVersion, originalProjectFiles); + await UpdateProjects(ct, package.title, latestVersion, originalProjectFiles); } } - private static void UpdateNuSpecs(string packageName, NuGetVersion latestVersion, string[] nuspecFiles) + private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] nuspecFiles) { foreach (var nuspecFile in nuspecFiles) { - var doc = new XmlDocument() - { - PreserveWhitespace = true - }; - doc.Load(nuspecFile); + var doc = await GetDocument(ct, nuspecFile); + +#if UAP + var nodes = doc + .SelectNodes($"//x:dependency[@id='{packageName}']") + .OfType(); +#else var mgr = new XmlNamespaceManager(doc.NameTable); @@ -163,6 +169,7 @@ private static void UpdateNuSpecs(string packageName, NuGetVersion latestVersion var nodes = doc .SelectNodes($"//x:dependency[@id='{packageName}']", mgr) .OfType(); +#endif foreach (var node in nodes) { @@ -191,12 +198,12 @@ private static void UpdateNuSpecs(string packageName, NuGetVersion latestVersion if (nodes.Any()) { - doc.Save(nuspecFile); + await SaveDocument(ct, doc, nuspecFile); } } } - private static void UpdateProjectJson(string packageName, NuGetVersion latestVersion, string[] jsonFiles) + private static async Task UpdateProjectJson(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] jsonFiles) { var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; var replaced = $@"""{packageName}"": ""{latestVersion}"""; @@ -204,7 +211,7 @@ private static void UpdateProjectJson(string packageName, NuGetVersion latestVer for (int i = 0; i < jsonFiles.Length; i++) { var file = jsonFiles[i]; - var fileContent = File.ReadAllText(file); + var fileContent = await ReadFileContent(ct, file); var match = Regex.Match(fileContent, originalMatch, RegexOptions.IgnoreCase); if (match?.Success ?? false) @@ -224,7 +231,7 @@ private static void UpdateProjectJson(string packageName, NuGetVersion latestVer RegexOptions.IgnoreCase ); - File.WriteAllText(file, newContent, Encoding.UTF8); + await SetFileContent(ct, file, newContent); LogUpdate(packageName, currentVersion, latestVersion, file); } @@ -236,83 +243,100 @@ private static void UpdateProjectJson(string packageName, NuGetVersion latestVer } } - private static void UpdateProjects(string packageName, NuGetVersion latestVersion, string[] projectFiles) + private static async Task UpdateProjects(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] projectFiles) { for (int i = 0; i < projectFiles.Length; i++) { var modified = false; + var path = projectFiles[i]; + var doc = await GetDocument(ct, path); - var doc = new XmlDocument() - { - PreserveWhitespace = true - }; - doc.Load(projectFiles[i]); - +#if UAP + modified = UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, path); +#else var nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, nsmgr); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, path, nsmgr); var nsmgr2 = new XmlNamespaceManager(doc.NameTable); nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, nsmgr2); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, path, nsmgr2); +#endif if (modified) { - doc.Save(projectFiles[i]); + await SaveDocument(ct, doc, projectFiles[i]); } } } - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr, string namespaceURI = "") +#if UAP + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath) +#else + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, XmlNamespaceManager namespaceManager) +#endif { - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", nsmgr)) + try { - if (packageReference.HasAttribute("Version", namespaceURI)) - { - var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - - if (currentVersion == version) - { - LogNoUpdate(packageName, currentVersion, doc.BaseURI, isLatest: true); - } - else if (currentVersion < version || _allowDowngrade) - { - packageReference.SetAttribute("Version", namespaceURI, version.ToString()); - modified = true; - LogUpdate(packageName, currentVersion, version, doc.BaseURI); - } - else - { - LogNoUpdate(packageName, currentVersion, doc.BaseURI); - } - } - else + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']")) { - var node = packageReference.SelectSingleNode("d:Version", nsmgr); - - if (node != null) + if (packageReference.HasAttribute("Version")) { - var currentVersion = new NuGetVersion(node.InnerText); + var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); if (currentVersion == version) { - LogNoUpdate(packageName, currentVersion, doc.BaseURI, isLatest: true); + LogNoUpdate(packageName, currentVersion, documentPath, isLatest: true); } else if (currentVersion < version || _allowDowngrade) { - node.InnerText = version.ToString(); + packageReference.SetAttribute("Version", version.ToString()); modified = true; - LogUpdate(packageName, currentVersion, version, doc.BaseURI); + LogUpdate(packageName, currentVersion, version, documentPath); } else { - LogNoUpdate(packageName, currentVersion, doc.BaseURI); + LogNoUpdate(packageName, currentVersion, documentPath); + } + } + else + { +#if UAP + var node = packageReference.SelectSingleNode("d:Version"); +#else + var node = packageReference.SelectSingleNode("d:Version", namespaceManager); +#endif + + if (node != null) + { + var currentVersion = new NuGetVersion(node.InnerText); + + if (currentVersion == version) + { + LogNoUpdate(packageName, currentVersion, documentPath, isLatest: true); + } + else if (currentVersion < version || _allowDowngrade) + { + node.InnerText = version.ToString(); + modified = true; + LogUpdate(packageName, currentVersion, version, documentPath); + } + else + { + LogNoUpdate(packageName, currentVersion, documentPath); + } } } } + + return modified; + } + catch(Exception ex) + { + //Probably means that the package hasn't been found } - return modified; + return false; } private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetadata[] sources) package, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null) @@ -361,17 +385,11 @@ private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) { _logAction($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); -#if DEBUG - Console.WriteLine($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); -#endif } private static void LogNoUpdate(string packageName, NuGetVersion currentVersion, string file, bool isLatest = false) { _logAction($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); -#if DEBUG - Console.WriteLine($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); -#endif } } } diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index bdc6907..cdb96b0 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,10 +1,15 @@ - + - netstandard20 - True + net46;uap10.0.16299 nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time + true + + + UAP + + all @@ -13,12 +18,34 @@ all - - - - - build - + + + + + + + + + + + + + + + true + + + + + + build + + + true + lib/uap10.0.16299 + + + \ No newline at end of file diff --git a/UpdateTarget.cs b/UpdateTarget.cs new file mode 100644 index 0000000..e8bc23b --- /dev/null +++ b/UpdateTarget.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nuget.Updater +{ + public enum UpdateTarget + { + Nuspec = 2, + ProjectJson = 4, + PackageReference = 8, + + All = Nuspec | ProjectJson | PackageReference + } +} From d33fd5d9e9757219dfb3ce443b2ddc342ba02f5d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 23 Jul 2018 11:01:55 -0400 Subject: [PATCH 046/201] Fixed UAP package --- NuGetBranchSwitchExecution.cs | 7 +++---- Nuget.Updater.csproj | 18 ++++++++---------- .../NuGetBranchSwitchTask.cs | 9 ++++----- .../NuGetUpdaterTask.cs | 4 +++- 4 files changed, 18 insertions(+), 20 deletions(-) rename NuGetBranchSwitchTask.cs => Tasks/NuGetBranchSwitchTask.cs (62%) rename NuGetUpdaterTask.cs => Tasks/NuGetUpdaterTask.cs (90%) diff --git a/NuGetBranchSwitchExecution.cs b/NuGetBranchSwitchExecution.cs index 62d2181..e655d0a 100644 --- a/NuGetBranchSwitchExecution.cs +++ b/NuGetBranchSwitchExecution.cs @@ -3,14 +3,13 @@ using System.Linq; using System.Text.RegularExpressions; using System.Xml; -using Microsoft.Build.Utilities; using NuGet.Versioning; namespace Nuget.Updater { public class NuGetBranchSwitch { - private static TaskLoggingHelper _log; + private static Action _log; private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; @@ -19,7 +18,7 @@ public class NuGetBranchSwitch private readonly string _targetBranch; private readonly string _solutionRoot; - public NuGetBranchSwitch(TaskLoggingHelper log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) + public NuGetBranchSwitch(Action log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) { _log = log; _packages = packages; @@ -204,7 +203,7 @@ private void UpdateNuSpecs(string packageName, string[] nuspecFiles) private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) { - _log?.LogMessage($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); + _log($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); #if DEBUG Console.WriteLine($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); #endif diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index cdb96b0..4b90e93 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,12 +1,12 @@  - net46;uap10.0.16299 + net46;uap10.0.17134 nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time true - + UAP @@ -15,23 +15,21 @@ all runtime; build; native; contentfiles; analyzers - - all - + + all + - + - - + - true @@ -44,7 +42,7 @@ true - lib/uap10.0.16299 + lib/uap10.0.17134 diff --git a/NuGetBranchSwitchTask.cs b/Tasks/NuGetBranchSwitchTask.cs similarity index 62% rename from NuGetBranchSwitchTask.cs rename to Tasks/NuGetBranchSwitchTask.cs index 3fdab4f..2d214fa 100644 --- a/NuGetBranchSwitchTask.cs +++ b/Tasks/NuGetBranchSwitchTask.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +#if !UAP +using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -20,6 +18,7 @@ public class NuGetBranchSwitchTask : Task [Required] public string TargetBranch { get; set; } - public override bool Execute() => new NuGetBranchSwitch(Log, SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); + public override bool Execute() => new NuGetBranchSwitch(message => Log.LogMessage(message), SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); } } +#endif \ No newline at end of file diff --git a/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs similarity index 90% rename from NuGetUpdaterTask.cs rename to Tasks/NuGetUpdaterTask.cs index 16de325..000f357 100644 --- a/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -1,4 +1,5 @@ -using Microsoft.Build.Framework; +#if !UAP +using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Nuget.Updater @@ -22,3 +23,4 @@ public override bool Execute() } } } +#endif \ No newline at end of file From 113305bcb0875016f761fb374133099dad1a941a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 24 Jul 2018 15:01:10 -0400 Subject: [PATCH 047/201] Improved UAP performance and general logging --- Entities/UpdateOperation.cs | 54 ++++ UpdateTarget.cs => Entities/UpdateTarget.cs | 3 + NuGetUpdater/NuGetUpdater.Net.cs | 46 +++ NuGetUpdater/NuGetUpdater.UAP.cs | 72 ++++- NuGetUpdater/NuGetUpdater.cs | 322 +++++++++++--------- Nuget.Updater.csproj | 1 + 6 files changed, 346 insertions(+), 152 deletions(-) create mode 100644 Entities/UpdateOperation.cs rename UpdateTarget.cs => Entities/UpdateTarget.cs (81%) diff --git a/Entities/UpdateOperation.cs b/Entities/UpdateOperation.cs new file mode 100644 index 0000000..bca2425 --- /dev/null +++ b/Entities/UpdateOperation.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NuGet.Versioning; + +namespace Nuget.Updater.Entities +{ + public class UpdateOperation + { + private readonly bool _isDowngradeAllowed; + + public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, NuGetVersion updatedVersion, string filePath) + { + _isDowngradeAllowed = isDowngradeAllowed; + + Date = DateTimeOffset.Now; + + PackageName = packageName; + PreviousVersion = previousVersion; + UpdatedVersion = updatedVersion; + FilePath = filePath; + } + + public DateTimeOffset Date { get; } + + public string PackageName { get; } + + public NuGetVersion PreviousVersion { get; } + + public NuGetVersion UpdatedVersion { get; } + + public string FilePath { get; } + + public bool ShouldProceed => PreviousVersion < UpdatedVersion || (_isDowngradeAllowed && PreviousVersion > UpdatedVersion); + + public bool IsLatestVersion => PreviousVersion == UpdatedVersion; + + public string GetLogMessage() + { + if (ShouldProceed) + { + return $"Updating [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; + } + else + { + return IsLatestVersion + ? $"Version [{UpdatedVersion}] of [{PackageName}] already found in [{FilePath}]. Skipping." + : $"Higher verson of [{PackageName}] ([{UpdatedVersion}]) found in [{FilePath}]. Skipping."; + } + } + } +} diff --git a/UpdateTarget.cs b/Entities/UpdateTarget.cs similarity index 81% rename from UpdateTarget.cs rename to Entities/UpdateTarget.cs index e8bc23b..b89e6bd 100644 --- a/UpdateTarget.cs +++ b/Entities/UpdateTarget.cs @@ -6,6 +6,9 @@ namespace Nuget.Updater { + /// + /// The type of files to update + /// public enum UpdateTarget { Nuspec = 2, diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs index ebdbfa1..32a9dd7 100644 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -7,6 +7,8 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; +using Nuget.Updater.Entities; +using NuGet.Versioning; namespace Nuget.Updater { @@ -27,6 +29,50 @@ private static async Task GetFiles(CancellationToken ct, string path, return Directory.GetFiles(path, filter, SearchOption.AllDirectories); } + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, XmlNamespaceManager namespaceManager) + { + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']")) + { + if (packageReference.HasAttribute("Version")) + { + var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); + + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + + if (operation.ShouldProceed) + { + packageReference.SetAttribute("Version", version.ToString()); + modified = true; + } + + _logAction(operation.GetLogMessage()); + _updateOperations.Add(operation); + } + else + { + var node = packageReference.SelectSingleNode("d:Version", namespaceManager); + + if (node != null) + { + var currentVersion = new NuGetVersion(node.InnerText); + + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + + if (operation.ShouldProceed) + { + node.InnerText = version.ToString(); + modified = true; + } + + _logAction(operation.GetLogMessage()); + _updateOperations.Add(operation); + } + } + } + + return modified; + } + private static async Task GetDocument(CancellationToken ct, string path) { var document = new XmlDocument() diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs index 0b540bf..b95cbe7 100644 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -5,6 +5,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Nuget.Updater.Entities; +using NuGet.Versioning; using Windows.Data.Xml.Dom; using Windows.Storage; @@ -16,26 +18,74 @@ private static async Task GetFiles(CancellationToken ct, string path, { var folder = await StorageFolder.GetFolderFromPathAsync(path); - var subFoldersContent = new List(); var folders = await folder.GetFoldersAsync(); - - if (folders.Any()) - { - foreach (var f in folders) - { - subFoldersContent.AddRange(await GetFiles(ct, f.Path, extensionFilter, nameFilter)); - } - } + var subFoldersContent = (await Task.WhenAll(folders.Select(f => GetFiles(ct, f.Path, extensionFilter, nameFilter)))).SelectMany(x => x); var files = await folder.GetFilesAsync(); return files .Where(f => (extensionFilter == null && nameFilter == null) || (extensionFilter != null && f.FileType == extensionFilter) || (nameFilter != null && f.Name == nameFilter)) - .Select(f => f.Path) + .Select(f => + { + _logAction($"Found {f.Path}"); + return f.Path; + }) .Concat(subFoldersContent) .ToArray(); } + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath) + { + var packageReferences = doc.GetElementsByTagName("PackageReference") + .Cast() + .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageName); + + foreach (var packageReference in packageReferences) + { + string versionAttribute = packageReference.GetAttribute("Version"); + + if (versionAttribute != null && versionAttribute != "") + { + var currentVersion = new NuGetVersion(versionAttribute); + + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + + if (operation.ShouldProceed) + { + packageReference.SetAttribute("Version", version.ToString()); + modified = true; + } + + _logAction(operation.GetLogMessage()); + _updateOperations.Add(operation); + } + else + { + var node = packageReference.GetElementsByTagName("Version").SingleOrDefault(); + + if (node != null) + { + var currentVersion = new NuGetVersion(node.InnerText); + + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + + if (operation.ShouldProceed) + { + node.InnerText = version.ToString(); + modified = true; + } + + _logAction(operation.GetLogMessage()); + _updateOperations.Add(operation); + } + } + } + + return modified; + + return false; + } + private static async Task GetDocument(CancellationToken ct, string path) { return await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path)); @@ -50,7 +100,7 @@ private static async Task ReadFileContent(CancellationToken ct, string p { var file = await StorageFile.GetFileFromPathAsync(path); var lines = await FileIO.ReadLinesAsync(file); - var content = string.Join(Environment.NewLine, lines); + string content = string.Join(Environment.NewLine, lines); return content; } diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index 84c67e4..a377bee 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Nuget.Updater.Entities; using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol; @@ -28,9 +27,10 @@ public partial class NuGetUpdater private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; private static Action _logAction; - private static bool _allowDowngrade; + private static readonly List _updateOperations = new List(); + public static bool Update( string solutionRoot, string targetVersion, @@ -40,27 +40,100 @@ public static bool Update( bool strict = true, IEnumerable keepLatestDev = null, IEnumerable ignorePackages = null, + UpdateTarget target = UpdateTarget.All, + Action logAction = null + ) + { + _updateOperations.Clear(); + +#if DEBUG + _logAction = logAction ?? Console.WriteLine; +#else + _logAction = logAction ?? new Action(_ => { }); +#endif + _allowDowngrade = allowDowngrade; + + var packages = GetPackages(CancellationToken.None, PAT).Result; + + UpdatePackages(CancellationToken.None, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target).Start(); + + LogUpdateSummary(); + + return true; + } + + public static async Task UpdateAsync( + CancellationToken ct, + string solutionRoot, + string targetVersion, + string excludeTag = "", + string PAT = "", + bool allowDowngrade = false, + bool strict = true, + IEnumerable keepLatestDev = null, + IEnumerable ignorePackages = null, + UpdateTarget target = UpdateTarget.All, Action logAction = null ) { + _updateOperations.Clear(); + #if DEBUG - _logAction = Console.WriteLine; + _logAction = logAction ?? Console.WriteLine; #else _logAction = logAction ?? new Action(_ => { }); #endif _allowDowngrade = allowDowngrade; - var packages = GetPackages(PAT); + var packages = await GetPackages(ct, PAT); + + await UpdatePackages(ct, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target); - UpdatePackages(solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages); + LogUpdateSummary(); return true; } - private static (string, IPackageSearchMetadata[])[] GetPackages(string PAT) + private static void LogUpdateSummary() { - var q = from package in GetVSTSPackages(PAT) - .Concat(GetNuGetOrgPackages()) + var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); + var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); + + if (completedUpdates.Any()) + { + var updatedPackages = completedUpdates + .Select(o => (o.PackageName, o.UpdatedVersion)) + .Distinct() + .ToArray(); + + _logAction($"Updated {updatedPackages.Length} packages:"); + + foreach(var p in updatedPackages) + { + _logAction($"[{p.PackageName}] to [{p.UpdatedVersion}]"); + } + } + + if (skippedUpdates.Any()) + { + var skippedPackages = skippedUpdates + .Select(o => (o.PackageName, o.PreviousVersion)) + .Distinct() + .ToArray(); + + _logAction($"Skipped {skippedPackages.Length} packages:"); + + foreach (var p in skippedPackages) + { + _logAction($"[{p.PackageName}] is at version [{p.PreviousVersion}]"); + } + } + } + + private static async Task<(string, IPackageSearchMetadata[])[]> GetPackages(CancellationToken ct, string PAT) + { + var q = from package in (await GetVSTSPackages(ct, PAT)) + .Concat(await GetNuGetOrgPackages(ct)) group package by package.Identity.Id into p select ( Name: p.Key, @@ -70,7 +143,7 @@ group package by package.Identity.Id into p return q.ToArray(); } - private static IEnumerable GetNuGetOrgPackages() + private static async Task> GetNuGetOrgPackages(CancellationToken ct) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); @@ -82,13 +155,12 @@ private static IEnumerable GetNuGetOrgPackages() var searchResource = repository.GetResource(); - return searchResource - .SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) - .Result - .ToArray(); + var packages = await searchResource.SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); + + return packages.ToArray(); } - private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) + private static async Task GetVSTSPackages(CancellationToken ct, string PAT) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); @@ -103,28 +175,45 @@ private static IPackageSearchMetadata[] GetVSTSPackages(string PAT) _logAction($"Pulling NuGet packages from {source.SourceUri}"); - return searchResource - .SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), CancellationToken.None) - .Result - .ToArray(); + var packages = await searchResource.SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); + + return packages.ToArray(); } - private static async void UpdatePackages( + private static async Task UpdatePackages( + CancellationToken ct, string solutionRoot, (string title, IPackageSearchMetadata[] sources)[] packages, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null, - IEnumerable ignoredPackages = null) + IEnumerable ignoredPackages = null, + UpdateTarget target = UpdateTarget.All + ) { - var ct = CancellationToken.None; + var originalNuSpecFiles = new string[0]; + var originalJsonFiles = new string[0]; + var originalProjectFiles = new Dictionary(); - var originalNuSpecFiles = await GetFiles(ct, solutionRoot, extensionFilter: ".nuspec"); - - var originalJsonFiles = await GetFiles(ct, solutionRoot, nameFilter: "project.json"); + if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) + { + originalNuSpecFiles = await GetTargetFiles(ct, solutionRoot, UpdateTarget.Nuspec); + } + if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) + { + originalJsonFiles = await GetTargetFiles(ct, solutionRoot, UpdateTarget.ProjectJson); + } + if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) + { + var paths = await GetTargetFiles(ct, solutionRoot, UpdateTarget.PackageReference); - var originalProjectFiles = await GetFiles(ct, solutionRoot, extensionFilter: ".csproj"); + foreach(var p in paths) + { + var document = await GetDocument(ct, p); + originalProjectFiles.Add(p, document); + } + } foreach (var package in packages) { @@ -142,12 +231,53 @@ private static async void UpdatePackages( _logAction($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); - await UpdateNuSpecs(ct, package.title, latestVersion, originalNuSpecFiles); + if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) + { + await UpdateNuSpecs(ct, package.title, latestVersion, originalNuSpecFiles); + } + if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) + { + await UpdateProjectJson(ct, package.title, latestVersion, originalJsonFiles); + } + if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) + { + await UpdateProjects(ct, package.title, latestVersion, originalProjectFiles); + } + } + } + + private static async Task GetTargetFiles(CancellationToken ct, string solutionRootPath, UpdateTarget target) + { + string extensionFilter = null, nameFilter = null; - await UpdateProjectJson(ct, package.title, latestVersion, originalJsonFiles); + switch (target) + { + case UpdateTarget.Nuspec: + extensionFilter = ".nuspec"; + break; + case UpdateTarget.ProjectJson: + nameFilter = "project.json"; + break; + case UpdateTarget.PackageReference: + extensionFilter = ".csproj"; + break; + case UpdateTarget.All: + default: + break; + } - await UpdateProjects(ct, package.title, latestVersion, originalProjectFiles); + if(extensionFilter == null && nameFilter == null) + { + return new string[0]; } + + _logAction($"Retrieving {nameFilter ?? extensionFilter} files"); + + var files = await GetFiles(ct, solutionRootPath, extensionFilter, nameFilter); + + _logAction($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); + + return files; } private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] nuspecFiles) @@ -161,8 +291,6 @@ private static async Task UpdateNuSpecs(CancellationToken ct, string packageName .SelectNodes($"//x:dependency[@id='{packageName}']") .OfType(); #else - - var mgr = new XmlNamespaceManager(doc.NameTable); mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); @@ -180,19 +308,15 @@ private static async Task UpdateNuSpecs(CancellationToken ct, string packageName { var currentVersion = new NuGetVersion(versionNodeValue); - if (currentVersion == latestVersion) - { - LogNoUpdate(packageName, currentVersion, nuspecFile, isLatest: true); - } - else if (currentVersion < latestVersion || _allowDowngrade) + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, nuspecFile); + + if (operation.ShouldProceed) { node.SetAttribute("version", latestVersion.ToString()); - LogUpdate(packageName, currentVersion, latestVersion, nuspecFile); - } - else - { - LogNoUpdate(packageName, currentVersion, nuspecFile); } + + _logAction(operation.GetLogMessage()); + _updateOperations.Add(operation); } } @@ -218,11 +342,9 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package { var currentVersion = new NuGetVersion(match.Groups[1].Value); - if (currentVersion == latestVersion) - { - LogNoUpdate(packageName, currentVersion, file, isLatest: true); - } - else if (currentVersion < latestVersion || _allowDowngrade) + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, file); + + if (operation.ShouldProceed) { var newContent = Regex.Replace( fileContent, @@ -232,113 +354,41 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package ); await SetFileContent(ct, file, newContent); - - LogUpdate(packageName, currentVersion, latestVersion, file); - } - else - { - LogNoUpdate(packageName, currentVersion, file); } + + _logAction(operation.GetLogMessage()); + _updateOperations.Add(operation); } } } - private static async Task UpdateProjects(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] projectFiles) + private static async Task UpdateProjects(CancellationToken ct, string packageName, NuGetVersion latestVersion, Dictionary projectFiles) { - for (int i = 0; i < projectFiles.Length; i++) + for (int i = 0; i < projectFiles.Count; i++) { var modified = false; - var path = projectFiles[i]; - var doc = await GetDocument(ct, path); + var path = projectFiles.ElementAt(i).Key; + var document = projectFiles.ElementAt(i).Value; #if UAP - modified = UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, path); + modified = UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path); #else - var nsmgr = new XmlNamespaceManager(doc.NameTable); + var nsmgr = new XmlNamespaceManager(document.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, path, nsmgr); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr); - var nsmgr2 = new XmlNamespaceManager(doc.NameTable); + var nsmgr2 = new XmlNamespaceManager(document.NameTable); nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, doc, path, nsmgr2); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr2); #endif if (modified) { - await SaveDocument(ct, doc, projectFiles[i]); + await SaveDocument(ct, document, path); } } } -#if UAP - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath) -#else - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, XmlNamespaceManager namespaceManager) -#endif - { - try - { - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']")) - { - if (packageReference.HasAttribute("Version")) - { - var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - - if (currentVersion == version) - { - LogNoUpdate(packageName, currentVersion, documentPath, isLatest: true); - } - else if (currentVersion < version || _allowDowngrade) - { - packageReference.SetAttribute("Version", version.ToString()); - modified = true; - LogUpdate(packageName, currentVersion, version, documentPath); - } - else - { - LogNoUpdate(packageName, currentVersion, documentPath); - } - } - else - { -#if UAP - var node = packageReference.SelectSingleNode("d:Version"); -#else - var node = packageReference.SelectSingleNode("d:Version", namespaceManager); -#endif - - if (node != null) - { - var currentVersion = new NuGetVersion(node.InnerText); - - if (currentVersion == version) - { - LogNoUpdate(packageName, currentVersion, documentPath, isLatest: true); - } - else if (currentVersion < version || _allowDowngrade) - { - node.InnerText = version.ToString(); - modified = true; - LogUpdate(packageName, currentVersion, version, documentPath); - } - else - { - LogNoUpdate(packageName, currentVersion, documentPath); - } - } - } - } - - return modified; - } - catch(Exception ex) - { - //Probably means that the package hasn't been found - } - - return false; - } - private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetadata[] sources) package, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null) { var versions = package @@ -381,15 +431,5 @@ private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" } } - - private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) - { - _logAction($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); - } - - private static void LogNoUpdate(string packageName, NuGetVersion currentVersion, string file, bool isLatest = false) - { - _logAction($"Found {(isLatest ? "latest" : "higher")} version of [{packageName}] ([{currentVersion}]) in [{file}]"); - } } } diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 4b90e93..9629971 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -4,6 +4,7 @@ nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time true + 7.1 From cef1182006135cc6e0a3db6e7cee9f81dc4c3f2b Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 10 Aug 2018 15:54:03 -0400 Subject: [PATCH 048/201] Improved UWP performance --- NuGetUpdater/NuGetUpdater.UAP.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs index b95cbe7..84b0134 100644 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -9,6 +9,7 @@ using NuGet.Versioning; using Windows.Data.Xml.Dom; using Windows.Storage; +using Windows.Storage.Search; namespace Nuget.Updater { @@ -18,19 +19,27 @@ private static async Task GetFiles(CancellationToken ct, string path, { var folder = await StorageFolder.GetFolderFromPathAsync(path); - var folders = await folder.GetFoldersAsync(); - var subFoldersContent = (await Task.WhenAll(folders.Select(f => GetFiles(ct, f.Path, extensionFilter, nameFilter)))).SelectMany(x => x); + var searchFilter = extensionFilter == null + ? $"filename:\"{nameFilter}\"" + : $"extension:{extensionFilter}"; - var files = await folder.GetFilesAsync(); + var queryOptions = new QueryOptions + { + UserSearchFilter = searchFilter, + IndexerOption = IndexerOption.UseIndexerWhenAvailable, + FolderDepth = FolderDepth.Deep + }; + + var query = folder.CreateFileQueryWithOptions(queryOptions); + + var files = await query.GetFilesAsync(); return files - .Where(f => (extensionFilter == null && nameFilter == null) || (extensionFilter != null && f.FileType == extensionFilter) || (nameFilter != null && f.Name == nameFilter)) .Select(f => { _logAction($"Found {f.Path}"); return f.Path; }) - .Concat(subFoldersContent) .ToArray(); } From 9355b999789a7e8e87b2ed8e739a16423adc376f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 14 Sep 2018 15:05:52 -0400 Subject: [PATCH 049/201] Ensured whitespaces are preserved when loading xml in UAP --- NuGetUpdater/NuGetUpdater.UAP.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs index 84b0134..f6fc6f8 100644 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -97,7 +97,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers private static async Task GetDocument(CancellationToken ct, string path) { - return await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path)); + return await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path), new XmlLoadSettings { ElementContentWhiteSpace = true }); } private static async Task SaveDocument(CancellationToken ct, XmlDocument document, string path) From 3254eab46651203def28f1ec8397f48504c7dc61 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 24 Sep 2018 16:15:34 -0400 Subject: [PATCH 050/201] Fixed desktop version of nuget updater --- NuGetUpdater/NuGetUpdater.Net.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs index 32a9dd7..b141761 100644 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -31,7 +31,7 @@ private static async Task GetFiles(CancellationToken ct, string path, private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, XmlNamespaceManager namespaceManager) { - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']")) + foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", namespaceManager)) { if (packageReference.HasAttribute("Version")) { From a063581b116ac49702a24db41266c24fe5bee886 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 24 Sep 2018 16:32:29 -0400 Subject: [PATCH 051/201] Override default version comparer to compare only the 2nd part of the tag and not compare "dev" to "beta". --- Entities/NuGetVersionExtensions.cs | 184 +++++++++++++++++++++++++++++ Entities/UpdateOperation.cs | 2 +- 2 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 Entities/NuGetVersionExtensions.cs diff --git a/Entities/NuGetVersionExtensions.cs b/Entities/NuGetVersionExtensions.cs new file mode 100644 index 0000000..cd2c843 --- /dev/null +++ b/Entities/NuGetVersionExtensions.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using NuGet.Versioning; + +namespace Nuget.Updater +{ + public static class NuGetVersionExtensions + { + public static bool IsEqualTo(this NuGetVersion x, NuGetVersion y) + { + return x == y; + } + + public static bool IsGreaterThan(this NuGetVersion x, NuGetVersion y) + { + if (ReferenceEquals(x, y)) + { + return false; + } + + if (ReferenceEquals(y, null)) + { + return false; + } + + if (ReferenceEquals(x, null)) + { + return true; + } + + // compare version + var result = x.Major.CompareTo(y.Major); + if (result != 0) + { + // If Major is higher then x is greater. + return result == 1; + } + + result = x.Minor.CompareTo(y.Minor); + if (result != 0) + { + // If Minor is higher then x is greater. + return result == 1; + } + + result = x.Patch.CompareTo(y.Patch); + if (result != 0) + { + return result == 1; + } + + // compare release labels + var xLabels = GetReleaseLabelsOrNull(x); + var yLabels = GetReleaseLabelsOrNull(y); + + if (xLabels != null + && yLabels == null) + { + return false; + } + + if (xLabels == null + && yLabels != null) + { + return true; + } + + if (xLabels != null + && yLabels != null) + { + + result = CompareReleaseLabels(xLabels, yLabels); + if (result != 0) + { + return result == 1; + } + } + + return false; + } + + /// + /// Returns an array of release labels from the version, or null. + /// + private static string[] GetReleaseLabelsOrNull(SemanticVersion version) + { + string[] labels = null; + + // Check if labels exist + if (version.IsPrerelease) + { + // Try to use string[] which is how labels are normally stored. + var enumerable = version.ReleaseLabels; + labels = enumerable as string[]; + + if (labels != null && enumerable != null) + { + // This is not the expected type, enumerate and convert to an array. + labels = enumerable.ToArray(); + } + } + + return labels; + } + + /// + /// Compares sets of release labels. + /// + private static int CompareReleaseLabels(string[] xLabels, string[] yLabels) + { + var result = 0; + + var count = Math.Max(xLabels.Length, yLabels.Length); + + for (var i = 0; i < count; i++) + { + var aExists = i < xLabels.Length; + var bExists = i < yLabels.Length; + + if (!aExists && bExists) + { + return -1; + } + + if (aExists && !bExists) + { + return 1; + } + + // compare the labels + result = CompareRelease(xLabels[i], yLabels[i]); + + if (result != 0) + { + return result; + } + } + + return result; + } + + /// + /// Release labels are compared as numbers if they are numeric, otherwise they will be compared + /// as strings. + /// + private static int CompareRelease(string version1, string version2) + { + var result = 0; + + // check if the identifiers are numeric + var v1IsNumeric = int.TryParse(version1, out var version1Num); + var v2IsNumeric = int.TryParse(version2, out var version2Num); + + // if both are numeric compare them as numbers + if (v1IsNumeric && v2IsNumeric) + { + result = version1Num.CompareTo(version2Num); + } + else if (v1IsNumeric || v2IsNumeric) + { + // numeric labels come before alpha labels + if (v1IsNumeric) + { + result = -1; + } + else + { + result = 1; + } + } + else + { + // If they aren't numeric, don't take a decision. + } + + return result; + } + + } +} diff --git a/Entities/UpdateOperation.cs b/Entities/UpdateOperation.cs index bca2425..bb468c9 100644 --- a/Entities/UpdateOperation.cs +++ b/Entities/UpdateOperation.cs @@ -33,7 +33,7 @@ public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion public string FilePath { get; } - public bool ShouldProceed => PreviousVersion < UpdatedVersion || (_isDowngradeAllowed && PreviousVersion > UpdatedVersion); + public bool ShouldProceed => PreviousVersion < UpdatedVersion || (_isDowngradeAllowed && PreviousVersion.IsGreaterThan(UpdatedVersion)); public bool IsLatestVersion => PreviousVersion == UpdatedVersion; From 2a9bc7cc210bd9b4edb99182906a80a4a2c54fa0 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 25 Sep 2018 17:20:02 -0400 Subject: [PATCH 052/201] Updated the way the task calls the underlying async method --- NuGetUpdater/NuGetUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index a377bee..d604be7 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -55,7 +55,7 @@ public static bool Update( var packages = GetPackages(CancellationToken.None, PAT).Result; - UpdatePackages(CancellationToken.None, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target).Start(); + UpdatePackages(CancellationToken.None, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target).RunSynchronously(); LogUpdateSummary(); From 321a6601f80dffceaae17a24222e34a1eb69e777 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Wed, 26 Sep 2018 14:41:48 -0400 Subject: [PATCH 053/201] Added the "allowDowngrade" parameter to the task --- Tasks/NuGetUpdaterTask.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 000f357..7720858 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -16,10 +16,13 @@ public class NuGetUpdaterTask : Task [Required] public string PAT { get; set; } + + [Required] + public bool AllowDowngrade { get; set; } public override bool Execute() { - return NuGetUpdater.Update(SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT, logAction: message => Log.LogMessage(message)); + return NuGetUpdater.Update(SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT, allowDowngrade: AllowDowngrade, logAction: message => Log.LogMessage(message)); } } } From b65f235101d439b1df7afac1c08a168e5d6bad90 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 28 Sep 2018 10:49:24 -0400 Subject: [PATCH 054/201] Updated the way the async task is awaited --- NuGetUpdater/NuGetUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index d604be7..a008ebe 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -55,7 +55,7 @@ public static bool Update( var packages = GetPackages(CancellationToken.None, PAT).Result; - UpdatePackages(CancellationToken.None, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target).RunSynchronously(); + UpdatePackages(CancellationToken.None, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target).Wait(); LogUpdateSummary(); From 7c2744a4fb07ddf45f74debbdb1e715abb0a60a2 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 1 Oct 2018 12:34:26 -0400 Subject: [PATCH 055/201] Ensured "stable" version is correctly handled --- NuGetUpdater/NuGetUpdater.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index a008ebe..eb6a0a8 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -398,6 +398,11 @@ private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetada var specialVersion = (keepLatestDev?.Contains(package.title, StringComparer.OrdinalIgnoreCase) ?? false) ? "dev" : targetVersion; + if(specialVersion == "stable") + { + specialVersion = ""; + } + return versions .Where(v => IsMatchingSpecialVersion(specialVersion, v, strict) && !ContainsTag(excludeTag, v)) .OrderByDescending(v => v.Version) From 333d5d6d937067127ed93314431e3b6d2c4dae1d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 1 Oct 2018 14:35:59 -0400 Subject: [PATCH 056/201] Added possibility to set IgnorePackage through the Task --- Tasks/NuGetUpdaterTask.cs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 7720858..0bd3201 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -11,6 +11,8 @@ public class NuGetUpdaterTask : Task public string ExcludeTag { get; set; } + public string IgnorePackages { get; set; } + [Required] public string SolutionRoot { get; set; } @@ -22,7 +24,33 @@ public class NuGetUpdaterTask : Task public override bool Execute() { - return NuGetUpdater.Update(SolutionRoot, SpecialVersion, ExcludeTag, PAT: PAT, allowDowngrade: AllowDowngrade, logAction: message => Log.LogMessage(message)); + string[] packagesToIgnore; + + switch (IgnorePackages) + { + case var p when p == null: + packagesToIgnore = new string[0]; + break; + case var p when p.Contains(";"): + packagesToIgnore = p.Split(';'); + break; + case var p when p != null: + packagesToIgnore = new[] { IgnorePackages }; + break; + default: + packagesToIgnore = null; + break; + } + + return NuGetUpdater.Update( + SolutionRoot, + SpecialVersion, + ExcludeTag, + PAT: PAT, + allowDowngrade: AllowDowngrade, + ignorePackages: packagesToIgnore, + logAction: message => Log.LogMessage(message) + ); } } } From 6c38c95b94ba9a0b52ceaae3bd91f4c93b86d53e Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 14 Feb 2019 17:49:04 -0500 Subject: [PATCH 057/201] Added possibility to specify a custom feed url and to update only specific packages --- NuGetUpdater/NuGetUpdater.Net.cs | 19 +++++- NuGetUpdater/NuGetUpdater.UAP.cs | 4 ++ NuGetUpdater/NuGetUpdater.cs | 108 ++++++++++++++++++++----------- Tasks/NuGetUpdaterTask.cs | 29 ++++++++- 4 files changed, 119 insertions(+), 41 deletions(-) diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs index b141761..6c7dceb 100644 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -12,8 +12,23 @@ namespace Nuget.Updater { - partial class NuGetUpdater + public partial class NuGetUpdater { + private static void LogUpdateSummaryToFile(string outputFilePath) + { + try + { + using (var file = File.OpenWrite(outputFilePath)) + using (var writer = new StreamWriter(file)) + { + LogSummary(line => writer.WriteLine(line)); + } + } + catch(Exception ex) + { + _logAction($"Failed to write to {outputFilePath}. Reason : {ex.Message}"); + } + } private static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) { @@ -30,7 +45,7 @@ private static async Task GetFiles(CancellationToken ct, string path, } private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, XmlNamespaceManager namespaceManager) - { + { foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", namespaceManager)) { if (packageReference.HasAttribute("Version")) diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs index f6fc6f8..d353fce 100644 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -15,6 +15,10 @@ namespace Nuget.Updater { partial class NuGetUpdater { + private static void LogUpdateSummaryToFile(string path) + { + } + private static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) { var folder = await StorageFolder.GetFolderFromPathAsync(path); diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index eb6a0a8..7e50002 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -33,47 +33,56 @@ public partial class NuGetUpdater public static bool Update( string solutionRoot, + string sourceFeed, string targetVersion, string excludeTag = "", string PAT = "", + bool includeNuGetOrg = true, bool allowDowngrade = false, bool strict = true, IEnumerable keepLatestDev = null, IEnumerable ignorePackages = null, + IEnumerable updatePackages = null, UpdateTarget target = UpdateTarget.All, - Action logAction = null + Action logAction = null, + string summaryOutputFilePath = null ) { - _updateOperations.Clear(); - -#if DEBUG - _logAction = logAction ?? Console.WriteLine; -#else - _logAction = logAction ?? new Action(_ => { }); -#endif - _allowDowngrade = allowDowngrade; - - var packages = GetPackages(CancellationToken.None, PAT).Result; - - UpdatePackages(CancellationToken.None, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target).Wait(); - - LogUpdateSummary(); - - return true; + return UpdateAsync( + CancellationToken.None, + solutionRoot, + sourceFeed, + targetVersion, + excludeTag, + PAT, + includeNuGetOrg, + allowDowngrade, + strict, + keepLatestDev, + ignorePackages, + updatePackages, + target, + logAction, + summaryOutputFilePath + ).Result; } public static async Task UpdateAsync( CancellationToken ct, string solutionRoot, + string sourceFeed, string targetVersion, string excludeTag = "", string PAT = "", + bool includeNuGetOrg = true, bool allowDowngrade = false, bool strict = true, IEnumerable keepLatestDev = null, IEnumerable ignorePackages = null, + IEnumerable updatePackages = null, UpdateTarget target = UpdateTarget.All, - Action logAction = null + Action logAction = null, + string summaryOutputFilePath = null ) { _updateOperations.Clear(); @@ -85,20 +94,35 @@ public static async Task UpdateAsync( #endif _allowDowngrade = allowDowngrade; - var packages = await GetPackages(ct, PAT); + var packages = await GetPackages(ct, sourceFeed, PAT, includeNuGetOrg); - await UpdatePackages(ct, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, target); + await UpdatePackages(ct, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, updatePackages, target); - LogUpdateSummary(); + LogUpdateSummary(summaryOutputFilePath); return true; } - private static void LogUpdateSummary() + private static void LogUpdateSummary(string outputFilePath = null) + { + LogSummary(_logAction); + + if (outputFilePath != null) + { + LogUpdateSummaryToFile(outputFilePath); + } + } + + private static void LogSummary(Action logAction) { var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); + if(completedUpdates.Any() || skippedUpdates.Any()) + { + logAction($"# Package update summary"); + } + if (completedUpdates.Any()) { var updatedPackages = completedUpdates @@ -106,11 +130,11 @@ private static void LogUpdateSummary() .Distinct() .ToArray(); - _logAction($"Updated {updatedPackages.Length} packages:"); + logAction($"## Updated {updatedPackages.Length} packages:"); - foreach(var p in updatedPackages) + foreach (var p in updatedPackages) { - _logAction($"[{p.PackageName}] to [{p.UpdatedVersion}]"); + logAction($"- [{p.PackageName}] to [{p.UpdatedVersion}]"); } } @@ -121,24 +145,26 @@ private static void LogUpdateSummary() .Distinct() .ToArray(); - _logAction($"Skipped {skippedPackages.Length} packages:"); + logAction($"## Skipped {skippedPackages.Length} packages:"); foreach (var p in skippedPackages) { - _logAction($"[{p.PackageName}] is at version [{p.PreviousVersion}]"); + logAction($"- [{p.PackageName}] is at version [{p.PreviousVersion}]"); } } } - private static async Task<(string, IPackageSearchMetadata[])[]> GetPackages(CancellationToken ct, string PAT) + private static async Task<(string, IPackageSearchMetadata[])[]> GetPackages(CancellationToken ct, string feed, string PAT, bool includNuGetOrg) { - var q = from package in (await GetVSTSPackages(ct, PAT)) - .Concat(await GetNuGetOrgPackages(ct)) + var packages = await GetFeedPackages(ct, feed, PAT); + + if (includNuGetOrg) { + packages = packages.Concat(await GetNuGetOrgPackages(ct)); + } + + var q = from package in packages group package by package.Identity.Id into p - select ( - Name: p.Key, - Sources: p.ToArray() - ); + select (Name: p.Key, Sources: p.ToArray()); return q.ToArray(); } @@ -160,14 +186,14 @@ private static async Task> GetNuGetOrgPackag return packages.ToArray(); } - private static async Task GetVSTSPackages(CancellationToken ct, string PAT) + private static async Task> GetFeedPackages(CancellationToken ct, string feed, string PAT) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); - var source = new PackageSource("https://nventive.pkgs.visualstudio.com/_packaging/nventive/nuget/v3/index.json", "nventive") + var source = new PackageSource(feed, "Feed") { - Credentials = PackageSourceCredential.FromUserInput("nventive", "it@nventive.com", PAT, false) + Credentials = PackageSourceCredential.FromUserInput("Feed", "user", PAT, false) }; var repository = repositoryProvider.CreateRepository(source); @@ -177,7 +203,7 @@ private static async Task GetVSTSPackages(Cancellation var packages = await searchResource.SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); - return packages.ToArray(); + return packages; } private static async Task UpdatePackages( @@ -189,6 +215,7 @@ private static async Task UpdatePackages( bool strict, IEnumerable keepLatestDev = null, IEnumerable ignoredPackages = null, + IEnumerable packagesToUpdate = null, UpdateTarget target = UpdateTarget.All ) { @@ -222,6 +249,11 @@ private static async Task UpdatePackages( continue; } + if(packagesToUpdate != null && !packagesToUpdate.Contains(package.title)) + { + continue; + } + var latestVersion = GetLatestVersion(package, targetVersion, excludeTag, strict, keepLatestDev); if (latestVersion == null) diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 0bd3201..462d1e8 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -13,9 +13,16 @@ public class NuGetUpdaterTask : Task public string IgnorePackages { get; set; } + public string UpdatePackages { get; set; } + + public string UpdateSummaryFile { get; set; } + [Required] public string SolutionRoot { get; set; } + [Required] + public string NuGetFeed { get; set; } + [Required] public string PAT { get; set; } @@ -35,20 +42,40 @@ public override bool Execute() packagesToIgnore = p.Split(';'); break; case var p when p != null: - packagesToIgnore = new[] { IgnorePackages }; + packagesToIgnore = new[] { p }; break; default: packagesToIgnore = null; break; } + string[] packagesToUpdate; + + switch (UpdatePackages) + { + case var p when p == null: + packagesToUpdate = new string[0]; + break; + case var p when p.Contains(";"): + packagesToUpdate = p.Split(';'); + break; + case var p when p != null: + packagesToUpdate = new[] { p }; + break; + default: + packagesToUpdate = null; + break; + } + return NuGetUpdater.Update( SolutionRoot, + NuGetFeed, SpecialVersion, ExcludeTag, PAT: PAT, allowDowngrade: AllowDowngrade, ignorePackages: packagesToIgnore, + updatePackages: packagesToUpdate, logAction: message => Log.LogMessage(message) ); } From dd249ecb75d5e63faae617dcaa4c1d058f4193c5 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 15 Feb 2019 11:42:34 -0500 Subject: [PATCH 058/201] Added url in log file --- Entities/UpdateOperation.cs | 5 +- NuGetUpdater/NuGetUpdater.Net.cs | 16 +++- NuGetUpdater/NuGetUpdater.UAP.cs | 6 +- NuGetUpdater/NuGetUpdater.cs | 141 ++++++++++++++++++------------- 4 files changed, 103 insertions(+), 65 deletions(-) diff --git a/Entities/UpdateOperation.cs b/Entities/UpdateOperation.cs index bb468c9..b1fc9b3 100644 --- a/Entities/UpdateOperation.cs +++ b/Entities/UpdateOperation.cs @@ -11,7 +11,7 @@ public class UpdateOperation { private readonly bool _isDowngradeAllowed; - public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, NuGetVersion updatedVersion, string filePath) + public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, NuGetVersion updatedVersion, string filePath, Uri feedUri) { _isDowngradeAllowed = isDowngradeAllowed; @@ -21,6 +21,7 @@ public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion PreviousVersion = previousVersion; UpdatedVersion = updatedVersion; FilePath = filePath; + FeedUri = feedUri; } public DateTimeOffset Date { get; } @@ -33,6 +34,8 @@ public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion public string FilePath { get; } + public Uri FeedUri { get; } + public bool ShouldProceed => PreviousVersion < UpdatedVersion || (_isDowngradeAllowed && PreviousVersion.IsGreaterThan(UpdatedVersion)); public bool IsLatestVersion => PreviousVersion == UpdatedVersion; diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs index 6c7dceb..9cd236f 100644 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -21,7 +21,7 @@ private static void LogUpdateSummaryToFile(string outputFilePath) using (var file = File.OpenWrite(outputFilePath)) using (var writer = new StreamWriter(file)) { - LogSummary(line => writer.WriteLine(line)); + LogSummary(line => writer.WriteLine(line), includeUrl: true); } } catch(Exception ex) @@ -44,7 +44,15 @@ private static async Task GetFiles(CancellationToken ct, string path, return Directory.GetFiles(path, filter, SearchOption.AllDirectories); } - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, XmlNamespaceManager namespaceManager) + private static bool UpdateProjectReferenceVersions( + string packageName, + NuGetVersion version, + bool modified, + XmlDocument doc, + string documentPath, + XmlNamespaceManager namespaceManager, + Uri feedUri + ) { foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", namespaceManager)) { @@ -52,7 +60,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); if (operation.ShouldProceed) { @@ -71,7 +79,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(node.InnerText); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); if (operation.ShouldProceed) { diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs index d353fce..eb9ebb6 100644 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -47,7 +47,7 @@ private static async Task GetFiles(CancellationToken ct, string path, .ToArray(); } - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath) + private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, Uri feedUri) { var packageReferences = doc.GetElementsByTagName("PackageReference") .Cast() @@ -61,7 +61,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(versionAttribute); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); if (operation.ShouldProceed) { @@ -80,7 +80,7 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(node.InnerText); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); if (operation.ShouldProceed) { diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index 7e50002..e8dcb3c 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -25,6 +25,7 @@ namespace Nuget.Updater public partial class NuGetUpdater { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + private const string AzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; private static Action _logAction; private static bool _allowDowngrade; @@ -113,7 +114,7 @@ private static void LogUpdateSummary(string outputFilePath = null) } } - private static void LogSummary(Action logAction) + private static void LogSummary(Action logAction, bool includeUrl = false) { var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); @@ -126,7 +127,7 @@ private static void LogSummary(Action logAction) if (completedUpdates.Any()) { var updatedPackages = completedUpdates - .Select(o => (o.PackageName, o.UpdatedVersion)) + .Select(o => (o.PackageName, o.UpdatedVersion, o.FeedUri)) .Distinct() .ToArray(); @@ -134,14 +135,17 @@ private static void LogSummary(Action logAction) foreach (var p in updatedPackages) { - logAction($"- [{p.PackageName}] to [{p.UpdatedVersion}]"); + var logMessage = $"[{p.PackageName}] to [{p.UpdatedVersion}]"; + var url = includeUrl ? GetPackageUrl(p.PackageName, p.UpdatedVersion, p.FeedUri) : default; + + logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); } } if (skippedUpdates.Any()) { var skippedPackages = skippedUpdates - .Select(o => (o.PackageName, o.PreviousVersion)) + .Select(o => (o.PackageName, o.PreviousVersion, o.FeedUri)) .Distinct() .ToArray(); @@ -149,27 +153,28 @@ private static void LogSummary(Action logAction) foreach (var p in skippedPackages) { - logAction($"- [{p.PackageName}] is at version [{p.PreviousVersion}]"); + var logMessage = $"[{p.PackageName}] is at version [{p.PreviousVersion}]"; + var url = includeUrl ? GetPackageUrl(p.PackageName, p.PreviousVersion, p.FeedUri) : default; + + logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); } } } - private static async Task<(string, IPackageSearchMetadata[])[]> GetPackages(CancellationToken ct, string feed, string PAT, bool includNuGetOrg) + private static async Task<(Uri, IEnumerable)[]> GetPackages(CancellationToken ct, string feed, string PAT, bool includNuGetOrg) { - var packages = await GetFeedPackages(ct, feed, PAT); + var packages = new List<(Uri, IEnumerable)>(); + + packages.Add(await GetFeedPackages(ct, feed, PAT)); if (includNuGetOrg) { - packages = packages.Concat(await GetNuGetOrgPackages(ct)); + packages.Add(await GetNuGetOrgPackages(ct)); } - var q = from package in packages - group package by package.Identity.Id into p - select (Name: p.Key, Sources: p.ToArray()); - - return q.ToArray(); + return packages.ToArray(); } - private static async Task> GetNuGetOrgPackages(CancellationToken ct) + private static async Task<(Uri sourceUri, IEnumerable packages)> GetNuGetOrgPackages(CancellationToken ct) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); @@ -183,10 +188,10 @@ private static async Task> GetNuGetOrgPackag var packages = await searchResource.SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); - return packages.ToArray(); + return (sourceUri: source.SourceUri, packages); } - private static async Task> GetFeedPackages(CancellationToken ct, string feed, string PAT) + private static async Task<(Uri sourceUri, IEnumerable packages)> GetFeedPackages(CancellationToken ct, string feed, string PAT) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); @@ -203,13 +208,13 @@ private static async Task> GetFeedPackages(C var packages = await searchResource.SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); - return packages; + return (sourceUri: source.SourceUri, packages); } private static async Task UpdatePackages( CancellationToken ct, string solutionRoot, - (string title, IPackageSearchMetadata[] sources)[] packages, + (Uri sourceUri, IEnumerable packages)[] sources, string targetVersion, string excludeTag, bool strict, @@ -242,38 +247,43 @@ private static async Task UpdatePackages( } } - foreach (var package in packages) + foreach (var source in sources) { - if (ignoredPackages != null && ignoredPackages.Contains(package.title)) + foreach (var package in source.packages) { - continue; - } + var packageId = package.Identity.Id; - if(packagesToUpdate != null && !packagesToUpdate.Contains(package.title)) - { - continue; - } + if (ignoredPackages != null && ignoredPackages.Contains(packageId)) + { + continue; + } - var latestVersion = GetLatestVersion(package, targetVersion, excludeTag, strict, keepLatestDev); + if (packagesToUpdate != null && !packagesToUpdate.Contains(packageId)) + { + continue; + } - if (latestVersion == null) - { - continue; - } + var latestVersion = await GetLatestVersion(ct, package, targetVersion, excludeTag, strict, keepLatestDev); - _logAction($"Latest {targetVersion} version for [{package.title}] is [{latestVersion}]"); + if (latestVersion == null) + { + continue; + } - if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) - { - await UpdateNuSpecs(ct, package.title, latestVersion, originalNuSpecFiles); - } - if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) - { - await UpdateProjectJson(ct, package.title, latestVersion, originalJsonFiles); - } - if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) - { - await UpdateProjects(ct, package.title, latestVersion, originalProjectFiles); + _logAction($"Latest {targetVersion} version for [{packageId}] is [{latestVersion}]"); + + if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) + { + await UpdateNuSpecs(ct, packageId, latestVersion, originalNuSpecFiles, source.sourceUri); + } + if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) + { + await UpdateProjectJson(ct, packageId, latestVersion, originalJsonFiles, source.sourceUri); + } + if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) + { + await UpdateProjects(ct, packageId, latestVersion, originalProjectFiles, source.sourceUri); + } } } } @@ -312,7 +322,7 @@ private static async Task GetTargetFiles(CancellationToken ct, string return files; } - private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] nuspecFiles) + private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] nuspecFiles, Uri feedUri) { foreach (var nuspecFile in nuspecFiles) { @@ -340,7 +350,7 @@ private static async Task UpdateNuSpecs(CancellationToken ct, string packageName { var currentVersion = new NuGetVersion(versionNodeValue); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, nuspecFile); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, nuspecFile, feedUri); if (operation.ShouldProceed) { @@ -359,7 +369,7 @@ private static async Task UpdateNuSpecs(CancellationToken ct, string packageName } } - private static async Task UpdateProjectJson(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] jsonFiles) + private static async Task UpdateProjectJson(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] jsonFiles, Uri feedUri) { var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; var replaced = $@"""{packageName}"": ""{latestVersion}"""; @@ -374,7 +384,7 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package { var currentVersion = new NuGetVersion(match.Groups[1].Value); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, file); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, file, feedUri); if (operation.ShouldProceed) { @@ -394,7 +404,7 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package } } - private static async Task UpdateProjects(CancellationToken ct, string packageName, NuGetVersion latestVersion, Dictionary projectFiles) + private static async Task UpdateProjects(CancellationToken ct, string packageName, NuGetVersion latestVersion, Dictionary projectFiles, Uri feedUri) { for (int i = 0; i < projectFiles.Count; i++) { @@ -403,15 +413,15 @@ private static async Task UpdateProjects(CancellationToken ct, string packageNam var document = projectFiles.ElementAt(i).Value; #if UAP - modified = UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path); + modified = UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, feedUri); #else var nsmgr = new XmlNamespaceManager(document.NameTable); nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr, feedUri); var nsmgr2 = new XmlNamespaceManager(document.NameTable); nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr2); + modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr2, feedUri); #endif if (modified) @@ -421,14 +431,11 @@ private static async Task UpdateProjects(CancellationToken ct, string packageNam } } - private static NuGetVersion GetLatestVersion((string title, IPackageSearchMetadata[] sources) package, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null) + private static async Task GetLatestVersion(CancellationToken ct, IPackageSearchMetadata package, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null) { - var versions = package - .sources - .SelectMany(s => s.GetVersionsAsync().Result) - .OrderByDescending(v => v.Version); + var versions = (await package.GetVersionsAsync()).OrderByDescending(v => v.Version); - var specialVersion = (keepLatestDev?.Contains(package.title, StringComparer.OrdinalIgnoreCase) ?? false) ? "dev" : targetVersion; + var specialVersion = (keepLatestDev?.Contains(package.Identity.Id, StringComparer.OrdinalIgnoreCase) ?? false) ? "dev" : targetVersion; if(specialVersion == "stable") { @@ -468,5 +475,25 @@ private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" } } + + private static string GetPackageUrl(string packageId, NuGetVersion version, Uri feedUri) + { + if(feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) + { + return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; + } + + var match = Regex.Match(feedUri.AbsoluteUri, AzureArtifactsFeedUrlPattern); + + if(match.Length > 0) + { + string accountName = match.Groups["account"].Value; + string feedName = match.Groups["feed"].Value; + + return $"https://dev.azure.com/{accountName}/_packaging?_a=package&feed={feedName}&package={packageId}&version={version.ToFullString()}&protocolType=NuGet"; + } + + return default; + } } } From 5cf0956b0005869a17e6912c26ddc25305a87936 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 15 Feb 2019 16:23:31 -0500 Subject: [PATCH 059/201] Added support for new ADO package feed url --- NuGetUpdater/NuGetUpdater.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index e8dcb3c..b827cb9 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -25,7 +25,8 @@ namespace Nuget.Updater public partial class NuGetUpdater { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - private const string AzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; + private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; + private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; private static Action _logAction; private static bool _allowDowngrade; @@ -483,7 +484,14 @@ private static string GetPackageUrl(string packageId, NuGetVersion version, Uri return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; } - var match = Regex.Match(feedUri.AbsoluteUri, AzureArtifactsFeedUrlPattern); + var pattern = LegacyAzureArtifactsFeedUrlPattern; + + if(feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com")) + { + pattern = AzureArtifactsFeedUrlPattern; + } + + var match = Regex.Match(feedUri.AbsoluteUri, pattern); if(match.Length > 0) { From b25e0f2e1f868d1697c560ef6673f8bec1272054 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 15 Feb 2019 18:50:55 -0500 Subject: [PATCH 060/201] Passed update summary file in the task --- Tasks/NuGetUpdaterTask.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 462d1e8..d44c43a 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -76,7 +76,8 @@ public override bool Execute() allowDowngrade: AllowDowngrade, ignorePackages: packagesToIgnore, updatePackages: packagesToUpdate, - logAction: message => Log.LogMessage(message) + logAction: message => Log.LogMessage(message), + summaryOutputFilePath: UpdateSummaryFile ); } } From 8f80e0c6a7681fe20d131c2aa1178de27a9c969a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 15 Feb 2019 18:52:58 -0500 Subject: [PATCH 061/201] Added logging --- NuGetUpdater/NuGetUpdater.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index b827cb9..219b664 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -187,7 +187,9 @@ private static void LogSummary(Action logAction, bool includeUrl = false var searchResource = repository.GetResource(); - var packages = await searchResource.SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); + var packages = (await searchResource.SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct)).ToArray(); + + _logAction($"FOund {packages.Length} packages"); return (sourceUri: source.SourceUri, packages); } @@ -207,7 +209,9 @@ private static void LogSummary(Action logAction, bool includeUrl = false _logAction($"Pulling NuGet packages from {source.SourceUri}"); - var packages = await searchResource.SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct); + var packages = (await searchResource.SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct)).ToArray(); + + _logAction($"FOund {packages.Length} packages"); return (sourceUri: source.SourceUri, packages); } From 633d4e8605947d5400045c86983699ee47eee6ff Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 18 Feb 2019 11:54:51 -0500 Subject: [PATCH 062/201] Clean up --- Entities/FeedNuGetVersion.cs | 18 ++ Entities/NuGetPackage.cs | 31 +++ Entities/UpdateOperation.cs | 6 +- Extensions/EnumerableAsyncExtensions.cs | 22 ++ Extensions/NuGetPackageExtensions.cs | 87 +++++++ Extensions/PackageSourceExtensions.cs | 56 +++++ NuGetUpdater/NuGetUpdater.Log.cs | 111 +++++++++ NuGetUpdater/NuGetUpdater.Net.cs | 43 ++-- NuGetUpdater/NuGetUpdater.UAP.cs | 24 +- NuGetUpdater/NuGetUpdater.cs | 298 +++++------------------- 10 files changed, 422 insertions(+), 274 deletions(-) create mode 100644 Entities/FeedNuGetVersion.cs create mode 100644 Entities/NuGetPackage.cs create mode 100644 Extensions/EnumerableAsyncExtensions.cs create mode 100644 Extensions/NuGetPackageExtensions.cs create mode 100644 Extensions/PackageSourceExtensions.cs create mode 100644 NuGetUpdater/NuGetUpdater.Log.cs diff --git a/Entities/FeedNuGetVersion.cs b/Entities/FeedNuGetVersion.cs new file mode 100644 index 0000000..d5d2d69 --- /dev/null +++ b/Entities/FeedNuGetVersion.cs @@ -0,0 +1,18 @@ +using System; +using NuGet.Versioning; + +namespace Nuget.Updater.Entities +{ + public class FeedNuGetVersion + { + public FeedNuGetVersion(Uri feedUri, NuGetVersion version) + { + FeedUri = feedUri; + Version = version; + } + + public Uri FeedUri { get; } + + public NuGetVersion Version { get; } + } +} diff --git a/Entities/NuGetPackage.cs b/Entities/NuGetPackage.cs new file mode 100644 index 0000000..bf9f8f4 --- /dev/null +++ b/Entities/NuGetPackage.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NuGet.Protocol.Core.Types; + +namespace Nuget.Updater.Entities +{ + public class NuGetPackage + { + public NuGetPackage(string packageId, params NuGetPackage[] packages) + { + PackageId = packageId; + Packages = packages + .SelectMany(p => p.Packages) + .ToDictionary(p => p.Key, p => p.Value); + } + + public NuGetPackage(IPackageSearchMetadata package, Uri packageSourceUri) + { + PackageId = package.Identity.Id; + Packages = new Dictionary + { + { packageSourceUri, package }, + }; + } + + public string PackageId { get; } + + public Dictionary Packages { get; } + } +} diff --git a/Entities/UpdateOperation.cs b/Entities/UpdateOperation.cs index b1fc9b3..4e723c7 100644 --- a/Entities/UpdateOperation.cs +++ b/Entities/UpdateOperation.cs @@ -11,7 +11,7 @@ public class UpdateOperation { private readonly bool _isDowngradeAllowed; - public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, NuGetVersion updatedVersion, string filePath, Uri feedUri) + public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, FeedNuGetVersion updatedVersion, string filePath) { _isDowngradeAllowed = isDowngradeAllowed; @@ -19,9 +19,9 @@ public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion PackageName = packageName; PreviousVersion = previousVersion; - UpdatedVersion = updatedVersion; + UpdatedVersion = updatedVersion.Version; FilePath = filePath; - FeedUri = feedUri; + FeedUri = updatedVersion.FeedUri; } public DateTimeOffset Date { get; } diff --git a/Extensions/EnumerableAsyncExtensions.cs b/Extensions/EnumerableAsyncExtensions.cs new file mode 100644 index 0000000..3eeae8d --- /dev/null +++ b/Extensions/EnumerableAsyncExtensions.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using NuGet.Common; + +namespace Nuget.Updater.Extensions +{ + public static class EnumerableAsyncExtensions + { + public static async Task ToArray(this IEnumerableAsync enumerable) + { + var list = new List(); + var enumerator = enumerable.GetEnumeratorAsync(); + + while(await enumerator.MoveNextAsync()) + { + list.Add(enumerator.Current); + } + + return list.ToArray(); + } + } +} diff --git a/Extensions/NuGetPackageExtensions.cs b/Extensions/NuGetPackageExtensions.cs new file mode 100644 index 0000000..f99d484 --- /dev/null +++ b/Extensions/NuGetPackageExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Nuget.Updater.Entities; +using NuGet.Versioning; + +namespace Nuget.Updater.Extensions +{ + public static class NuGetPackageExtensions + { + public static async Task GetLatestVersion( + this NuGetPackage package, + CancellationToken ct, + string targetVersion, + string excludeTag, + bool strict, + IEnumerable keepLatestDev = null + ) + { + var versions = (await package.GetVersions(ct)).OrderByDescending(v => v.Version); + + + var specialVersion = targetVersion; + + if ((keepLatestDev?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? false)) + { + specialVersion = "dev"; + } + + if (specialVersion == "stable") + { + specialVersion = ""; + } + + var version = versions + .Where(v => IsMatchingSpecialVersion(specialVersion, v.Version, strict) && !ContainsTag(excludeTag, v.Version)) + .OrderByDescending(v => v.Version) + .FirstOrDefault(); + + return version; + } + + private static async Task> GetVersions(this NuGetPackage package, CancellationToken ct) + { + var versions = new List(); + foreach(var p in package.Packages) + { + foreach(var v in await p.Value.GetVersionsAsync()) + { + versions.Add(new FeedNuGetVersion(p.Key, v.Version)); + } + } + + return versions; + } + + private static bool ContainsTag(string tag, NuGetVersion version) + { + if (tag?.Equals("") ?? true) + { + return false; + } + + return version?.ReleaseLabels?.Contains(tag) ?? false; + } + + private static bool IsMatchingSpecialVersion(string specialVersion, NuGetVersion version, bool strict) + { + if (string.IsNullOrEmpty(specialVersion)) + { + return !version?.ReleaseLabels?.Any() ?? true; + } + else + { + var releaseLabels = version?.ReleaseLabels; + var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + + return strict + ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" + : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" + } + } + } +} diff --git a/Extensions/PackageSourceExtensions.cs b/Extensions/PackageSourceExtensions.cs new file mode 100644 index 0000000..9ab9c08 --- /dev/null +++ b/Extensions/PackageSourceExtensions.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Nuget.Updater.Entities; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; + +namespace Nuget.Updater.Extensions +{ + public static class PackageSourceExtensions + { + public static async Task SearchPackages(this PackageSource source, CancellationToken ct, Action logAction, string searchTerm = "") + { + var settings = Settings.LoadDefaultSettings(null); + var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + + var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); + + logAction($"Pulling NuGet packages from {source.SourceUri}"); + + var searchResource = repository.GetResource(); + + var packages = (await searchResource.SearchAsync(searchTerm, new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), skip: 0, take: 1000, log: new NullLogger(), cancellationToken: ct)).ToArray(); + + logAction($"Found {packages.Length} packages"); + + return source.ToNuGetPackages(packages); + } + + public static async Task ListPackages(this PackageSource source, CancellationToken ct, Action logAction, string searchTerm = "") + { + var settings = Settings.LoadDefaultSettings(null); + var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + + var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); + + logAction($"Pulling NuGet packages from {source.SourceUri}"); + + var listResource = repository.GetResource(); + + var packages = await (await listResource.ListAsync(searchTerm, prerelease: true, allVersions: false, includeDelisted: false, log: new NullLogger(), token: ct)).ToArray(); + + logAction($"Found {packages.Length} packages"); + + return source.ToNuGetPackages(packages); + } + + private static NuGetPackage[] ToNuGetPackages(this PackageSource source, IPackageSearchMetadata[] packages) => + packages + .Select(p => new NuGetPackage(p, source.SourceUri)) + .ToArray(); + } +} diff --git a/NuGetUpdater/NuGetUpdater.Log.cs b/NuGetUpdater/NuGetUpdater.Log.cs new file mode 100644 index 0000000..aba933f --- /dev/null +++ b/NuGetUpdater/NuGetUpdater.Log.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Nuget.Updater.Entities; +using NuGet.Versioning; + +namespace Nuget.Updater +{ + partial class NuGetUpdater + { + private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; + private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; + + private static readonly List _updateOperations = new List(); + + private static Action _logAction; + + private static void Log(string message) => _logAction(message); + + private static void Log(UpdateOperation operation) + { + Log(operation.GetLogMessage()); + _updateOperations.Add(operation); + } + + private static void LogUpdateSummary(string outputFilePath = null) + { + LogSummary(_logAction); + + if (outputFilePath != null) + { + LogUpdateSummaryToFile(outputFilePath); + } + } + + private static void LogSummary(Action logAction, bool includeUrl = false) + { + var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); + var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); + + if (completedUpdates.Any() || skippedUpdates.Any()) + { + logAction($"# Package update summary"); + } + + if (completedUpdates.Any()) + { + var updatedPackages = completedUpdates + .Select(o => (o.PackageName, o.UpdatedVersion, o.FeedUri)) + .Distinct() + .ToArray(); + + logAction($"## Updated {updatedPackages.Length} packages:"); + + foreach (var p in updatedPackages) + { + var logMessage = $"[{p.PackageName}] to [{p.UpdatedVersion}]"; + var url = includeUrl ? GetPackageUrl(p.PackageName, p.UpdatedVersion, p.FeedUri) : default; + + logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); + } + } + + if (skippedUpdates.Any()) + { + var skippedPackages = skippedUpdates + .Select(o => (o.PackageName, o.PreviousVersion, o.FeedUri)) + .Distinct() + .ToArray(); + + logAction($"## Skipped {skippedPackages.Length} packages:"); + + foreach (var p in skippedPackages) + { + var logMessage = $"[{p.PackageName}] is at version [{p.PreviousVersion}]"; + var url = includeUrl ? GetPackageUrl(p.PackageName, p.PreviousVersion, p.FeedUri) : default; + + logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); + } + } + } + + private static string GetPackageUrl(string packageId, NuGetVersion version, Uri feedUri) + { + if (feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) + { + return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; + } + + var pattern = LegacyAzureArtifactsFeedUrlPattern; + + if (feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com")) + { + pattern = AzureArtifactsFeedUrlPattern; + } + + var match = Regex.Match(feedUri.AbsoluteUri, pattern); + + if (match.Length > 0) + { + string accountName = match.Groups["account"].Value; + string feedName = match.Groups["feed"].Value; + + return $"https://dev.azure.com/{accountName}/_packaging?_a=package&feed={feedName}&package={packageId}&version={version.ToFullString()}&protocolType=NuGet"; + } + + return default; + } + } +} diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs index 9cd236f..a3ae75a 100644 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -1,8 +1,6 @@ #if !UAP using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -18,15 +16,15 @@ private static void LogUpdateSummaryToFile(string outputFilePath) { try { - using (var file = File.OpenWrite(outputFilePath)) + using (var file = File.Open(outputFilePath, FileMode.Open | FileMode.Truncate, FileAccess.Write)) using (var writer = new StreamWriter(file)) { LogSummary(line => writer.WriteLine(line), includeUrl: true); } } - catch(Exception ex) + catch (Exception ex) { - _logAction($"Failed to write to {outputFilePath}. Reason : {ex.Message}"); + Log($"Failed to write to {outputFilePath}. Reason : {ex.Message}"); } } @@ -43,33 +41,45 @@ private static async Task GetFiles(CancellationToken ct, string path, return Directory.GetFiles(path, filter, SearchOption.AllDirectories); } + private static bool UpdateProjectReferenceVersions(string packageName, FeedNuGetVersion version, XmlDocument document, string documentPath) + { + var modified = false; + + var nsmgr = new XmlNamespaceManager(document.NameTable); + nsmgr.AddNamespace("d", MsBuildNamespace); + modified |= UpdateProjectReferenceVersions(packageName, version, document, documentPath, nsmgr); + + var nsmgr2 = new XmlNamespaceManager(document.NameTable); + nsmgr2.AddNamespace("d", ""); + modified |= UpdateProjectReferenceVersions(packageName, version, document, documentPath, nsmgr2); + + return modified; + } private static bool UpdateProjectReferenceVersions( string packageName, - NuGetVersion version, - bool modified, + FeedNuGetVersion version, XmlDocument doc, string documentPath, - XmlNamespaceManager namespaceManager, - Uri feedUri + XmlNamespaceManager namespaceManager ) { + var modified = false; foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", namespaceManager)) { if (packageReference.HasAttribute("Version")) { var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); if (operation.ShouldProceed) { - packageReference.SetAttribute("Version", version.ToString()); + packageReference.SetAttribute("Version", version.Version.ToString()); modified = true; } - _logAction(operation.GetLogMessage()); - _updateOperations.Add(operation); + Log(operation); } else { @@ -79,16 +89,15 @@ Uri feedUri { var currentVersion = new NuGetVersion(node.InnerText); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); if (operation.ShouldProceed) { - node.InnerText = version.ToString(); + node.InnerText = version.Version.ToString(); modified = true; } - _logAction(operation.GetLogMessage()); - _updateOperations.Add(operation); + Log(operation); } } } diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs index eb9ebb6..936c748 100644 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ b/NuGetUpdater/NuGetUpdater.UAP.cs @@ -1,8 +1,6 @@ #if UAP using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Nuget.Updater.Entities; @@ -41,14 +39,16 @@ private static async Task GetFiles(CancellationToken ct, string path, return files .Select(f => { - _logAction($"Found {f.Path}"); + Log($"Found {f.Path}"); return f.Path; }) .ToArray(); } - private static bool UpdateProjectReferenceVersions(string packageName, NuGetVersion version, bool modified, XmlDocument doc, string documentPath, Uri feedUri) + private static bool UpdateProjectReferenceVersions(string packageName, FeedNuGetVersion version, XmlDocument doc, string documentPath) { + var modified = false; + var packageReferences = doc.GetElementsByTagName("PackageReference") .Cast() .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageName); @@ -61,16 +61,15 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(versionAttribute); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); if (operation.ShouldProceed) { - packageReference.SetAttribute("Version", version.ToString()); + packageReference.SetAttribute("Version", version.Version.ToString()); modified = true; } - _logAction(operation.GetLogMessage()); - _updateOperations.Add(operation); + Log(operation); } else { @@ -80,23 +79,20 @@ private static bool UpdateProjectReferenceVersions(string packageName, NuGetVers { var currentVersion = new NuGetVersion(node.InnerText); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath, feedUri); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); if (operation.ShouldProceed) { - node.InnerText = version.ToString(); + node.InnerText = version.Version.ToString(); modified = true; } - _logAction(operation.GetLogMessage()); - _updateOperations.Add(operation); + Log(operation); } } } return modified; - - return false; } private static async Task GetDocument(CancellationToken ct, string path) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index 219b664..5702d95 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -5,10 +5,8 @@ using System.Threading; using System.Threading.Tasks; using Nuget.Updater.Entities; -using NuGet.Common; +using Nuget.Updater.Extensions; using NuGet.Configuration; -using NuGet.Protocol; -using NuGet.Protocol.Core.Types; using NuGet.Versioning; #if UAP @@ -25,14 +23,9 @@ namespace Nuget.Updater public partial class NuGetUpdater { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; - private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; - private static Action _logAction; private static bool _allowDowngrade; - private static readonly List _updateOperations = new List(); - public static bool Update( string solutionRoot, string sourceFeed, @@ -89,10 +82,11 @@ public static async Task UpdateAsync( { _updateOperations.Clear(); + _logAction = logAction #if DEBUG - _logAction = logAction ?? Console.WriteLine; + ?? Console.WriteLine; #else - _logAction = logAction ?? new Action(_ => { }); + ?? new Action(_ => { }); #endif _allowDowngrade = allowDowngrade; @@ -105,121 +99,35 @@ public static async Task UpdateAsync( return true; } - private static void LogUpdateSummary(string outputFilePath = null) + private static async Task GetPackages(CancellationToken ct, string feed, string PAT, bool includNuGetOrg) { - LogSummary(_logAction); - - if (outputFilePath != null) + var privateSource = new PackageSource(feed, "Feed") { - LogUpdateSummaryToFile(outputFilePath); - } - } + Credentials = PackageSourceCredential.FromUserInput("Feed", "user", PAT, false) + }; - private static void LogSummary(Action logAction, bool includeUrl = false) - { - var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); - var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); + //Using search instead of list because the latter forces the v2 api + var packages = await privateSource.SearchPackages(ct, Log); - if(completedUpdates.Any() || skippedUpdates.Any()) + if (includNuGetOrg) { - logAction($"# Package update summary"); - } + var publicSource = new PackageSource("https://api.nuget.org/v3/index.json"); - if (completedUpdates.Any()) - { - var updatedPackages = completedUpdates - .Select(o => (o.PackageName, o.UpdatedVersion, o.FeedUri)) - .Distinct() + //Using search instead of list because the latter forces the v2 api + packages = packages + .Concat(await publicSource.SearchPackages(ct, Log, searchTerm: "owner:nventive")) + .GroupBy(p => p.PackageId) + .Select(g => new NuGetPackage(g.Key, g.ToArray())) .ToArray(); - - logAction($"## Updated {updatedPackages.Length} packages:"); - - foreach (var p in updatedPackages) - { - var logMessage = $"[{p.PackageName}] to [{p.UpdatedVersion}]"; - var url = includeUrl ? GetPackageUrl(p.PackageName, p.UpdatedVersion, p.FeedUri) : default; - - logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); - } - } - - if (skippedUpdates.Any()) - { - var skippedPackages = skippedUpdates - .Select(o => (o.PackageName, o.PreviousVersion, o.FeedUri)) - .Distinct() - .ToArray(); - - logAction($"## Skipped {skippedPackages.Length} packages:"); - - foreach (var p in skippedPackages) - { - var logMessage = $"[{p.PackageName}] is at version [{p.PreviousVersion}]"; - var url = includeUrl ? GetPackageUrl(p.PackageName, p.PreviousVersion, p.FeedUri) : default; - - logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); - } - } - } - - private static async Task<(Uri, IEnumerable)[]> GetPackages(CancellationToken ct, string feed, string PAT, bool includNuGetOrg) - { - var packages = new List<(Uri, IEnumerable)>(); - - packages.Add(await GetFeedPackages(ct, feed, PAT)); - - if (includNuGetOrg) { - packages.Add(await GetNuGetOrgPackages(ct)); } - return packages.ToArray(); - } - - private static async Task<(Uri sourceUri, IEnumerable packages)> GetNuGetOrgPackages(CancellationToken ct) - { - var settings = Settings.LoadDefaultSettings(null); - var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); - - var source = new PackageSource("https://api.nuget.org/v3/index.json"); - var repository = repositoryProvider.CreateRepository(source); - - _logAction($"Pulling NuGet packages from {source.SourceUri}"); - - var searchResource = repository.GetResource(); - - var packages = (await searchResource.SearchAsync("owner:nventive", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct)).ToArray(); - - _logAction($"FOund {packages.Length} packages"); - - return (sourceUri: source.SourceUri, packages); - } - - private static async Task<(Uri sourceUri, IEnumerable packages)> GetFeedPackages(CancellationToken ct, string feed, string PAT) - { - var settings = Settings.LoadDefaultSettings(null); - var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); - - var source = new PackageSource(feed, "Feed") - { - Credentials = PackageSourceCredential.FromUserInput("Feed", "user", PAT, false) - }; - var repository = repositoryProvider.CreateRepository(source); - - var searchResource = repository.GetResource(); - - _logAction($"Pulling NuGet packages from {source.SourceUri}"); - - var packages = (await searchResource.SearchAsync("", new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), 0, 1000, new NullLogger(), ct)).ToArray(); - - _logAction($"FOund {packages.Length} packages"); - - return (sourceUri: source.SourceUri, packages); + return packages; } private static async Task UpdatePackages( CancellationToken ct, string solutionRoot, - (Uri sourceUri, IEnumerable packages)[] sources, + NuGetPackage[] packages, string targetVersion, string excludeTag, bool strict, @@ -245,50 +153,47 @@ private static async Task UpdatePackages( { var paths = await GetTargetFiles(ct, solutionRoot, UpdateTarget.PackageReference); - foreach(var p in paths) + foreach (var p in paths) { var document = await GetDocument(ct, p); originalProjectFiles.Add(p, document); } } - foreach (var source in sources) + foreach (var package in packages) { - foreach (var package in source.packages) - { - var packageId = package.Identity.Id; + var packageId = package.PackageId; - if (ignoredPackages != null && ignoredPackages.Contains(packageId)) - { - continue; - } + if (ignoredPackages != null && ignoredPackages.Contains(packageId)) + { + continue; + } - if (packagesToUpdate != null && !packagesToUpdate.Contains(packageId)) - { - continue; - } + if ((packagesToUpdate?.Any() ?? false) && !packagesToUpdate.Contains(packageId)) + { + continue; + } - var latestVersion = await GetLatestVersion(ct, package, targetVersion, excludeTag, strict, keepLatestDev); + var latest = await package.GetLatestVersion(ct, targetVersion, excludeTag, strict, keepLatestDev); - if (latestVersion == null) - { - continue; - } + if (latest == null) + { + continue; + } - _logAction($"Latest {targetVersion} version for [{packageId}] is [{latestVersion}]"); + Log($"Latest {targetVersion} version for [{packageId}] is [{latest.Version}]"); - if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) - { - await UpdateNuSpecs(ct, packageId, latestVersion, originalNuSpecFiles, source.sourceUri); - } - if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) - { - await UpdateProjectJson(ct, packageId, latestVersion, originalJsonFiles, source.sourceUri); - } - if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) - { - await UpdateProjects(ct, packageId, latestVersion, originalProjectFiles, source.sourceUri); - } + if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) + { + await UpdateNuSpecs(ct, packageId, latest, originalNuSpecFiles); + } + if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) + { + await UpdateProjectJson(ct, packageId, latest, originalJsonFiles); + } + if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) + { + await UpdateProjects(ct, packageId, latest, originalProjectFiles); } } } @@ -313,21 +218,21 @@ private static async Task GetTargetFiles(CancellationToken ct, string break; } - if(extensionFilter == null && nameFilter == null) + if (extensionFilter == null && nameFilter == null) { return new string[0]; } - _logAction($"Retrieving {nameFilter ?? extensionFilter} files"); + Log($"Retrieving {nameFilter ?? extensionFilter} files"); var files = await GetFiles(ct, solutionRootPath, extensionFilter, nameFilter); - _logAction($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); + Log($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); return files; } - private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] nuspecFiles, Uri feedUri) + private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, FeedNuGetVersion latestVersion, string[] nuspecFiles) { foreach (var nuspecFile in nuspecFiles) { @@ -355,15 +260,14 @@ private static async Task UpdateNuSpecs(CancellationToken ct, string packageName { var currentVersion = new NuGetVersion(versionNodeValue); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, nuspecFile, feedUri); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, nuspecFile); if (operation.ShouldProceed) { node.SetAttribute("version", latestVersion.ToString()); } - _logAction(operation.GetLogMessage()); - _updateOperations.Add(operation); + Log(operation); } } @@ -374,7 +278,7 @@ private static async Task UpdateNuSpecs(CancellationToken ct, string packageName } } - private static async Task UpdateProjectJson(CancellationToken ct, string packageName, NuGetVersion latestVersion, string[] jsonFiles, Uri feedUri) + private static async Task UpdateProjectJson(CancellationToken ct, string packageName, FeedNuGetVersion latestVersion, string[] jsonFiles) { var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; var replaced = $@"""{packageName}"": ""{latestVersion}"""; @@ -389,7 +293,7 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package { var currentVersion = new NuGetVersion(match.Groups[1].Value); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, file, feedUri); + var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, file); if (operation.ShouldProceed) { @@ -403,109 +307,23 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package await SetFileContent(ct, file, newContent); } - _logAction(operation.GetLogMessage()); - _updateOperations.Add(operation); + Log(operation); } } } - private static async Task UpdateProjects(CancellationToken ct, string packageName, NuGetVersion latestVersion, Dictionary projectFiles, Uri feedUri) + private static async Task UpdateProjects(CancellationToken ct, string packageName, FeedNuGetVersion latestVersion, Dictionary projectFiles) { for (int i = 0; i < projectFiles.Count; i++) { - var modified = false; var path = projectFiles.ElementAt(i).Key; var document = projectFiles.ElementAt(i).Value; -#if UAP - modified = UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, feedUri); -#else - var nsmgr = new XmlNamespaceManager(document.NameTable); - nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr, feedUri); - - var nsmgr2 = new XmlNamespaceManager(document.NameTable); - nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(packageName, latestVersion, modified, document, path, nsmgr2, feedUri); -#endif - - if (modified) + if (UpdateProjectReferenceVersions(packageName, latestVersion, document, path)) { await SaveDocument(ct, document, path); } } } - - private static async Task GetLatestVersion(CancellationToken ct, IPackageSearchMetadata package, string targetVersion, string excludeTag, bool strict, IEnumerable keepLatestDev = null) - { - var versions = (await package.GetVersionsAsync()).OrderByDescending(v => v.Version); - - var specialVersion = (keepLatestDev?.Contains(package.Identity.Id, StringComparer.OrdinalIgnoreCase) ?? false) ? "dev" : targetVersion; - - if(specialVersion == "stable") - { - specialVersion = ""; - } - - return versions - .Where(v => IsMatchingSpecialVersion(specialVersion, v, strict) && !ContainsTag(excludeTag, v)) - .OrderByDescending(v => v.Version) - .FirstOrDefault() - ?.Version; - } - - private static bool ContainsTag(string tag, VersionInfo version) - { - if (tag?.Equals("") ?? true) - { - return false; - } - - return version?.Version?.ReleaseLabels?.Contains(tag) ?? false; - } - - private static bool IsMatchingSpecialVersion(string specialVersion, VersionInfo version, bool strict) - { - if (string.IsNullOrEmpty(specialVersion)) - { - return !version.Version?.ReleaseLabels?.Any() ?? true; - } - else - { - var releaseLabels = version.Version?.ReleaseLabels; - var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; - - return strict - ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" - : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" - } - } - - private static string GetPackageUrl(string packageId, NuGetVersion version, Uri feedUri) - { - if(feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) - { - return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; - } - - var pattern = LegacyAzureArtifactsFeedUrlPattern; - - if(feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com")) - { - pattern = AzureArtifactsFeedUrlPattern; - } - - var match = Regex.Match(feedUri.AbsoluteUri, pattern); - - if(match.Length > 0) - { - string accountName = match.Groups["account"].Value; - string feedName = match.Groups["feed"].Value; - - return $"https://dev.azure.com/{accountName}/_packaging?_a=package&feed={feedName}&package={packageId}&version={version.ToFullString()}&protocolType=NuGet"; - } - - return default; - } } } From 0c0b5bcc1e4591be50286290e8ab981a8cc16d49 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 18 Feb 2019 12:24:18 -0500 Subject: [PATCH 063/201] Fixed file opening --- NuGetUpdater/NuGetUpdater.Net.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs index a3ae75a..82920ae 100644 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ b/NuGetUpdater/NuGetUpdater.Net.cs @@ -16,7 +16,12 @@ private static void LogUpdateSummaryToFile(string outputFilePath) { try { - using (var file = File.Open(outputFilePath, FileMode.Open | FileMode.Truncate, FileAccess.Write)) + if(File.Exists(outputFilePath)) + { + File.WriteAllText(outputFilePath, ""); + } + + using (var file = File.OpenWrite(outputFilePath)) using (var writer = new StreamWriter(file)) { LogSummary(line => writer.WriteLine(line), includeUrl: true); From 6fc3527a9bf08c9495b3199caaf7017a16712ca9 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 19 Feb 2019 12:40:45 -0500 Subject: [PATCH 064/201] Fixed comparison between versions --- Entities/UpdateOperation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/UpdateOperation.cs b/Entities/UpdateOperation.cs index 4e723c7..40fbbb5 100644 --- a/Entities/UpdateOperation.cs +++ b/Entities/UpdateOperation.cs @@ -36,7 +36,7 @@ public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion public Uri FeedUri { get; } - public bool ShouldProceed => PreviousVersion < UpdatedVersion || (_isDowngradeAllowed && PreviousVersion.IsGreaterThan(UpdatedVersion)); + public bool ShouldProceed => UpdatedVersion.IsGreaterThan(PreviousVersion) || _isDowngradeAllowed; public bool IsLatestVersion => PreviousVersion == UpdatedVersion; From 6645ab7a7df35f6d6888a29a769b9187dea55f32 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 19 Feb 2019 12:49:22 -0500 Subject: [PATCH 065/201] Improved downgrade message --- Entities/UpdateOperation.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Entities/UpdateOperation.cs b/Entities/UpdateOperation.cs index 40fbbb5..1400533 100644 --- a/Entities/UpdateOperation.cs +++ b/Entities/UpdateOperation.cs @@ -44,7 +44,9 @@ public string GetLogMessage() { if (ShouldProceed) { - return $"Updating [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; + return UpdatedVersion.IsGreaterThan(PreviousVersion) + ? $"Updating [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]" + : $"Downgrading [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; } else { From e6dc83a12acae9a46112baf6bf0c5c504ae229ac Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 19 Feb 2019 14:09:00 -0500 Subject: [PATCH 066/201] Reorganized code --- .../NuGetUpdater.Log.cs => Entities/Logger.cs | 72 +++- Extensions/FeedNuGetVersionExtensions.cs | 35 ++ Extensions/NuGetPackageExtensions.cs | 43 +-- Extensions/ParametersExtension.cs | 27 ++ Extensions/XmlDocumentExtensions.Net.cs | 113 ++++++ Extensions/XmlDocumentExtensions.UAP.cs | 91 +++++ Extensions/XmlDocumentExtensions.cs | 52 +++ Helpers/FileHelper.Net.cs | 54 +++ Helpers/FileHelper.UAP.cs | 62 ++++ .../NuGetBranchSwitchExecution.cs | 0 {Tasks => Legacy}/NuGetBranchSwitchTask.cs | 0 NuGetUpdater/NuGetUpdater.Execution.cs | 96 ++++++ NuGetUpdater/NuGetUpdater.Net.cs | 141 -------- NuGetUpdater/NuGetUpdater.Parameters.cs | 74 ++++ NuGetUpdater/NuGetUpdater.UAP.cs | 124 ------- NuGetUpdater/NuGetUpdater.cs | 324 +++++++----------- 16 files changed, 787 insertions(+), 521 deletions(-) rename NuGetUpdater/NuGetUpdater.Log.cs => Entities/Logger.cs (58%) create mode 100644 Extensions/FeedNuGetVersionExtensions.cs create mode 100644 Extensions/ParametersExtension.cs create mode 100644 Extensions/XmlDocumentExtensions.Net.cs create mode 100644 Extensions/XmlDocumentExtensions.UAP.cs create mode 100644 Extensions/XmlDocumentExtensions.cs create mode 100644 Helpers/FileHelper.Net.cs create mode 100644 Helpers/FileHelper.UAP.cs rename NuGetBranchSwitchExecution.cs => Legacy/NuGetBranchSwitchExecution.cs (100%) rename {Tasks => Legacy}/NuGetBranchSwitchTask.cs (100%) create mode 100644 NuGetUpdater/NuGetUpdater.Execution.cs delete mode 100644 NuGetUpdater/NuGetUpdater.Net.cs create mode 100644 NuGetUpdater/NuGetUpdater.Parameters.cs delete mode 100644 NuGetUpdater/NuGetUpdater.UAP.cs diff --git a/NuGetUpdater/NuGetUpdater.Log.cs b/Entities/Logger.cs similarity index 58% rename from NuGetUpdater/NuGetUpdater.Log.cs rename to Entities/Logger.cs index aba933f..08363ac 100644 --- a/NuGetUpdater/NuGetUpdater.Log.cs +++ b/Entities/Logger.cs @@ -1,47 +1,82 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.RegularExpressions; -using Nuget.Updater.Entities; +using System.Threading.Tasks; +using Nuget.Updater.Helpers; using NuGet.Versioning; -namespace Nuget.Updater +namespace Nuget.Updater.Entities { - partial class NuGetUpdater + public class Logger { private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; - private static readonly List _updateOperations = new List(); + private readonly List _updateOperations = new List(); + private readonly Action _logAction; + private readonly string _summaryFilePath; - private static Action _logAction; + public Logger(Action logAction = null, string summaryFilePath = null) + { + _logAction = logAction +#if DEBUG + ?? Console.WriteLine; +#else + ?? new Action(_ => { }); +#endif + _summaryFilePath = summaryFilePath; + } + + public void Clear() => _updateOperations.Clear(); + + public void Write(string message) => _logAction(message); - private static void Log(string message) => _logAction(message); + public void Write(IEnumerable operations) + { + foreach (var o in operations) + { + Write(o); + } + } - private static void Log(UpdateOperation operation) + public void Write(UpdateOperation operation) { - Log(operation.GetLogMessage()); + Write(operation.GetLogMessage()); _updateOperations.Add(operation); } - private static void LogUpdateSummary(string outputFilePath = null) + public void WriteSummary() { - LogSummary(_logAction); + var summary = GetSummary().ToArray(); - if (outputFilePath != null) + foreach (var line in summary) { - LogUpdateSummaryToFile(outputFilePath); + Write(line); + } + + if (_summaryFilePath != null) + { + try + { + FileHelper.LogToFile(_summaryFilePath, summary); + } + catch (Exception ex) + { + Write($"Failed to write to {_summaryFilePath}. Reason : {ex.Message}"); + } } } - private static void LogSummary(Action logAction, bool includeUrl = false) + private IEnumerable GetSummary(bool includeUrl = false) { var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); if (completedUpdates.Any() || skippedUpdates.Any()) { - logAction($"# Package update summary"); + yield return $"# Package update summary"; } if (completedUpdates.Any()) @@ -51,14 +86,14 @@ private static void LogSummary(Action logAction, bool includeUrl = false .Distinct() .ToArray(); - logAction($"## Updated {updatedPackages.Length} packages:"); + yield return $"## Updated {updatedPackages.Length} packages:"; foreach (var p in updatedPackages) { var logMessage = $"[{p.PackageName}] to [{p.UpdatedVersion}]"; var url = includeUrl ? GetPackageUrl(p.PackageName, p.UpdatedVersion, p.FeedUri) : default; - logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); + yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; } } @@ -69,18 +104,17 @@ private static void LogSummary(Action logAction, bool includeUrl = false .Distinct() .ToArray(); - logAction($"## Skipped {skippedPackages.Length} packages:"); + yield return $"## Skipped {skippedPackages.Length} packages:"; foreach (var p in skippedPackages) { var logMessage = $"[{p.PackageName}] is at version [{p.PreviousVersion}]"; var url = includeUrl ? GetPackageUrl(p.PackageName, p.PreviousVersion, p.FeedUri) : default; - logAction(url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"); + yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; } } } - private static string GetPackageUrl(string packageId, NuGetVersion version, Uri feedUri) { if (feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) diff --git a/Extensions/FeedNuGetVersionExtensions.cs b/Extensions/FeedNuGetVersionExtensions.cs new file mode 100644 index 0000000..d7f0934 --- /dev/null +++ b/Extensions/FeedNuGetVersionExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Nuget.Updater.Entities; + +namespace Nuget.Updater.Extensions +{ + public static class FeedNuGetVersionExtensions + { + public static bool ContainsTag(this FeedNuGetVersion version, string tag) => + !string.IsNullOrEmpty(tag) + && (version?.Version?.ReleaseLabels?.Contains(tag) ?? false); + + public static bool IsMatchingSpecialVersion(this FeedNuGetVersion version, string specialVersion, bool strict) + { + var releaseLabels = version?.Version?.ReleaseLabels; + + if (string.IsNullOrEmpty(specialVersion)) + { + return !releaseLabels?.Any() ?? true; + } + else + { + var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; + + return strict + ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" + : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" + } + } + } +} diff --git a/Extensions/NuGetPackageExtensions.cs b/Extensions/NuGetPackageExtensions.cs index f99d484..9c0eec3 100644 --- a/Extensions/NuGetPackageExtensions.cs +++ b/Extensions/NuGetPackageExtensions.cs @@ -14,18 +14,12 @@ public static class NuGetPackageExtensions public static async Task GetLatestVersion( this NuGetPackage package, CancellationToken ct, - string targetVersion, - string excludeTag, - bool strict, - IEnumerable keepLatestDev = null + NuGetUpdater.Parameters parameters ) { - var versions = (await package.GetVersions(ct)).OrderByDescending(v => v.Version); - - - var specialVersion = targetVersion; + var specialVersion = parameters.TargetVersion; - if ((keepLatestDev?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? false)) + if (parameters.ShouldKeepPackageAtLatestDev(package.PackageId)) { specialVersion = "dev"; } @@ -35,8 +29,10 @@ public static async Task GetLatestVersion( specialVersion = ""; } + var versions = (await package.GetVersions(ct)).OrderByDescending(v => v.Version); + var version = versions - .Where(v => IsMatchingSpecialVersion(specialVersion, v.Version, strict) && !ContainsTag(excludeTag, v.Version)) + .Where(v => v.IsMatchingSpecialVersion(specialVersion, parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) .OrderByDescending(v => v.Version) .FirstOrDefault(); @@ -56,32 +52,5 @@ private static async Task> GetVersions(this NuGetP return versions; } - - private static bool ContainsTag(string tag, NuGetVersion version) - { - if (tag?.Equals("") ?? true) - { - return false; - } - - return version?.ReleaseLabels?.Contains(tag) ?? false; - } - - private static bool IsMatchingSpecialVersion(string specialVersion, NuGetVersion version, bool strict) - { - if (string.IsNullOrEmpty(specialVersion)) - { - return !version?.ReleaseLabels?.Any() ?? true; - } - else - { - var releaseLabels = version?.ReleaseLabels; - var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; - - return strict - ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" - : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" - } - } } } diff --git a/Extensions/ParametersExtension.cs b/Extensions/ParametersExtension.cs new file mode 100644 index 0000000..d248706 --- /dev/null +++ b/Extensions/ParametersExtension.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nuget.Updater.Entities; +using NuGet.Configuration; + +namespace Nuget.Updater.Extensions +{ + internal static class ParametersExtension + { + internal static bool HasUpdateTarget(this NuGetUpdater.Parameters parameters, UpdateTarget target) => (parameters.UpdateTarget & target) == target; + + internal static PackageSource GetFeedPackageSource(this NuGetUpdater.Parameters parameters) => new PackageSource(parameters.SourceFeed, "Feed") + { + Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false) + }; + + internal static bool ShouldUpdatePackage(this NuGetUpdater.Parameters parameters, NuGetPackage package) => + (parameters.PackagesToIgnore == null || !parameters.PackagesToIgnore.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase)) + && (parameters.PackagesToUpdate == null || parameters.PackagesToUpdate.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase)); + + internal static bool ShouldKeepPackageAtLatestDev(this NuGetUpdater.Parameters parameters, string packageId) => + parameters.PackagesToKeepAtLatestDev != null && parameters.PackagesToKeepAtLatestDev.Contains(packageId, StringComparer.OrdinalIgnoreCase); + } +} diff --git a/Extensions/XmlDocumentExtensions.Net.cs b/Extensions/XmlDocumentExtensions.Net.cs new file mode 100644 index 0000000..ad7c37e --- /dev/null +++ b/Extensions/XmlDocumentExtensions.Net.cs @@ -0,0 +1,113 @@ +#if !UAP +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using Nuget.Updater.Entities; +using NuGet.Versioning; + +namespace Nuget.Updater.Extensions +{ + partial class XmlDocumentExtensions + { + private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + + public static UpdateOperation[] UpdateProjectReferenceVersions( + this XmlDocument document, + string packageId, + FeedNuGetVersion version, + string path, + bool isDowngradeAllowed) + { + var operations = new List(); + + var nsmgr = new XmlNamespaceManager(document.NameTable); + nsmgr.AddNamespace("d", MsBuildNamespace); + operations.AddRange(document.UpdateProjectReferenceVersions(packageId, version, path, nsmgr, isDowngradeAllowed)); + + var nsmgr2 = new XmlNamespaceManager(document.NameTable); + nsmgr2.AddNamespace("d", ""); + operations.AddRange(document.UpdateProjectReferenceVersions(packageId, version, path, nsmgr2, isDowngradeAllowed)); + + return operations.ToArray(); + } + + private static UpdateOperation[] UpdateProjectReferenceVersions( + this XmlDocument document, + string packageId, + FeedNuGetVersion version, + string path, + XmlNamespaceManager namespaceManager, + bool isDowngradeAllowed + ) + { + var operations = new List(); + + foreach (XmlElement packageReference in document.SelectNodes($"//d:PackageReference[@Include='{packageId}']", namespaceManager)) + { + if (packageReference.HasAttribute("Version")) + { + var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if (operation.ShouldProceed) + { + packageReference.SetAttribute("Version", version.Version.ToString()); + } + + operations.Add(operation); + } + else + { + var node = packageReference.SelectSingleNode("d:Version", namespaceManager); + + if (node != null) + { + var currentVersion = new NuGetVersion(node.InnerText); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if (operation.ShouldProceed) + { + node.InnerText = version.Version.ToString(); + } + + operations.Add(operation); + } + } + } + + return operations.ToArray(); + } + + private static IEnumerable GetElements(this XmlDocument document, string xpath) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace("x", document.DocumentElement.NamespaceURI); + + return document + .SelectNodes(xpath, namespaceManager) + .OfType(); + } + + public static async Task> GetDocument(this string path, CancellationToken ct) + { + var document = new XmlDocument() + { + PreserveWhitespace = true + }; + + document.Load(path); + + return new KeyValuePair(path, document); + } + + public static async Task Save(this XmlDocument document, CancellationToken ct, string path) + { + document.Save(path); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/XmlDocumentExtensions.UAP.cs b/Extensions/XmlDocumentExtensions.UAP.cs new file mode 100644 index 0000000..e1b65ad --- /dev/null +++ b/Extensions/XmlDocumentExtensions.UAP.cs @@ -0,0 +1,91 @@ +#if UAP +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using Nuget.Updater.Entities; +using NuGet.Versioning; +using Windows.Data.Xml.Dom; +using Windows.Storage; +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; + +namespace Nuget.Updater.Extensions +{ + partial class XmlDocumentExtensions + { + public static UpdateOperation[] UpdateProjectReferenceVersions( + this XmlDocument document, + string packageId, + FeedNuGetVersion version, + string path, + bool isDowngradeAllowed + ) + { + var operations = new List(); + + var packageReferences = document.GetElementsByTagName("PackageReference") + .Cast() + .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageId); + + foreach (var packageReference in packageReferences) + { + string versionAttribute = packageReference.GetAttribute("Version"); + + if (versionAttribute != null && versionAttribute != "") + { + var currentVersion = new NuGetVersion(versionAttribute); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if (operation.ShouldProceed) + { + packageReference.SetAttribute("Version", version.Version.ToString()); + } + + operations.Add(operation); + } + else + { + var node = packageReference.GetElementsByTagName("Version").SingleOrDefault(); + + if (node != null) + { + var currentVersion = new NuGetVersion(node.InnerText); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if (operation.ShouldProceed) + { + node.InnerText = version.Version.ToString(); + } + + operations.Add(operation); + } + } + } + + return operations.ToArray(); + } + + private static IEnumerable GetElements(this XmlDocument document, string xpath) => document + .SelectNodes(xpath) + .OfType(); + + public static async Task> GetDocument(this string path, CancellationToken ct) + { + var document = await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path), new XmlLoadSettings { ElementContentWhiteSpace = true }); + + return new KeyValuePair(path, document); + } + + public static async Task Save(this XmlDocument document, CancellationToken ct, string path) + { + await document.SaveToFileAsync(await StorageFile.GetFileFromPathAsync(path)); + } + } +} +#endif \ No newline at end of file diff --git a/Extensions/XmlDocumentExtensions.cs b/Extensions/XmlDocumentExtensions.cs new file mode 100644 index 0000000..def3c42 --- /dev/null +++ b/Extensions/XmlDocumentExtensions.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nuget.Updater.Entities; +using NuGet.Versioning; + +#if UAP +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +#else +using XmlDocument = System.Xml.XmlDocument; +#endif + +namespace Nuget.Updater.Extensions +{ + public static partial class XmlDocumentExtensions + { + public static UpdateOperation[] UpdateNuSpecVersions( + this XmlDocument document, + string packageId, + FeedNuGetVersion version, + string path, + bool isDowngradeAllowed + ) + { + var operations = new List(); + + foreach (var node in document.GetElements($"//x:dependency[@id='{packageId}']")) + { + var versionNodeValue = node.GetAttribute("version"); + + // only nodes with explicit version, skip expansion. + if (!versionNodeValue.Contains("{")) + { + var currentVersion = new NuGetVersion(versionNodeValue); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if (operation.ShouldProceed) + { + node.SetAttribute("version", version.Version.ToString()); + } + + operations.Add(operation); + } + } + + return operations.ToArray(); + } + + + } +} \ No newline at end of file diff --git a/Helpers/FileHelper.Net.cs b/Helpers/FileHelper.Net.cs new file mode 100644 index 0000000..bb0f090 --- /dev/null +++ b/Helpers/FileHelper.Net.cs @@ -0,0 +1,54 @@ +#if !UAP +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Nuget.Updater.Helpers +{ + public static class FileHelper + { + public static void LogToFile(string outputFilePath, IEnumerable log) + { + if (File.Exists(outputFilePath)) + { + File.WriteAllText(outputFilePath, ""); + } + + using (var file = File.OpenWrite(outputFilePath)) + using (var writer = new StreamWriter(file)) + { + foreach (var line in log) + { + writer.WriteLine(line); + } + } + } + + public static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) + { + var filter = extensionFilter != null + ? "*" + extensionFilter + : null; + + if (nameFilter != null && filter == null) + { + filter = nameFilter; + } + + return Directory.GetFiles(path, filter, SearchOption.AllDirectories); + } + + public static async Task ReadFileContent(CancellationToken ct, string path) + { + return File.ReadAllText(path); + } + + public static async Task SetFileContent(CancellationToken ct, string path, string content) + { + File.WriteAllText(path, content, Encoding.UTF8); + } + } +} +#endif \ No newline at end of file diff --git a/Helpers/FileHelper.UAP.cs b/Helpers/FileHelper.UAP.cs new file mode 100644 index 0000000..ef287c1 --- /dev/null +++ b/Helpers/FileHelper.UAP.cs @@ -0,0 +1,62 @@ +#if UAP +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Search; + +namespace Nuget.Updater.Helpers +{ + public static class FileHelper + { + public static void LogToFile(string outputFilePath, IEnumerable log) + { + } + + public static async Task GetFiles( + CancellationToken ct, + string path, + string extensionFilter = null, + string nameFilter = null + ) + { + var folder = await StorageFolder.GetFolderFromPathAsync(path); + + var searchFilter = extensionFilter == null + ? $"filename:\"{nameFilter}\"" + : $"extension:{extensionFilter}"; + + var queryOptions = new QueryOptions + { + UserSearchFilter = searchFilter, + IndexerOption = IndexerOption.UseIndexerWhenAvailable, + FolderDepth = FolderDepth.Deep + }; + + var query = folder.CreateFileQueryWithOptions(queryOptions); + + var files = await query.GetFilesAsync(); + + return files.Select(f => f.Path).ToArray(); + } + + public static async Task ReadFileContent(CancellationToken ct, string path) + { + var file = await StorageFile.GetFileFromPathAsync(path); + var lines = await FileIO.ReadLinesAsync(file); + string content = string.Join(Environment.NewLine, lines); + + return content; + } + + public static async Task SetFileContent(CancellationToken ct, string path, string content) + { + var file = await StorageFile.GetFileFromPathAsync(path); + await FileIO.WriteTextAsync(file, content); + } + } +} +#endif \ No newline at end of file diff --git a/NuGetBranchSwitchExecution.cs b/Legacy/NuGetBranchSwitchExecution.cs similarity index 100% rename from NuGetBranchSwitchExecution.cs rename to Legacy/NuGetBranchSwitchExecution.cs diff --git a/Tasks/NuGetBranchSwitchTask.cs b/Legacy/NuGetBranchSwitchTask.cs similarity index 100% rename from Tasks/NuGetBranchSwitchTask.cs rename to Legacy/NuGetBranchSwitchTask.cs diff --git a/NuGetUpdater/NuGetUpdater.Execution.cs b/NuGetUpdater/NuGetUpdater.Execution.cs new file mode 100644 index 0000000..fa32f69 --- /dev/null +++ b/NuGetUpdater/NuGetUpdater.Execution.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nuget.Updater.Entities; + +namespace Nuget.Updater +{ + partial class NuGetUpdater + { + public static bool Update( + string solutionRoot, + string sourceFeed, + string targetVersion, + string excludeTag = "", + string PAT = "", + bool includeNuGetOrg = true, + bool allowDowngrade = false, + bool strict = true, + IEnumerable keepLatestDev = null, + IEnumerable ignorePackages = null, + IEnumerable updatePackages = null, + UpdateTarget target = UpdateTarget.All, + Action logAction = null, + string summaryOutputFilePath = null + ) + { + return UpdateAsync( + CancellationToken.None, + solutionRoot, + sourceFeed, + targetVersion, + excludeTag, + PAT, + includeNuGetOrg, + allowDowngrade, + strict, + keepLatestDev, + ignorePackages, + updatePackages, + target, + logAction, + summaryOutputFilePath + ).Result; + } + + public static async Task UpdateAsync( + CancellationToken ct, + string solutionRoot, + string sourceFeed, + string targetVersion, + string excludeTag = "", + string feedPat = "", + bool includeNuGetOrg = true, + bool isDowngradeAllowed = false, + bool strict = true, + IEnumerable packagesTokeepAtLatestDev = null, + IEnumerable packagesToIgnore = null, + IEnumerable packagesToUpdate = null, + UpdateTarget updateTarget = UpdateTarget.All, + Action logAction = null, + string summaryOutputFilePath = null + ) + { + var parameters = new Parameters + { + SolutionRoot = solutionRoot, + SourceFeed = sourceFeed, + SourceFeedPersonalAccessToken = feedPat, + TargetVersion = targetVersion, + Strict = strict, + TagToExclude = excludeTag, + UpdateTarget = updateTarget, + IncludeNuGetOrg = includeNuGetOrg, + IsDowngradeAllowed = isDowngradeAllowed, + PackagesToKeepAtLatestDev = packagesTokeepAtLatestDev, + PackagesToIgnore = packagesToIgnore, + PackagesToUpdate = packagesToUpdate, + }; + + var log = new Logger(logAction, summaryOutputFilePath); + + return await UpdateAsync(ct, parameters, log); + } + + public static async Task UpdateAsync( + CancellationToken ct, + Parameters parameters, + Logger log + ) + { + var updater = new NuGetUpdater(parameters, log); + return await updater.UpdatePackages(ct); + } + } +} diff --git a/NuGetUpdater/NuGetUpdater.Net.cs b/NuGetUpdater/NuGetUpdater.Net.cs deleted file mode 100644 index 82920ae..0000000 --- a/NuGetUpdater/NuGetUpdater.Net.cs +++ /dev/null @@ -1,141 +0,0 @@ -#if !UAP -using System; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Nuget.Updater.Entities; -using NuGet.Versioning; - -namespace Nuget.Updater -{ - public partial class NuGetUpdater - { - private static void LogUpdateSummaryToFile(string outputFilePath) - { - try - { - if(File.Exists(outputFilePath)) - { - File.WriteAllText(outputFilePath, ""); - } - - using (var file = File.OpenWrite(outputFilePath)) - using (var writer = new StreamWriter(file)) - { - LogSummary(line => writer.WriteLine(line), includeUrl: true); - } - } - catch (Exception ex) - { - Log($"Failed to write to {outputFilePath}. Reason : {ex.Message}"); - } - } - - private static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) - { - var filter = extensionFilter != null - ? "*" + extensionFilter - : null; - - if (nameFilter != null && filter == null) - { - filter = nameFilter; - } - - return Directory.GetFiles(path, filter, SearchOption.AllDirectories); - } - private static bool UpdateProjectReferenceVersions(string packageName, FeedNuGetVersion version, XmlDocument document, string documentPath) - { - var modified = false; - - var nsmgr = new XmlNamespaceManager(document.NameTable); - nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(packageName, version, document, documentPath, nsmgr); - - var nsmgr2 = new XmlNamespaceManager(document.NameTable); - nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(packageName, version, document, documentPath, nsmgr2); - - return modified; - } - - private static bool UpdateProjectReferenceVersions( - string packageName, - FeedNuGetVersion version, - XmlDocument doc, - string documentPath, - XmlNamespaceManager namespaceManager - ) - { - var modified = false; - foreach (XmlElement packageReference in doc.SelectNodes($"//d:PackageReference[@Include='{packageName}']", namespaceManager)) - { - if (packageReference.HasAttribute("Version")) - { - var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); - - if (operation.ShouldProceed) - { - packageReference.SetAttribute("Version", version.Version.ToString()); - modified = true; - } - - Log(operation); - } - else - { - var node = packageReference.SelectSingleNode("d:Version", namespaceManager); - - if (node != null) - { - var currentVersion = new NuGetVersion(node.InnerText); - - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); - - if (operation.ShouldProceed) - { - node.InnerText = version.Version.ToString(); - modified = true; - } - - Log(operation); - } - } - } - - return modified; - } - - private static async Task GetDocument(CancellationToken ct, string path) - { - var document = new XmlDocument() - { - PreserveWhitespace = true - }; - - document.Load(path); - - return document; - } - - private static async Task SaveDocument(CancellationToken ct, XmlDocument document, string path) - { - document.Save(path); - } - - private static async Task ReadFileContent(CancellationToken ct, string path) - { - return File.ReadAllText(path); - } - - private static async Task SetFileContent(CancellationToken ct, string path, string content) - { - File.WriteAllText(path, content, Encoding.UTF8); - } - } -} -#endif diff --git a/NuGetUpdater/NuGetUpdater.Parameters.cs b/NuGetUpdater/NuGetUpdater.Parameters.cs new file mode 100644 index 0000000..a328060 --- /dev/null +++ b/NuGetUpdater/NuGetUpdater.Parameters.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Nuget.Updater.Entities; +using NuGet.Configuration; + +namespace Nuget.Updater +{ + partial class NuGetUpdater + { + public class Parameters + { + /// + /// The location of the solution to update. + /// + public string SolutionRoot { get; set; } + + /// + /// The URL of the private feed to use. + /// + public string SourceFeed { get; set; } + + /// + /// The Personal Access Token to use to access the private feed. + /// + public string SourceFeedPersonalAccessToken { get; set; } + + /// + /// The target version for the update (stable, dev, beta, etc.) + /// + public string TargetVersion { get; set; } + + /// + /// Whether it should exactly match the target version. + /// + public bool Strict { get; set; } + + /// + /// A specific tag to exclude when looking for versions. + /// + public string TagToExclude { get; set; } + + /// + /// Whether to include packages from NuGet.org + /// + public bool IncludeNuGetOrg { get; set; } + + /// + /// Whether the packages can be downgraded if the version found is lower. + /// + public bool IsDowngradeAllowed { get; set; } + + /// + /// The type of files with NuGet references to update. + /// + public UpdateTarget UpdateTarget { get; set; } + + /// + /// A list of packages to keep at latest dev. + /// + public IEnumerable PackagesToKeepAtLatestDev { get; set; } + + /// + /// A list of packages to ignore. + /// + public IEnumerable PackagesToIgnore { get; set; } + + /// + /// A list of packages to update. Will update all packages found if nothing is specified. + /// + public IEnumerable PackagesToUpdate { get; set; } + } + } +} diff --git a/NuGetUpdater/NuGetUpdater.UAP.cs b/NuGetUpdater/NuGetUpdater.UAP.cs deleted file mode 100644 index 936c748..0000000 --- a/NuGetUpdater/NuGetUpdater.UAP.cs +++ /dev/null @@ -1,124 +0,0 @@ -#if UAP -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Nuget.Updater.Entities; -using NuGet.Versioning; -using Windows.Data.Xml.Dom; -using Windows.Storage; -using Windows.Storage.Search; - -namespace Nuget.Updater -{ - partial class NuGetUpdater - { - private static void LogUpdateSummaryToFile(string path) - { - } - - private static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) - { - var folder = await StorageFolder.GetFolderFromPathAsync(path); - - var searchFilter = extensionFilter == null - ? $"filename:\"{nameFilter}\"" - : $"extension:{extensionFilter}"; - - var queryOptions = new QueryOptions - { - UserSearchFilter = searchFilter, - IndexerOption = IndexerOption.UseIndexerWhenAvailable, - FolderDepth = FolderDepth.Deep - }; - - var query = folder.CreateFileQueryWithOptions(queryOptions); - - var files = await query.GetFilesAsync(); - - return files - .Select(f => - { - Log($"Found {f.Path}"); - return f.Path; - }) - .ToArray(); - } - - private static bool UpdateProjectReferenceVersions(string packageName, FeedNuGetVersion version, XmlDocument doc, string documentPath) - { - var modified = false; - - var packageReferences = doc.GetElementsByTagName("PackageReference") - .Cast() - .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageName); - - foreach (var packageReference in packageReferences) - { - string versionAttribute = packageReference.GetAttribute("Version"); - - if (versionAttribute != null && versionAttribute != "") - { - var currentVersion = new NuGetVersion(versionAttribute); - - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); - - if (operation.ShouldProceed) - { - packageReference.SetAttribute("Version", version.Version.ToString()); - modified = true; - } - - Log(operation); - } - else - { - var node = packageReference.GetElementsByTagName("Version").SingleOrDefault(); - - if (node != null) - { - var currentVersion = new NuGetVersion(node.InnerText); - - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, version, documentPath); - - if (operation.ShouldProceed) - { - node.InnerText = version.Version.ToString(); - modified = true; - } - - Log(operation); - } - } - } - - return modified; - } - - private static async Task GetDocument(CancellationToken ct, string path) - { - return await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path), new XmlLoadSettings { ElementContentWhiteSpace = true }); - } - - private static async Task SaveDocument(CancellationToken ct, XmlDocument document, string path) - { - await document.SaveToFileAsync(await StorageFile.GetFileFromPathAsync(path)); - } - - private static async Task ReadFileContent(CancellationToken ct, string path) - { - var file = await StorageFile.GetFileFromPathAsync(path); - var lines = await FileIO.ReadLinesAsync(file); - string content = string.Join(Environment.NewLine, lines); - - return content; - } - - private static async Task SetFileContent(CancellationToken ct, string path, string content) - { - var file = await StorageFile.GetFileFromPathAsync(path); - await FileIO.WriteTextAsync(file, content); - } - } -} -#endif \ No newline at end of file diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index 5702d95..acb72e2 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -6,116 +6,88 @@ using System.Threading.Tasks; using Nuget.Updater.Entities; using Nuget.Updater.Extensions; +using Nuget.Updater.Helpers; using NuGet.Configuration; using NuGet.Versioning; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; -using XmlElement = System.Xml.XmlElement; #else using XmlDocument = System.Xml.XmlDocument; -using XmlElement = System.Xml.XmlElement; -using XmlNamespaceManager = System.Xml.XmlNamespaceManager; #endif namespace Nuget.Updater { public partial class NuGetUpdater { - private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - - private static bool _allowDowngrade; - - public static bool Update( - string solutionRoot, - string sourceFeed, - string targetVersion, - string excludeTag = "", - string PAT = "", - bool includeNuGetOrg = true, - bool allowDowngrade = false, - bool strict = true, - IEnumerable keepLatestDev = null, - IEnumerable ignorePackages = null, - IEnumerable updatePackages = null, - UpdateTarget target = UpdateTarget.All, - Action logAction = null, - string summaryOutputFilePath = null - ) + private static readonly PackageSource NuGetOrgSource = new PackageSource("https://api.nuget.org/v3/index.json"); + + private readonly Parameters _parameters; + private readonly Logger _log; + + private NuGetUpdater(Parameters parameters, Logger log) { - return UpdateAsync( - CancellationToken.None, - solutionRoot, - sourceFeed, - targetVersion, - excludeTag, - PAT, - includeNuGetOrg, - allowDowngrade, - strict, - keepLatestDev, - ignorePackages, - updatePackages, - target, - logAction, - summaryOutputFilePath - ).Result; + _parameters = parameters; + _log = log; } - public static async Task UpdateAsync( - CancellationToken ct, - string solutionRoot, - string sourceFeed, - string targetVersion, - string excludeTag = "", - string PAT = "", - bool includeNuGetOrg = true, - bool allowDowngrade = false, - bool strict = true, - IEnumerable keepLatestDev = null, - IEnumerable ignorePackages = null, - IEnumerable updatePackages = null, - UpdateTarget target = UpdateTarget.All, - Action logAction = null, - string summaryOutputFilePath = null - ) + private async Task UpdatePackages(CancellationToken ct) { - _updateOperations.Clear(); + _log.Clear(); - _logAction = logAction -#if DEBUG - ?? Console.WriteLine; -#else - ?? new Action(_ => { }); -#endif - _allowDowngrade = allowDowngrade; + var packages = await GetPackages(ct); + var targetFiles = await GetTargetFiles(ct); + + foreach(var package in packages.Where(p => _parameters.ShouldUpdatePackage(p))) + { + var latest = await package.GetLatestVersion(ct, _parameters); + + if (latest == null) + { + _log.Write($"Skipping [{package.PackageId}]: no {_parameters.TargetVersion} version found."); + continue; + } - var packages = await GetPackages(ct, sourceFeed, PAT, includeNuGetOrg); + _log.Write($"Latest {_parameters.TargetVersion} version for [{package.PackageId}] is [{latest.Version}]"); - await UpdatePackages(ct, solutionRoot, packages, targetVersion, excludeTag, strict, keepLatestDev, ignorePackages, updatePackages, target); + foreach(var files in targetFiles) + { + var updates = new UpdateOperation[0]; - LogUpdateSummary(summaryOutputFilePath); + switch (files.Key) + { + case UpdateTarget.Nuspec: + updates = await UpdateNuSpecs(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); + break; + case UpdateTarget.ProjectJson: + updates = await UpdateProjectJson(ct, package.PackageId, latest, files.Value.Select(p => p.Key).ToArray(), _parameters.IsDowngradeAllowed); + break; + case UpdateTarget.PackageReference: + updates = await UpdateProjects(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); + break; + default: + break; + } + + _log.Write(updates); + } + } + + _log.WriteSummary(); return true; } - private static async Task GetPackages(CancellationToken ct, string feed, string PAT, bool includNuGetOrg) + private async Task GetPackages(CancellationToken ct) { - var privateSource = new PackageSource(feed, "Feed") - { - Credentials = PackageSourceCredential.FromUserInput("Feed", "user", PAT, false) - }; - //Using search instead of list because the latter forces the v2 api - var packages = await privateSource.SearchPackages(ct, Log); + var packages = await _parameters.GetFeedPackageSource().SearchPackages(ct, _log.Write); - if (includNuGetOrg) + if (_parameters.IncludeNuGetOrg) { - var publicSource = new PackageSource("https://api.nuget.org/v3/index.json"); - //Using search instead of list because the latter forces the v2 api packages = packages - .Concat(await publicSource.SearchPackages(ct, Log, searchTerm: "owner:nventive")) + .Concat(await NuGetOrgSource.SearchPackages(ct, _log.Write, searchTerm: "owner:nventive")) .GroupBy(p => p.PackageId) .Select(g => new NuGetPackage(g.Key, g.ToArray())) .ToArray(); @@ -124,81 +96,22 @@ private static async Task GetPackages(CancellationToken ct, stri return packages; } - private static async Task UpdatePackages( - CancellationToken ct, - string solutionRoot, - NuGetPackage[] packages, - string targetVersion, - string excludeTag, - bool strict, - IEnumerable keepLatestDev = null, - IEnumerable ignoredPackages = null, - IEnumerable packagesToUpdate = null, - UpdateTarget target = UpdateTarget.All - ) + private async Task>> GetTargetFiles(CancellationToken ct) { - var originalNuSpecFiles = new string[0]; - var originalJsonFiles = new string[0]; - var originalProjectFiles = new Dictionary(); + var targetFiles = new Dictionary>(); - if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) - { - originalNuSpecFiles = await GetTargetFiles(ct, solutionRoot, UpdateTarget.Nuspec); - } - if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) + foreach (var target in new[] { UpdateTarget.Nuspec, UpdateTarget.PackageReference, UpdateTarget.ProjectJson }) { - originalJsonFiles = await GetTargetFiles(ct, solutionRoot, UpdateTarget.ProjectJson); - } - if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) - { - var paths = await GetTargetFiles(ct, solutionRoot, UpdateTarget.PackageReference); - - foreach (var p in paths) + if (_parameters.HasUpdateTarget(target)) { - var document = await GetDocument(ct, p); - originalProjectFiles.Add(p, document); + targetFiles.Add(target, await GetFilesForTarget(ct, target)); } } - foreach (var package in packages) - { - var packageId = package.PackageId; - - if (ignoredPackages != null && ignoredPackages.Contains(packageId)) - { - continue; - } - - if ((packagesToUpdate?.Any() ?? false) && !packagesToUpdate.Contains(packageId)) - { - continue; - } - - var latest = await package.GetLatestVersion(ct, targetVersion, excludeTag, strict, keepLatestDev); - - if (latest == null) - { - continue; - } - - Log($"Latest {targetVersion} version for [{packageId}] is [{latest.Version}]"); - - if ((target & UpdateTarget.Nuspec) == UpdateTarget.Nuspec) - { - await UpdateNuSpecs(ct, packageId, latest, originalNuSpecFiles); - } - if ((target & UpdateTarget.ProjectJson) == UpdateTarget.ProjectJson) - { - await UpdateProjectJson(ct, packageId, latest, originalJsonFiles); - } - if ((target & UpdateTarget.PackageReference) == UpdateTarget.PackageReference) - { - await UpdateProjects(ct, packageId, latest, originalProjectFiles); - } - } + return targetFiles; } - private static async Task GetTargetFiles(CancellationToken ct, string solutionRootPath, UpdateTarget target) + private async Task> GetFilesForTarget(CancellationToken ct, UpdateTarget target) { string extensionFilter = null, nameFilter = null; @@ -213,87 +126,110 @@ private static async Task GetTargetFiles(CancellationToken ct, string case UpdateTarget.PackageReference: extensionFilter = ".csproj"; break; - case UpdateTarget.All: default: break; } if (extensionFilter == null && nameFilter == null) { - return new string[0]; + return new Dictionary(); } - Log($"Retrieving {nameFilter ?? extensionFilter} files"); + _log.Write($"Retrieving {nameFilter ?? extensionFilter} files"); - var files = await GetFiles(ct, solutionRootPath, extensionFilter, nameFilter); + var files = await FileHelper.GetFiles(ct, _parameters.SolutionRoot, extensionFilter, nameFilter); - Log($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); + _log.Write($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); - return files; + if(target == UpdateTarget.ProjectJson) + { + return files.ToDictionary(f => f, f => default(XmlDocument)); + } + + return (await Task.WhenAll(files.Select(f => f.GetDocument(ct)))) + .ToDictionary(p => p.Key, p => p.Value); } - private static async Task UpdateNuSpecs(CancellationToken ct, string packageName, FeedNuGetVersion latestVersion, string[] nuspecFiles) + private static async Task UpdateNuSpecs( + CancellationToken ct, + string packageId, + FeedNuGetVersion version, + Dictionary nuspecDocuments, + bool isDowngradeAllowed + ) { - foreach (var nuspecFile in nuspecFiles) - { - var doc = await GetDocument(ct, nuspecFile); + var operations = new List(); -#if UAP - var nodes = doc - .SelectNodes($"//x:dependency[@id='{packageName}']") - .OfType(); -#else - var mgr = new XmlNamespaceManager(doc.NameTable); - mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); + foreach (var document in nuspecDocuments) + { + var path = document.Key; + var xmlDocument = document.Value; - var nodes = doc - .SelectNodes($"//x:dependency[@id='{packageName}']", mgr) - .OfType(); -#endif + var updates = xmlDocument.UpdateNuSpecVersions(packageId, version, path, isDowngradeAllowed); - foreach (var node in nodes) + if (updates.Any(u => u.ShouldProceed)) { - var versionNodeValue = node.GetAttribute("version"); + await xmlDocument.Save(ct, path); + } - // only nodes with explicit version, skip expansion. - if (!versionNodeValue.Contains("{")) - { - var currentVersion = new NuGetVersion(versionNodeValue); + operations.AddRange(updates); + } - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, nuspecFile); + return operations.ToArray(); + } - if (operation.ShouldProceed) - { - node.SetAttribute("version", latestVersion.ToString()); - } + private static async Task UpdateProjects( + CancellationToken ct, + string packageId, + FeedNuGetVersion version, + Dictionary projectDocuments, + bool isDowngradeAllowed + ) + { + var operations = new List(); - Log(operation); - } - } + foreach (var document in projectDocuments) + { + var path = document.Key; + var xmlDocument = document.Value; - if (nodes.Any()) + var updates = xmlDocument.UpdateProjectReferenceVersions(packageId, version, path, isDowngradeAllowed); + + if (updates.Any(u => u.ShouldProceed)) { - await SaveDocument(ct, doc, nuspecFile); + await xmlDocument.Save(ct, path); } + + operations.AddRange(updates); } + + return operations.ToArray(); } - private static async Task UpdateProjectJson(CancellationToken ct, string packageName, FeedNuGetVersion latestVersion, string[] jsonFiles) + private static async Task UpdateProjectJson( + CancellationToken ct, + string packageName, + FeedNuGetVersion latestVersion, + string[] jsonFiles, + bool isDowngradeAllowed + ) { + var operations = new List(); + var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; var replaced = $@"""{packageName}"": ""{latestVersion}"""; for (int i = 0; i < jsonFiles.Length; i++) { var file = jsonFiles[i]; - var fileContent = await ReadFileContent(ct, file); + var fileContent = await FileHelper.ReadFileContent(ct, file); var match = Regex.Match(fileContent, originalMatch, RegexOptions.IgnoreCase); if (match?.Success ?? false) { var currentVersion = new NuGetVersion(match.Groups[1].Value); - var operation = new UpdateOperation(_allowDowngrade, packageName, currentVersion, latestVersion, file); + var operation = new UpdateOperation(isDowngradeAllowed, packageName, currentVersion, latestVersion, file); if (operation.ShouldProceed) { @@ -304,26 +240,14 @@ private static async Task UpdateProjectJson(CancellationToken ct, string package RegexOptions.IgnoreCase ); - await SetFileContent(ct, file, newContent); + await FileHelper.SetFileContent(ct, file, newContent); } - Log(operation); + operations.Add(operation); } } - } - private static async Task UpdateProjects(CancellationToken ct, string packageName, FeedNuGetVersion latestVersion, Dictionary projectFiles) - { - for (int i = 0; i < projectFiles.Count; i++) - { - var path = projectFiles.ElementAt(i).Key; - var document = projectFiles.ElementAt(i).Value; - - if (UpdateProjectReferenceVersions(packageName, latestVersion, document, path)) - { - await SaveDocument(ct, document, path); - } - } + return operations.ToArray(); } } } From 798a91b4026319b2837de21738d77363d540817d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 19 Feb 2019 15:12:34 -0500 Subject: [PATCH 067/201] Added new UseStableIfMoreRecent feature --- Extensions/NuGetPackageExtensions.cs | 18 ++++-- NuGetUpdater/NuGetUpdater.Execution.cs | 27 ++++++-- NuGetUpdater/NuGetUpdater.Parameters.cs | 5 ++ Tasks/NuGetUpdaterTask.cs | 86 +++++++++++-------------- 4 files changed, 79 insertions(+), 57 deletions(-) diff --git a/Extensions/NuGetPackageExtensions.cs b/Extensions/NuGetPackageExtensions.cs index 9c0eec3..52dedca 100644 --- a/Extensions/NuGetPackageExtensions.cs +++ b/Extensions/NuGetPackageExtensions.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Nuget.Updater.Entities; -using NuGet.Versioning; namespace Nuget.Updater.Extensions { @@ -36,6 +33,19 @@ NuGetUpdater.Parameters parameters .OrderByDescending(v => v.Version) .FirstOrDefault(); + if(version != null && parameters.UseStableIfMoreRecent && specialVersion != "") + { + var stableVersion = versions + .Where(v => v.IsMatchingSpecialVersion("", parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) + .OrderByDescending(v => v.Version) + .FirstOrDefault(); + + if (stableVersion?.Version.IsGreaterThan(version.Version) ?? false) + { + return stableVersion; + } + } + return version; } diff --git a/NuGetUpdater/NuGetUpdater.Execution.cs b/NuGetUpdater/NuGetUpdater.Execution.cs index fa32f69..25e4d91 100644 --- a/NuGetUpdater/NuGetUpdater.Execution.cs +++ b/NuGetUpdater/NuGetUpdater.Execution.cs @@ -22,7 +22,8 @@ public static bool Update( IEnumerable updatePackages = null, UpdateTarget target = UpdateTarget.All, Action logAction = null, - string summaryOutputFilePath = null + string summaryOutputFilePath = null, + bool useStableIfMoreRecent = false ) { return UpdateAsync( @@ -40,10 +41,17 @@ public static bool Update( updatePackages, target, logAction, - summaryOutputFilePath + summaryOutputFilePath, + useStableIfMoreRecent ).Result; } + public static bool Update( + Parameters parameters, + Action logAction = null, + string summaryOutputFilePath = null + ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logAction)).Result; + public static async Task UpdateAsync( CancellationToken ct, string solutionRoot, @@ -59,7 +67,8 @@ public static async Task UpdateAsync( IEnumerable packagesToUpdate = null, UpdateTarget updateTarget = UpdateTarget.All, Action logAction = null, - string summaryOutputFilePath = null + string summaryOutputFilePath = null, + bool useStableIfMoreRecent = false ) { var parameters = new Parameters @@ -76,13 +85,19 @@ public static async Task UpdateAsync( PackagesToKeepAtLatestDev = packagesTokeepAtLatestDev, PackagesToIgnore = packagesToIgnore, PackagesToUpdate = packagesToUpdate, + UseStableIfMoreRecent = useStableIfMoreRecent, }; - var log = new Logger(logAction, summaryOutputFilePath); - - return await UpdateAsync(ct, parameters, log); + return await UpdateAsync(ct, parameters, logAction, summaryOutputFilePath); } + public static Task UpdateAsync( + CancellationToken ct, + Parameters parameters, + Action logAction = null, + string summaryOutputFilePath = null + ) => UpdateAsync(ct, parameters, new Logger(logAction, summaryOutputFilePath)); + public static async Task UpdateAsync( CancellationToken ct, Parameters parameters, diff --git a/NuGetUpdater/NuGetUpdater.Parameters.cs b/NuGetUpdater/NuGetUpdater.Parameters.cs index a328060..195cbcb 100644 --- a/NuGetUpdater/NuGetUpdater.Parameters.cs +++ b/NuGetUpdater/NuGetUpdater.Parameters.cs @@ -69,6 +69,11 @@ public class Parameters /// A list of packages to update. Will update all packages found if nothing is specified. /// public IEnumerable PackagesToUpdate { get; set; } + + /// + /// Whether to use the stable version if a more recent version is available + /// + public bool UseStableIfMoreRecent { get; set; } } } } diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index d44c43a..1c48f1c 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -9,14 +9,6 @@ public class NuGetUpdaterTask : Task [Required] public string SpecialVersion { get; set; } - public string ExcludeTag { get; set; } - - public string IgnorePackages { get; set; } - - public string UpdatePackages { get; set; } - - public string UpdateSummaryFile { get; set; } - [Required] public string SolutionRoot { get; set; } @@ -29,56 +21,56 @@ public class NuGetUpdaterTask : Task [Required] public bool AllowDowngrade { get; set; } + public string ExcludeTag { get; set; } + + public string IgnorePackages { get; set; } + + public string UpdatePackages { get; set; } + + public string UpdateSummaryFile { get; set; } + + public bool UseStableIfMoreRecent { get; set; } + public override bool Execute() { - string[] packagesToIgnore; - - switch (IgnorePackages) + var parameters = new NuGetUpdater.Parameters { - case var p when p == null: - packagesToIgnore = new string[0]; - break; - case var p when p.Contains(";"): - packagesToIgnore = p.Split(';'); - break; - case var p when p != null: - packagesToIgnore = new[] { p }; - break; - default: - packagesToIgnore = null; - break; - } + SolutionRoot = SolutionRoot, + SourceFeed = NuGetFeed, + SourceFeedPersonalAccessToken = PAT, + TargetVersion = SpecialVersion, + IsDowngradeAllowed = AllowDowngrade, + PackagesToIgnore = GetPackages(IgnorePackages), + PackagesToUpdate = GetPackages(UpdatePackages), + TagToExclude = ExcludeTag, + UseStableIfMoreRecent = UseStableIfMoreRecent, + //Default values + PackagesToKeepAtLatestDev = new string[0], + IncludeNuGetOrg = true, + Strict = true, + UpdateTarget = UpdateTarget.All, + }; - string[] packagesToUpdate; + return NuGetUpdater.Update( + parameters, + message => Log.LogMessage(message), + UpdateSummaryFile + ); + } - switch (UpdatePackages) + private string[] GetPackages(string input) + { + switch (input) { case var p when p == null: - packagesToUpdate = new string[0]; - break; + return new string[0]; case var p when p.Contains(";"): - packagesToUpdate = p.Split(';'); - break; + return p.Split(';'); case var p when p != null: - packagesToUpdate = new[] { p }; - break; + return new[] { p }; default: - packagesToUpdate = null; - break; + return new string[0]; } - - return NuGetUpdater.Update( - SolutionRoot, - NuGetFeed, - SpecialVersion, - ExcludeTag, - PAT: PAT, - allowDowngrade: AllowDowngrade, - ignorePackages: packagesToIgnore, - updatePackages: packagesToUpdate, - logAction: message => Log.LogMessage(message), - summaryOutputFilePath: UpdateSummaryFile - ); } } } From 104be837ecdd7a6b697edd2a054fe1007d1d1423 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 19 Feb 2019 18:03:54 -0500 Subject: [PATCH 068/201] Ensured stable version is considered even if not special version is found --- Extensions/NuGetPackageExtensions.cs | 4 ++-- NuGetUpdater/NuGetUpdater.Execution.cs | 2 +- Tasks/NuGetUpdaterTask.cs | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Extensions/NuGetPackageExtensions.cs b/Extensions/NuGetPackageExtensions.cs index 52dedca..8896ed1 100644 --- a/Extensions/NuGetPackageExtensions.cs +++ b/Extensions/NuGetPackageExtensions.cs @@ -33,14 +33,14 @@ NuGetUpdater.Parameters parameters .OrderByDescending(v => v.Version) .FirstOrDefault(); - if(version != null && parameters.UseStableIfMoreRecent && specialVersion != "") + if(parameters.UseStableIfMoreRecent && specialVersion != "") { var stableVersion = versions .Where(v => v.IsMatchingSpecialVersion("", parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) .OrderByDescending(v => v.Version) .FirstOrDefault(); - if (stableVersion?.Version.IsGreaterThan(version.Version) ?? false) + if (version == null || (stableVersion?.Version.IsGreaterThan(version.Version) ?? false)) { return stableVersion; } diff --git a/NuGetUpdater/NuGetUpdater.Execution.cs b/NuGetUpdater/NuGetUpdater.Execution.cs index 25e4d91..15c5763 100644 --- a/NuGetUpdater/NuGetUpdater.Execution.cs +++ b/NuGetUpdater/NuGetUpdater.Execution.cs @@ -50,7 +50,7 @@ public static bool Update( Parameters parameters, Action logAction = null, string summaryOutputFilePath = null - ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logAction)).Result; + ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logAction, summaryOutputFilePath)).Result; public static async Task UpdateAsync( CancellationToken ct, diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 1c48f1c..46eba40 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -62,14 +62,12 @@ private string[] GetPackages(string input) { switch (input) { - case var p when p == null: - return new string[0]; case var p when p.Contains(";"): return p.Split(';'); case var p when p != null: return new[] { p }; default: - return new string[0]; + return null; } } } From 423a0a0e54bbe0a56af57a127171c9f2abf14cba Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 19 Feb 2019 18:26:18 -0500 Subject: [PATCH 069/201] Fixed null reference --- Tasks/NuGetUpdaterTask.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 46eba40..bfddaa3 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -62,6 +62,8 @@ private string[] GetPackages(string input) { switch (input) { + case var p when p == null: + return null; case var p when p.Contains(";"): return p.Split(';'); case var p when p != null: From b0808c7f5261589d2a4e347167daa39db130572c Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 21 Feb 2019 10:57:13 -0500 Subject: [PATCH 070/201] Improved output --- Entities/Logger.cs | 22 ++++++++++++------- Extensions/ParametersExtension.cs | 36 +++++++++++++++++++++++++++++++ NuGetUpdater/NuGetUpdater.cs | 2 +- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Entities/Logger.cs b/Entities/Logger.cs index 08363ac..102e851 100644 --- a/Entities/Logger.cs +++ b/Entities/Logger.cs @@ -4,6 +4,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Nuget.Updater.Extensions; using Nuget.Updater.Helpers; using NuGet.Versioning; @@ -47,11 +48,9 @@ public void Write(UpdateOperation operation) _updateOperations.Add(operation); } - public void WriteSummary() + public void WriteSummary(NuGetUpdater.Parameters parameters) { - var summary = GetSummary().ToArray(); - - foreach (var line in summary) + foreach (var line in GetSummary(parameters)) { Write(line); } @@ -60,7 +59,7 @@ public void WriteSummary() { try { - FileHelper.LogToFile(_summaryFilePath, summary); + FileHelper.LogToFile(_summaryFilePath, GetSummary(parameters, includeUrl: true)); } catch (Exception ex) { @@ -69,14 +68,21 @@ public void WriteSummary() } } - private IEnumerable GetSummary(bool includeUrl = false) + private IEnumerable GetSummary(NuGetUpdater.Parameters parameters, bool includeUrl = false) { var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); - if (completedUpdates.Any() || skippedUpdates.Any()) + yield return $"# Package update summary"; + + if (_updateOperations.Count == 0) + { + yield return $"No packages have been updated."; + } + + foreach(var line in parameters.GetSummary()) { - yield return $"# Package update summary"; + yield return line; } if (completedUpdates.Any()) diff --git a/Extensions/ParametersExtension.cs b/Extensions/ParametersExtension.cs index d248706..d955e59 100644 --- a/Extensions/ParametersExtension.cs +++ b/Extensions/ParametersExtension.cs @@ -23,5 +23,41 @@ internal static bool ShouldUpdatePackage(this NuGetUpdater.Parameters parameters internal static bool ShouldKeepPackageAtLatestDev(this NuGetUpdater.Parameters parameters, string packageId) => parameters.PackagesToKeepAtLatestDev != null && parameters.PackagesToKeepAtLatestDev.Contains(packageId, StringComparer.OrdinalIgnoreCase); + + internal static IEnumerable GetSummary(this NuGetUpdater.Parameters parameters) + { + yield return $"## Configuration"; + + var packageSources = parameters.IncludeNuGetOrg ? $"NuGet.org and {parameters.SourceFeed}" : parameters.SourceFeed; + yield return $"- Using NuGet pacakges from {packageSources}"; + + var targetVersion = parameters.UseStableIfMoreRecent ? $"{parameters.TargetVersion} with fallback to stable if a more recent version is available" : parameters.TargetVersion; + yield return $"- Targeting version {targetVersion}"; + + if (parameters.IsDowngradeAllowed) + { + yield return $"- Allowing package downgrade if a lower version is found"; + } + + if(parameters.TagToExclude != null && parameters.TagToExclude != "") + { + yield return $"- Excluding versions with the {parameters.TagToExclude} tag"; + } + + if(parameters.PackagesToKeepAtLatestDev?.Any() ?? false) + { + yield return $"- Keeping {string.Join(",", parameters.PackagesToKeepAtLatestDev)} at latest dev"; + } + + if (parameters.PackagesToIgnore?.Any() ?? false) + { + yield return $"- Ignoring {string.Join(",", parameters.PackagesToKeepAtLatestDev)}"; + } + + if (parameters.PackagesToUpdate?.Any() ?? false) + { + yield return $"- Updating only {string.Join(",", parameters.PackagesToKeepAtLatestDev)}"; + } + } } } diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index acb72e2..ac7d380 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -73,7 +73,7 @@ private async Task UpdatePackages(CancellationToken ct) } } - _log.WriteSummary(); + _log.WriteSummary(_parameters); return true; } From bd405d1c3b8cc433d0a91c3827c9e6104d1d3218 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 21 Feb 2019 12:22:11 -0500 Subject: [PATCH 071/201] Improved "Packages"-type parameters --- Extensions/ParametersExtension.cs | 4 ++-- Tasks/NuGetUpdaterTask.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Extensions/ParametersExtension.cs b/Extensions/ParametersExtension.cs index d955e59..c4b76e2 100644 --- a/Extensions/ParametersExtension.cs +++ b/Extensions/ParametersExtension.cs @@ -51,12 +51,12 @@ internal static IEnumerable GetSummary(this NuGetUpdater.Parameters para if (parameters.PackagesToIgnore?.Any() ?? false) { - yield return $"- Ignoring {string.Join(",", parameters.PackagesToKeepAtLatestDev)}"; + yield return $"- Ignoring {string.Join(",", parameters.PackagesToIgnore)}"; } if (parameters.PackagesToUpdate?.Any() ?? false) { - yield return $"- Updating only {string.Join(",", parameters.PackagesToKeepAtLatestDev)}"; + yield return $"- Updating only {string.Join(",", parameters.PackagesToUpdate)}"; } } } diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index bfddaa3..b27156d 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -66,7 +66,7 @@ private string[] GetPackages(string input) return null; case var p when p.Contains(";"): return p.Split(';'); - case var p when p != null: + case var p when p != null && p != "": return new[] { p }; default: return null; From 83a2b6b8e0d038c303644df14848fcc09428449f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 21 Feb 2019 13:49:09 -0500 Subject: [PATCH 072/201] Fixed typo --- Extensions/ParametersExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions/ParametersExtension.cs b/Extensions/ParametersExtension.cs index c4b76e2..44c8c1b 100644 --- a/Extensions/ParametersExtension.cs +++ b/Extensions/ParametersExtension.cs @@ -29,7 +29,7 @@ internal static IEnumerable GetSummary(this NuGetUpdater.Parameters para yield return $"## Configuration"; var packageSources = parameters.IncludeNuGetOrg ? $"NuGet.org and {parameters.SourceFeed}" : parameters.SourceFeed; - yield return $"- Using NuGet pacakges from {packageSources}"; + yield return $"- Using NuGet packages from {packageSources}"; var targetVersion = parameters.UseStableIfMoreRecent ? $"{parameters.TargetVersion} with fallback to stable if a more recent version is available" : parameters.TargetVersion; yield return $"- Targeting version {targetVersion}"; From 1cf4d056ec72b97a6bd9ad9426314be63cee2f92 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 4 Mar 2019 13:34:51 -0500 Subject: [PATCH 073/201] PackageReference with Update are now updated --- Extensions/XmlDocumentExtensions.Net.cs | 2 +- Extensions/XmlDocumentExtensions.UAP.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Extensions/XmlDocumentExtensions.Net.cs b/Extensions/XmlDocumentExtensions.Net.cs index ad7c37e..4365099 100644 --- a/Extensions/XmlDocumentExtensions.Net.cs +++ b/Extensions/XmlDocumentExtensions.Net.cs @@ -44,7 +44,7 @@ bool isDowngradeAllowed { var operations = new List(); - foreach (XmlElement packageReference in document.SelectNodes($"//d:PackageReference[@Include='{packageId}']", namespaceManager)) + foreach (XmlElement packageReference in document.SelectNodes($"//d:PackageReference[@Include='{packageId}' or @Updated='{packageId}']", namespaceManager)) { if (packageReference.HasAttribute("Version")) { diff --git a/Extensions/XmlDocumentExtensions.UAP.cs b/Extensions/XmlDocumentExtensions.UAP.cs index e1b65ad..33ca79c 100644 --- a/Extensions/XmlDocumentExtensions.UAP.cs +++ b/Extensions/XmlDocumentExtensions.UAP.cs @@ -29,7 +29,9 @@ bool isDowngradeAllowed var packageReferences = document.GetElementsByTagName("PackageReference") .Cast() - .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageId); + .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageId + || p.Attributes.GetNamedItem("Update")?.NodeValue?.ToString() == packageId + ); foreach (var packageReference in packageReferences) { From ad5f1bbbe4bfd29198ae181375fb991670e2879c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Mon, 11 Mar 2019 10:00:48 -0400 Subject: [PATCH 074/201] Update build.targets and build.props files. --- Entities/UpdateTarget.cs | 6 +++-- Extensions/XmlDocumentExtensions.Net.cs | 5 +++- NuGetUpdater/NuGetUpdater.cs | 35 ++++++++++++++++++++----- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Entities/UpdateTarget.cs b/Entities/UpdateTarget.cs index b89e6bd..16cfa56 100644 --- a/Entities/UpdateTarget.cs +++ b/Entities/UpdateTarget.cs @@ -14,7 +14,9 @@ public enum UpdateTarget Nuspec = 2, ProjectJson = 4, PackageReference = 8, + DirectoryProps = 16, + DirectoryTargets = 32, - All = Nuspec | ProjectJson | PackageReference - } + All = Nuspec | ProjectJson | PackageReference | DirectoryProps | DirectoryTargets, + } } diff --git a/Extensions/XmlDocumentExtensions.Net.cs b/Extensions/XmlDocumentExtensions.Net.cs index 4365099..fa7acff 100644 --- a/Extensions/XmlDocumentExtensions.Net.cs +++ b/Extensions/XmlDocumentExtensions.Net.cs @@ -44,7 +44,10 @@ bool isDowngradeAllowed { var operations = new List(); - foreach (XmlElement packageReference in document.SelectNodes($"//d:PackageReference[@Include='{packageId}' or @Updated='{packageId}']", namespaceManager)) + var packageReferences = document.SelectNodes($"//d:PackageReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); + var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); + + foreach (XmlElement packageReference in packageReferences.Concat(dotnetCliReferences)) { if (packageReference.HasAttribute("Version")) { diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index ac7d380..572d62c 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -62,8 +62,10 @@ private async Task UpdatePackages(CancellationToken ct) case UpdateTarget.ProjectJson: updates = await UpdateProjectJson(ct, package.PackageId, latest, files.Value.Select(p => p.Key).ToArray(), _parameters.IsDowngradeAllowed); break; - case UpdateTarget.PackageReference: - updates = await UpdateProjects(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); + case UpdateTarget.DirectoryProps: + case UpdateTarget.DirectoryTargets: + case UpdateTarget.PackageReference: + updates = await UpdateProjects(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); break; default: break; @@ -100,7 +102,15 @@ private async Task>> Ge { var targetFiles = new Dictionary>(); - foreach (var target in new[] { UpdateTarget.Nuspec, UpdateTarget.PackageReference, UpdateTarget.ProjectJson }) + var updateTarget = new[] { + UpdateTarget.Nuspec, + UpdateTarget.PackageReference, + UpdateTarget.ProjectJson, + UpdateTarget.DirectoryProps, + UpdateTarget.DirectoryTargets, + }; + + foreach (var target in updateTarget) { if (_parameters.HasUpdateTarget(target)) { @@ -120,13 +130,24 @@ private async Task> GetFilesForTarget(Cancellati case UpdateTarget.Nuspec: extensionFilter = ".nuspec"; break; + case UpdateTarget.ProjectJson: nameFilter = "project.json"; break; - case UpdateTarget.PackageReference: - extensionFilter = ".csproj"; - break; - default: + + case UpdateTarget.PackageReference: + extensionFilter = ".csproj"; + break; + + case UpdateTarget.DirectoryTargets: + extensionFilter = "Directory.Build.targets"; + break; + + case UpdateTarget.DirectoryProps: + extensionFilter = "Directory.Build.props"; + break; + + default: break; } From 81524f4baecce4b6f4b78fd82bde371be76134c3 Mon Sep 17 00:00:00 2001 From: Xiaotian GU Date: Tue, 7 May 2019 19:34:01 -0400 Subject: [PATCH 075/201] fixed Windows.Data.Xml.Dom.XmlDocument whitespaces --- Extensions/XmlDocumentExtensions.UAP.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Extensions/XmlDocumentExtensions.UAP.cs b/Extensions/XmlDocumentExtensions.UAP.cs index 33ca79c..0c79d0e 100644 --- a/Extensions/XmlDocumentExtensions.UAP.cs +++ b/Extensions/XmlDocumentExtensions.UAP.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml; @@ -86,7 +87,11 @@ public static async Task> GetDocument(this str public static async Task Save(this XmlDocument document, CancellationToken ct, string path) { - await document.SaveToFileAsync(await StorageFile.GetFileFromPathAsync(path)); + var xml = document.GetXml(); + xml = Regex.Replace(xml, "(\\?>)(<)", "$1\n$2"); // xml declaration should follow by a new line + xml = Regex.Replace(xml, "([^ ])(/>)", "$1 $2"); // self-enclosing tag should end with a space + + await FileIO.WriteTextAsync(await StorageFile.GetFileFromPathAsync(path), xml); } } } From a2d53235fc3452a44008efb061bace5751f4c7a5 Mon Sep 17 00:00:00 2001 From: Xiaotian GU Date: Wed, 8 May 2019 14:49:39 -0400 Subject: [PATCH 076/201] fixed xml encoding declariation missing --- Extensions/XmlDocumentExtensions.UAP.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Extensions/XmlDocumentExtensions.UAP.cs b/Extensions/XmlDocumentExtensions.UAP.cs index 0c79d0e..67019a7 100644 --- a/Extensions/XmlDocumentExtensions.UAP.cs +++ b/Extensions/XmlDocumentExtensions.UAP.cs @@ -88,6 +88,10 @@ public static async Task> GetDocument(this str public static async Task Save(this XmlDocument document, CancellationToken ct, string path) { var xml = document.GetXml(); + xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding") + ? x.Result("$1${declaration} encoding=\"utf-8\"$2") // restore encoding declaration that is stripped by `GetXml` + : x.Value + ); xml = Regex.Replace(xml, "(\\?>)(<)", "$1\n$2"); // xml declaration should follow by a new line xml = Regex.Replace(xml, "([^ ])(/>)", "$1 $2"); // self-enclosing tag should end with a space From c83821366dddd33ba12a773f09d74fa89c7cd501 Mon Sep 17 00:00:00 2001 From: Luke Bayly Date: Wed, 26 Jun 2019 13:59:42 -0400 Subject: [PATCH 077/201] Fix nuget updater to find Directory.Build.props and Directory.Build.targets Related Work Items: #150878 --- NuGetUpdater/NuGetUpdater.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index 572d62c..c91ed22 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -140,11 +140,11 @@ private async Task> GetFilesForTarget(Cancellati break; case UpdateTarget.DirectoryTargets: - extensionFilter = "Directory.Build.targets"; + nameFilter = "Directory.Build.targets"; break; case UpdateTarget.DirectoryProps: - extensionFilter = "Directory.Build.props"; + nameFilter = "Directory.Build.props"; break; default: From fa997afb394ab28d740fcdf610b2afa5c4098c98 Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Fri, 12 Jul 2019 13:19:50 -0400 Subject: [PATCH 078/201] fixed FeedNuGetVersion text in projects.json --- NuGetUpdater/NuGetUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index c91ed22..3da8c5b 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -238,7 +238,7 @@ bool isDowngradeAllowed var operations = new List(); var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; - var replaced = $@"""{packageName}"": ""{latestVersion}"""; + var replaced = $@"""{packageName}"": ""{latestVersion.Version.ToString()}"""; for (int i = 0; i < jsonFiles.Length; i++) { From 5790ffe1d1bb7412acccea4505b9e11b770fb706 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 16 Jul 2019 16:05:38 -0400 Subject: [PATCH 079/201] Added netcore head --- Extensions/ParametersExtension.cs | 6 ++++++ Legacy/NuGetBranchSwitchTask.cs | 2 +- Nuget.Updater.csproj | 11 ++++++++--- Tasks/NuGetUpdaterTask.cs | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Extensions/ParametersExtension.cs b/Extensions/ParametersExtension.cs index 44c8c1b..2d9e4c0 100644 --- a/Extensions/ParametersExtension.cs +++ b/Extensions/ParametersExtension.cs @@ -14,7 +14,11 @@ internal static class ParametersExtension internal static PackageSource GetFeedPackageSource(this NuGetUpdater.Parameters parameters) => new PackageSource(parameters.SourceFeed, "Feed") { +#if UAP Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false) +#else + Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false, null) +#endif }; internal static bool ShouldUpdatePackage(this NuGetUpdater.Parameters parameters, NuGetPackage package) => @@ -28,6 +32,8 @@ internal static IEnumerable GetSummary(this NuGetUpdater.Parameters para { yield return $"## Configuration"; + yield return $"- Update targetting {parameters.SolutionRoot}"; + var packageSources = parameters.IncludeNuGetOrg ? $"NuGet.org and {parameters.SourceFeed}" : parameters.SourceFeed; yield return $"- Using NuGet packages from {packageSources}"; diff --git a/Legacy/NuGetBranchSwitchTask.cs b/Legacy/NuGetBranchSwitchTask.cs index 2d214fa..d21eea7 100644 --- a/Legacy/NuGetBranchSwitchTask.cs +++ b/Legacy/NuGetBranchSwitchTask.cs @@ -1,4 +1,4 @@ -#if !UAP +#if !UAP && !NETSTANDARD using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; diff --git a/Nuget.Updater.csproj b/Nuget.Updater.csproj index 9629971..689c7bf 100644 --- a/Nuget.Updater.csproj +++ b/Nuget.Updater.csproj @@ -1,6 +1,6 @@  - net46;uap10.0.17134 + netstandard20;net472;uap10.0.17134 nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time true @@ -18,8 +18,13 @@ - - + + + + + + + all diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index b27156d..31b26f6 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -1,4 +1,4 @@ -#if !UAP +#if !UAP && !NETSTANDARD using Microsoft.Build.Framework; using Microsoft.Build.Utilities; From 48168257fc42100d4af4c548d387556bba7ee821 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 16 Jul 2019 16:52:47 -0400 Subject: [PATCH 080/201] Replace Action with TextWriter --- Entities/ActionTextWriter.cs | 20 ++++++++++++++++++++ Entities/Logger.cs | 13 +++++++------ NuGetUpdater/NuGetUpdater.Execution.cs | 17 +++++++++-------- Tasks/NuGetUpdaterTask.cs | 3 ++- 4 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 Entities/ActionTextWriter.cs diff --git a/Entities/ActionTextWriter.cs b/Entities/ActionTextWriter.cs new file mode 100644 index 0000000..18ebb1d --- /dev/null +++ b/Entities/ActionTextWriter.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; +using System.Text; + +namespace Nuget.Updater.Entities +{ + public class ActionTextWriter : TextWriter + { + private readonly Action _writeAction; + + public ActionTextWriter(Action writeAction) + { + _writeAction = writeAction ?? new Action(_ => { }); + } + + public override void Write(string value) => _writeAction(value); + + public override Encoding Encoding => Encoding.Default; + } +} diff --git a/Entities/Logger.cs b/Entities/Logger.cs index 102e851..c0053e6 100644 --- a/Entities/Logger.cs +++ b/Entities/Logger.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -16,23 +17,23 @@ public class Logger private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; private readonly List _updateOperations = new List(); - private readonly Action _logAction; + private readonly TextWriter _writer; private readonly string _summaryFilePath; - public Logger(Action logAction = null, string summaryFilePath = null) + public Logger(TextWriter writer, string summaryFilePath = null) { - _logAction = logAction + writer = writer #if DEBUG - ?? Console.WriteLine; + ?? Console.Out; #else - ?? new Action(_ => { }); + ?? TextWriter.Null; #endif _summaryFilePath = summaryFilePath; } public void Clear() => _updateOperations.Clear(); - public void Write(string message) => _logAction(message); + public void Write(string message) => _writer.Write(message); public void Write(IEnumerable operations) { diff --git a/NuGetUpdater/NuGetUpdater.Execution.cs b/NuGetUpdater/NuGetUpdater.Execution.cs index 15c5763..1f073e9 100644 --- a/NuGetUpdater/NuGetUpdater.Execution.cs +++ b/NuGetUpdater/NuGetUpdater.Execution.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using Nuget.Updater.Entities; @@ -21,7 +22,7 @@ public static bool Update( IEnumerable ignorePackages = null, IEnumerable updatePackages = null, UpdateTarget target = UpdateTarget.All, - Action logAction = null, + TextWriter logWriter = null, string summaryOutputFilePath = null, bool useStableIfMoreRecent = false ) @@ -40,7 +41,7 @@ public static bool Update( ignorePackages, updatePackages, target, - logAction, + logWriter, summaryOutputFilePath, useStableIfMoreRecent ).Result; @@ -48,9 +49,9 @@ public static bool Update( public static bool Update( Parameters parameters, - Action logAction = null, + TextWriter logWriter = null, string summaryOutputFilePath = null - ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logAction, summaryOutputFilePath)).Result; + ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logWriter, summaryOutputFilePath)).Result; public static async Task UpdateAsync( CancellationToken ct, @@ -66,7 +67,7 @@ public static async Task UpdateAsync( IEnumerable packagesToIgnore = null, IEnumerable packagesToUpdate = null, UpdateTarget updateTarget = UpdateTarget.All, - Action logAction = null, + TextWriter logWriter = null, string summaryOutputFilePath = null, bool useStableIfMoreRecent = false ) @@ -88,15 +89,15 @@ public static async Task UpdateAsync( UseStableIfMoreRecent = useStableIfMoreRecent, }; - return await UpdateAsync(ct, parameters, logAction, summaryOutputFilePath); + return await UpdateAsync(ct, parameters, logWriter, summaryOutputFilePath); } public static Task UpdateAsync( CancellationToken ct, Parameters parameters, - Action logAction = null, + TextWriter logWriter = null, string summaryOutputFilePath = null - ) => UpdateAsync(ct, parameters, new Logger(logAction, summaryOutputFilePath)); + ) => UpdateAsync(ct, parameters, new Logger(logWriter, summaryOutputFilePath)); public static async Task UpdateAsync( CancellationToken ct, diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs index 31b26f6..7c551c8 100644 --- a/Tasks/NuGetUpdaterTask.cs +++ b/Tasks/NuGetUpdaterTask.cs @@ -1,6 +1,7 @@ #if !UAP && !NETSTANDARD using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using Nuget.Updater.Entities; namespace Nuget.Updater { @@ -53,7 +54,7 @@ public override bool Execute() return NuGetUpdater.Update( parameters, - message => Log.LogMessage(message), + new ActionTextWriter(s => Log.LogMessage(s)), UpdateSummaryFile ); } From e16b605a0c839734096e19523e3df9b479d13dae Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 16 Jul 2019 17:03:22 -0400 Subject: [PATCH 081/201] Removed hardcoded owner:nventive --- NuGetUpdater/NuGetUpdater.Execution.cs | 4 ++++ NuGetUpdater/NuGetUpdater.Parameters.cs | 5 +++++ NuGetUpdater/NuGetUpdater.cs | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/NuGetUpdater/NuGetUpdater.Execution.cs b/NuGetUpdater/NuGetUpdater.Execution.cs index 1f073e9..ed10abb 100644 --- a/NuGetUpdater/NuGetUpdater.Execution.cs +++ b/NuGetUpdater/NuGetUpdater.Execution.cs @@ -16,6 +16,7 @@ public static bool Update( string excludeTag = "", string PAT = "", bool includeNuGetOrg = true, + string publicPackageOwner = null, bool allowDowngrade = false, bool strict = true, IEnumerable keepLatestDev = null, @@ -35,6 +36,7 @@ public static bool Update( excludeTag, PAT, includeNuGetOrg, + publicPackageOwner, allowDowngrade, strict, keepLatestDev, @@ -61,6 +63,7 @@ public static async Task UpdateAsync( string excludeTag = "", string feedPat = "", bool includeNuGetOrg = true, + string publicPackageOwner = null, bool isDowngradeAllowed = false, bool strict = true, IEnumerable packagesTokeepAtLatestDev = null, @@ -82,6 +85,7 @@ public static async Task UpdateAsync( TagToExclude = excludeTag, UpdateTarget = updateTarget, IncludeNuGetOrg = includeNuGetOrg, + PublickPackageOwner = publicPackageOwner, IsDowngradeAllowed = isDowngradeAllowed, PackagesToKeepAtLatestDev = packagesTokeepAtLatestDev, PackagesToIgnore = packagesToIgnore, diff --git a/NuGetUpdater/NuGetUpdater.Parameters.cs b/NuGetUpdater/NuGetUpdater.Parameters.cs index 195cbcb..d3e4ad9 100644 --- a/NuGetUpdater/NuGetUpdater.Parameters.cs +++ b/NuGetUpdater/NuGetUpdater.Parameters.cs @@ -74,6 +74,11 @@ public class Parameters /// Whether to use the stable version if a more recent version is available /// public bool UseStableIfMoreRecent { get; set; } + + /// + /// The name of the owner of the public packages to update; specify if is set to true + /// + public string PublickPackageOwner { get; set; } } } } diff --git a/NuGetUpdater/NuGetUpdater.cs b/NuGetUpdater/NuGetUpdater.cs index 3da8c5b..ae36cc8 100644 --- a/NuGetUpdater/NuGetUpdater.cs +++ b/NuGetUpdater/NuGetUpdater.cs @@ -89,7 +89,7 @@ private async Task GetPackages(CancellationToken ct) { //Using search instead of list because the latter forces the v2 api packages = packages - .Concat(await NuGetOrgSource.SearchPackages(ct, _log.Write, searchTerm: "owner:nventive")) + .Concat(await NuGetOrgSource.SearchPackages(ct, _log.Write, searchTerm: $"owner:{_parameters.PublickPackageOwner}")) .GroupBy(p => p.PackageId) .Select(g => new NuGetPackage(g.Key, g.ToArray())) .ToArray(); From 56cfe349c8a0a22400eda2b8bc71f79650b69d73 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 18 Jul 2019 13:41:51 -0400 Subject: [PATCH 082/201] Re-structuring folders --- .gitignore | 247 ++++++++++++++++++ Tasks/NuGetUpdaterTask.cs | 78 ------ build/Nuget.Updater.targets | 12 - src/NuGet.Updater.sln | 25 ++ .../Entities}/ActionTextWriter.cs | 0 .../Entities}/FeedNuGetVersion.cs | 0 .../NuGet.Updater/Entities}/Logger.cs | 0 .../NuGet.Updater/Entities}/NuGetPackage.cs | 0 .../Entities}/NuGetVersionExtensions.cs | 0 .../Entities}/UpdateOperation.cs | 0 .../NuGet.Updater/Entities}/UpdateTarget.cs | 0 .../Extensions}/EnumerableAsyncExtensions.cs | 0 .../Extensions}/FeedNuGetVersionExtensions.cs | 0 .../Extensions}/NuGetPackageExtensions.cs | 0 .../Extensions}/PackageSourceExtensions.cs | 0 .../Extensions}/ParametersExtension.cs | 0 .../Extensions}/XmlDocumentExtensions.Net.cs | 0 .../Extensions}/XmlDocumentExtensions.UAP.cs | 0 .../Extensions}/XmlDocumentExtensions.cs | 0 .../NuGet.Updater/Helpers}/FileHelper.Net.cs | 0 .../NuGet.Updater/Helpers}/FileHelper.UAP.cs | 0 .../Legacy}/NuGetBranchSwitchExecution.cs | 0 .../Legacy}/NuGetBranchSwitchTask.cs | 0 .../NuGet.Updater/NuGet.Updater.csproj | 26 +- .../NuGet.Updater}/NuGetUpdater.Execution.cs | 0 .../NuGet.Updater}/NuGetUpdater.cs | 0 .../NuGetUpdater}/NuGetUpdater.Parameters.cs | 0 .../NuGet.Protocol/uap10.0/NuGet.Common.dll | Bin 0 -> 80896 bytes .../uap10.0/NuGet.Configuration.dll | Bin 0 -> 78848 bytes .../uap10.0/NuGet.Frameworks.dll | Bin 0 -> 92672 bytes .../uap10.0/NuGet.Packaging.Core.dll | Bin 0 -> 35840 bytes .../uap10.0/NuGet.Packaging.dll | Bin 0 -> 451584 bytes .../NuGet.Protocol/uap10.0/NuGet.Protocol.dll | Bin 0 -> 641536 bytes .../uap10.0/NuGet.Versioning.dll | Bin 0 -> 35840 bytes src/global.json | 5 + 35 files changed, 287 insertions(+), 106 deletions(-) create mode 100644 .gitignore delete mode 100644 Tasks/NuGetUpdaterTask.cs delete mode 100644 build/Nuget.Updater.targets create mode 100644 src/NuGet.Updater.sln rename {Entities => src/NuGet.Updater/Entities}/ActionTextWriter.cs (100%) rename {Entities => src/NuGet.Updater/Entities}/FeedNuGetVersion.cs (100%) rename {Entities => src/NuGet.Updater/Entities}/Logger.cs (100%) rename {Entities => src/NuGet.Updater/Entities}/NuGetPackage.cs (100%) rename {Entities => src/NuGet.Updater/Entities}/NuGetVersionExtensions.cs (100%) rename {Entities => src/NuGet.Updater/Entities}/UpdateOperation.cs (100%) rename {Entities => src/NuGet.Updater/Entities}/UpdateTarget.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/EnumerableAsyncExtensions.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/FeedNuGetVersionExtensions.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/NuGetPackageExtensions.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/PackageSourceExtensions.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/ParametersExtension.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/XmlDocumentExtensions.Net.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/XmlDocumentExtensions.UAP.cs (100%) rename {Extensions => src/NuGet.Updater/Extensions}/XmlDocumentExtensions.cs (100%) rename {Helpers => src/NuGet.Updater/Helpers}/FileHelper.Net.cs (100%) rename {Helpers => src/NuGet.Updater/Helpers}/FileHelper.UAP.cs (100%) rename {Legacy => src/NuGet.Updater/Legacy}/NuGetBranchSwitchExecution.cs (100%) rename {Legacy => src/NuGet.Updater/Legacy}/NuGetBranchSwitchTask.cs (100%) rename Nuget.Updater.csproj => src/NuGet.Updater/NuGet.Updater.csproj (66%) rename {NuGetUpdater => src/NuGet.Updater}/NuGetUpdater.Execution.cs (100%) rename {NuGetUpdater => src/NuGet.Updater}/NuGetUpdater.cs (100%) rename {NuGetUpdater => src/NuGet.Updater/NuGetUpdater}/NuGetUpdater.Parameters.cs (100%) create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Common.dll create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Configuration.dll create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Frameworks.dll create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Packaging.Core.dll create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Packaging.dll create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Protocol.dll create mode 100644 src/Packages/NuGet.Protocol/uap10.0/NuGet.Versioning.dll create mode 100644 src/global.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af54091 --- /dev/null +++ b/.gitignore @@ -0,0 +1,247 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Remove everything beginning with a dot +.*/ +!.github/ + +# Umbrella +umbrella_installlog.txt +Resource.Designer.cs +*.g.cs + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +# [Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ +logfile=obj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.mdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Xamarin.Android generated resource files +Resources/Resource.Designer.cs + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# DotCover is a Code Coverage Tool +*.dotCover + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +#*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +#!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ + +# Ignore generated files from the uno binding helper +/Uno/Uno.UI.BindingHelper.Android/**/*.class +/Uno/Uno.UI.BindingHelper.Android/**/*.jar + +# Xamarin +*/Resources/Resource.Desginer.cs +/nuget_version_override.txt +/src/crosstargeting_override.props +msbuild.binlog +/Uno/Uno.UI.TestComparer/Properties/launchSettings.json +*.diagsession + +/src/SourceGenerators/System.Xaml/obj-net_4_x-win32 diff --git a/Tasks/NuGetUpdaterTask.cs b/Tasks/NuGetUpdaterTask.cs deleted file mode 100644 index 7c551c8..0000000 --- a/Tasks/NuGetUpdaterTask.cs +++ /dev/null @@ -1,78 +0,0 @@ -#if !UAP && !NETSTANDARD -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Nuget.Updater.Entities; - -namespace Nuget.Updater -{ - public class NuGetUpdaterTask : Task - { - [Required] - public string SpecialVersion { get; set; } - - [Required] - public string SolutionRoot { get; set; } - - [Required] - public string NuGetFeed { get; set; } - - [Required] - public string PAT { get; set; } - - [Required] - public bool AllowDowngrade { get; set; } - - public string ExcludeTag { get; set; } - - public string IgnorePackages { get; set; } - - public string UpdatePackages { get; set; } - - public string UpdateSummaryFile { get; set; } - - public bool UseStableIfMoreRecent { get; set; } - - public override bool Execute() - { - var parameters = new NuGetUpdater.Parameters - { - SolutionRoot = SolutionRoot, - SourceFeed = NuGetFeed, - SourceFeedPersonalAccessToken = PAT, - TargetVersion = SpecialVersion, - IsDowngradeAllowed = AllowDowngrade, - PackagesToIgnore = GetPackages(IgnorePackages), - PackagesToUpdate = GetPackages(UpdatePackages), - TagToExclude = ExcludeTag, - UseStableIfMoreRecent = UseStableIfMoreRecent, - //Default values - PackagesToKeepAtLatestDev = new string[0], - IncludeNuGetOrg = true, - Strict = true, - UpdateTarget = UpdateTarget.All, - }; - - return NuGetUpdater.Update( - parameters, - new ActionTextWriter(s => Log.LogMessage(s)), - UpdateSummaryFile - ); - } - - private string[] GetPackages(string input) - { - switch (input) - { - case var p when p == null: - return null; - case var p when p.Contains(";"): - return p.Split(';'); - case var p when p != null && p != "": - return new[] { p }; - default: - return null; - } - } - } -} -#endif \ No newline at end of file diff --git a/build/Nuget.Updater.targets b/build/Nuget.Updater.targets deleted file mode 100644 index f8a9b3f..0000000 --- a/build/Nuget.Updater.targets +++ /dev/null @@ -1,12 +0,0 @@ - - - - - ..\lib\netstandard2.0\Nuget.Updater.dll - true - - - - - - \ No newline at end of file diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln new file mode 100644 index 0000000..da1c300 --- /dev/null +++ b/src/NuGet.Updater.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29021.104 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nuget.Updater", "Nuget.Updater\NuGet.Updater.csproj", "{29277270-8EFE-41A3-8CD8-D54EBF514C41}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1C528FB3-4BD1-4D56-ABEE-A20F6367040F} + EndGlobalSection +EndGlobal diff --git a/Entities/ActionTextWriter.cs b/src/NuGet.Updater/Entities/ActionTextWriter.cs similarity index 100% rename from Entities/ActionTextWriter.cs rename to src/NuGet.Updater/Entities/ActionTextWriter.cs diff --git a/Entities/FeedNuGetVersion.cs b/src/NuGet.Updater/Entities/FeedNuGetVersion.cs similarity index 100% rename from Entities/FeedNuGetVersion.cs rename to src/NuGet.Updater/Entities/FeedNuGetVersion.cs diff --git a/Entities/Logger.cs b/src/NuGet.Updater/Entities/Logger.cs similarity index 100% rename from Entities/Logger.cs rename to src/NuGet.Updater/Entities/Logger.cs diff --git a/Entities/NuGetPackage.cs b/src/NuGet.Updater/Entities/NuGetPackage.cs similarity index 100% rename from Entities/NuGetPackage.cs rename to src/NuGet.Updater/Entities/NuGetPackage.cs diff --git a/Entities/NuGetVersionExtensions.cs b/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs similarity index 100% rename from Entities/NuGetVersionExtensions.cs rename to src/NuGet.Updater/Entities/NuGetVersionExtensions.cs diff --git a/Entities/UpdateOperation.cs b/src/NuGet.Updater/Entities/UpdateOperation.cs similarity index 100% rename from Entities/UpdateOperation.cs rename to src/NuGet.Updater/Entities/UpdateOperation.cs diff --git a/Entities/UpdateTarget.cs b/src/NuGet.Updater/Entities/UpdateTarget.cs similarity index 100% rename from Entities/UpdateTarget.cs rename to src/NuGet.Updater/Entities/UpdateTarget.cs diff --git a/Extensions/EnumerableAsyncExtensions.cs b/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs similarity index 100% rename from Extensions/EnumerableAsyncExtensions.cs rename to src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs diff --git a/Extensions/FeedNuGetVersionExtensions.cs b/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs similarity index 100% rename from Extensions/FeedNuGetVersionExtensions.cs rename to src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs diff --git a/Extensions/NuGetPackageExtensions.cs b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs similarity index 100% rename from Extensions/NuGetPackageExtensions.cs rename to src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs diff --git a/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs similarity index 100% rename from Extensions/PackageSourceExtensions.cs rename to src/NuGet.Updater/Extensions/PackageSourceExtensions.cs diff --git a/Extensions/ParametersExtension.cs b/src/NuGet.Updater/Extensions/ParametersExtension.cs similarity index 100% rename from Extensions/ParametersExtension.cs rename to src/NuGet.Updater/Extensions/ParametersExtension.cs diff --git a/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs similarity index 100% rename from Extensions/XmlDocumentExtensions.Net.cs rename to src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs diff --git a/Extensions/XmlDocumentExtensions.UAP.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs similarity index 100% rename from Extensions/XmlDocumentExtensions.UAP.cs rename to src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs diff --git a/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs similarity index 100% rename from Extensions/XmlDocumentExtensions.cs rename to src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs diff --git a/Helpers/FileHelper.Net.cs b/src/NuGet.Updater/Helpers/FileHelper.Net.cs similarity index 100% rename from Helpers/FileHelper.Net.cs rename to src/NuGet.Updater/Helpers/FileHelper.Net.cs diff --git a/Helpers/FileHelper.UAP.cs b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs similarity index 100% rename from Helpers/FileHelper.UAP.cs rename to src/NuGet.Updater/Helpers/FileHelper.UAP.cs diff --git a/Legacy/NuGetBranchSwitchExecution.cs b/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs similarity index 100% rename from Legacy/NuGetBranchSwitchExecution.cs rename to src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs diff --git a/Legacy/NuGetBranchSwitchTask.cs b/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs similarity index 100% rename from Legacy/NuGetBranchSwitchTask.cs rename to src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs diff --git a/Nuget.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj similarity index 66% rename from Nuget.Updater.csproj rename to src/NuGet.Updater/NuGet.Updater.csproj index 689c7bf..8e21f96 100644 --- a/Nuget.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -1,10 +1,14 @@  netstandard20;net472;uap10.0.17134 + true + 7.2 + + + + nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time - true - 7.1 @@ -12,10 +16,7 @@ - - all - runtime; build; native; contentfiles; analyzers - + @@ -26,27 +27,20 @@ - - all - - - - + + true - - build - - + true lib/uap10.0.17134 diff --git a/NuGetUpdater/NuGetUpdater.Execution.cs b/src/NuGet.Updater/NuGetUpdater.Execution.cs similarity index 100% rename from NuGetUpdater/NuGetUpdater.Execution.cs rename to src/NuGet.Updater/NuGetUpdater.Execution.cs diff --git a/NuGetUpdater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs similarity index 100% rename from NuGetUpdater/NuGetUpdater.cs rename to src/NuGet.Updater/NuGetUpdater.cs diff --git a/NuGetUpdater/NuGetUpdater.Parameters.cs b/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs similarity index 100% rename from NuGetUpdater/NuGetUpdater.Parameters.cs rename to src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs diff --git a/src/Packages/NuGet.Protocol/uap10.0/NuGet.Common.dll b/src/Packages/NuGet.Protocol/uap10.0/NuGet.Common.dll new file mode 100644 index 0000000000000000000000000000000000000000..9b1f678245e9decc5a10698601a0f0883f377a4b GIT binary patch literal 80896 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μ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 D+(=%U literal 0 HcmV?d00001 diff --git a/src/Packages/NuGet.Protocol/uap10.0/NuGet.Frameworks.dll b/src/Packages/NuGet.Protocol/uap10.0/NuGet.Frameworks.dll new file mode 100644 index 0000000000000000000000000000000000000000..788c21a33d519e0ef68d3d2f8c5af56d984750b0 GIT binary patch literal 92672 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

h#bowzs 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 literal 0 HcmV?d00001 diff --git a/src/Packages/NuGet.Protocol/uap10.0/NuGet.Packaging.Core.dll b/src/Packages/NuGet.Protocol/uap10.0/NuGet.Packaging.Core.dll new file mode 100644 index 0000000000000000000000000000000000000000..aefa41e70b6d35837b8188614327dc3aee60e447 GIT binary patch literal 35840 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 literal 0 HcmV?d00001 diff --git a/src/Packages/NuGet.Protocol/uap10.0/NuGet.Packaging.dll b/src/Packages/NuGet.Protocol/uap10.0/NuGet.Packaging.dll new file mode 100644 index 0000000000000000000000000000000000000000..f9f15240c833b61e3eb0425c3c0ae8aacc05cc4c GIT binary patch literal 451584 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

|4O1gqNBw7yc$`;~DMs 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≺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

qK7MeQ(si0CnPk!UL9OoZu{lskto&k zlG}k&lrL3c+S*U<-{Uyl&VyHeSuDgBe+`t@zYjp^-0u^icnyHpK@uh1icpMbx9ggl z)`&j@b6zEFfbnq2+JckiXUMV_X)#YZ7JD%dG4&=!3Jrx~`A!CvD1mx7!zES%*9M&g zPOAN7QJZROOjgp#@^CGsJbuaihSr>9{G7~AQYU?Rc_g#Tfqg`0YtxFfvK}`YZ~N+C zJy8~>bvVP^JxO^a=F7GCJc(!Y)tTIdB0(eg2=(LpFhXH|9u4Nvp|s@N+G%bcweMi4 z%9tf->*gBa#u4oKJ>ouqdxJ2!6~6eg>?N|WR96e-Ebi>=Y`?$7-AGXNxK3ndS(4S9 zp$WMixW{o8Vt&bLPNH>P3hpIYBdx_+s$U;ZbfaM0OO|h;9)Ih*k=lC3h|JCtnz4_1 zhH(hhH9=h={T3@gxm70V+`4ObX{weu}AE6Takj8u6vGRbP0%;E>O z5bIWP3&q!*yTmSBb7l#ZtVcZK598~ewd1-!t~8j@4f{H|v9&naIQzhQ%uUc^?Ey>r z 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%0Aioqu8wJvy&fx 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 Date: Thu, 18 Jul 2019 14:14:05 -0400 Subject: [PATCH 083/201] Added netcore app --- .../NuGet.Updater.Tool.csproj | 43 +++++++++++ src/NuGet.Updater.Tool/Program.cs | 77 +++++++++++++++++++ src/NuGet.Updater.sln | 12 ++- .../Entities/ActionTextWriter.cs | 2 +- .../Entities/FeedNuGetVersion.cs | 2 +- src/NuGet.Updater/Entities/Logger.cs | 6 +- src/NuGet.Updater/Entities/NuGetPackage.cs | 2 +- .../Entities/NuGetVersionExtensions.cs | 2 +- src/NuGet.Updater/Entities/UpdateOperation.cs | 2 +- src/NuGet.Updater/Entities/UpdateTarget.cs | 2 +- .../Extensions/EnumerableAsyncExtensions.cs | 2 +- .../Extensions/FeedNuGetVersionExtensions.cs | 4 +- .../Extensions/NuGetPackageExtensions.cs | 4 +- .../Extensions/PackageSourceExtensions.cs | 4 +- .../Extensions/ParametersExtension.cs | 4 +- .../Extensions/XmlDocumentExtensions.Net.cs | 4 +- .../Extensions/XmlDocumentExtensions.UAP.cs | 4 +- .../Extensions/XmlDocumentExtensions.cs | 4 +- src/NuGet.Updater/Helpers/FileHelper.Net.cs | 2 +- src/NuGet.Updater/Helpers/FileHelper.UAP.cs | 2 +- .../Legacy/NuGetBranchSwitchExecution.cs | 2 +- .../Legacy/NuGetBranchSwitchTask.cs | 2 +- src/NuGet.Updater/NuGetUpdater.Execution.cs | 4 +- src/NuGet.Updater/NuGetUpdater.cs | 8 +- .../NuGetUpdater/NuGetUpdater.Parameters.cs | 4 +- 25 files changed, 165 insertions(+), 39 deletions(-) create mode 100644 src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj create mode 100644 src/NuGet.Updater.Tool/Program.cs diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj new file mode 100644 index 0000000..f495c94 --- /dev/null +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -0,0 +1,43 @@ + + + + Exe + netcoreapp2.2 + true + true + nugetupdater + latest + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + + + + + nventive.NuGet.Updater.Tool + NuGet Updater Tool + A netcore version of the NuGet updater + nventive + nventive + Apache-2.0 + https://nv-assets.azurewebsites.net/logos/nv_small_square_logo.jpg + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs new file mode 100644 index 0000000..b728f36 --- /dev/null +++ b/src/NuGet.Updater.Tool/Program.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Mono.Options; + +using UpdaterParameters = NuGet.Updater.NuGetUpdater.Parameters; + +namespace NuGet.Updater.Tool +{ + public class Program + { + public static async Task Main(string[] args) + { + var parameters = new UpdaterParameters + { + UpdateTarget = UpdateTarget.All + }; + + var isHelp = false; + string summaryFile = default; + + var options = new OptionSet + { + { "help", "Displays this help screen", s => isHelp = true }, + { "solution=", "The path to the solution to update", s => parameters.SolutionRoot = s }, + { "feed=", "The URL of a private NuGet feed to use", s => parameters.SourceFeed = s }, + { "pat=", "The PAT used to authenticate to the private NuGet feed", s => parameters.SourceFeedPersonalAccessToken = s }, + { "version=", "The target version to use", s => parameters.TargetVersion = s }, + { "strict=", s => parameters.Strict = GetBoolean(s) }, + { "excludeTag=", "A tag to exclude from the search", s => parameters.TagToExclude = s }, + { "useNuGetorg=", "Whether to pull packages from NuGet.org", s => parameters.IncludeNuGetOrg = GetBoolean(s) }, + { "publicPackagesOwner=", "The owner of the public packages to update; must be specified is useNuGetorg is true", s => parameters.PublickPackageOwner = s }, + { "allowDowngrade=", "Whether package downgrade is allowed", s => parameters.IsDowngradeAllowed = GetBoolean(s) }, + { "keepLatestDev=", "A comma-separated list of packages to keep at latest dev", s => parameters.PackagesToKeepAtLatestDev = GetList(s) }, + { "ignore=", "A comma-separated list of packages to ignore", s => parameters.PackagesToIgnore = GetList(s) }, + { "update=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => parameters.PackagesToUpdate = GetList(s) }, + { "useStableIfMoreRecent=", "Whether to use the latest stable if a more recent version is found", s => parameters.UseStableIfMoreRecent = GetBoolean(s) }, + { "outputFile=", "The path to a file where the update summary will be written", s => summaryFile = s }, + }; + + options.Parse(args); + + if (isHelp) + { + Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); + options.WriteOptionDescriptions(Console.Out); + } + else + { + await NuGetUpdater.UpdateAsync(CancellationToken.None, parameters, Console.Out, summaryFile); + } + } + + private static bool GetBoolean(string value, bool fallbackValue = false) + { + if (bool.TryParse(value, out var boolean)) + { + return boolean; + } + + return fallbackValue; + } + + private static List GetList(string value) + { + var list = new List(); + + if (value.Contains(",")) + { + list.AddRange(value.Split(",")); + } + + return list; + } + } +} diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index da1c300..0d8b11c 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29021.104 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.757 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nuget.Updater", "Nuget.Updater\NuGet.Updater.csproj", "{29277270-8EFE-41A3-8CD8-D54EBF514C41}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater", "Nuget.Updater\NuGet.Updater.csproj", "{29277270-8EFE-41A3-8CD8-D54EBF514C41}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tool", "NuGet.Updater.Tool\NuGet.Updater.Tool.csproj", "{3DAA81DB-00A6-4A84-8180-19A588398F52}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Debug|Any CPU.Build.0 = Debug|Any CPU {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Release|Any CPU.ActiveCfg = Release|Any CPU {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Release|Any CPU.Build.0 = Release|Any CPU + {3DAA81DB-00A6-4A84-8180-19A588398F52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DAA81DB-00A6-4A84-8180-19A588398F52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DAA81DB-00A6-4A84-8180-19A588398F52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DAA81DB-00A6-4A84-8180-19A588398F52}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/NuGet.Updater/Entities/ActionTextWriter.cs b/src/NuGet.Updater/Entities/ActionTextWriter.cs index 18ebb1d..b307a32 100644 --- a/src/NuGet.Updater/Entities/ActionTextWriter.cs +++ b/src/NuGet.Updater/Entities/ActionTextWriter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text; -namespace Nuget.Updater.Entities +namespace NuGet.Updater.Entities { public class ActionTextWriter : TextWriter { diff --git a/src/NuGet.Updater/Entities/FeedNuGetVersion.cs b/src/NuGet.Updater/Entities/FeedNuGetVersion.cs index d5d2d69..9b40807 100644 --- a/src/NuGet.Updater/Entities/FeedNuGetVersion.cs +++ b/src/NuGet.Updater/Entities/FeedNuGetVersion.cs @@ -1,7 +1,7 @@ using System; using NuGet.Versioning; -namespace Nuget.Updater.Entities +namespace NuGet.Updater.Entities { public class FeedNuGetVersion { diff --git a/src/NuGet.Updater/Entities/Logger.cs b/src/NuGet.Updater/Entities/Logger.cs index c0053e6..4c9a589 100644 --- a/src/NuGet.Updater/Entities/Logger.cs +++ b/src/NuGet.Updater/Entities/Logger.cs @@ -5,11 +5,11 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Nuget.Updater.Extensions; -using Nuget.Updater.Helpers; +using NuGet.Updater.Extensions; +using NuGet.Updater.Helpers; using NuGet.Versioning; -namespace Nuget.Updater.Entities +namespace NuGet.Updater.Entities { public class Logger { diff --git a/src/NuGet.Updater/Entities/NuGetPackage.cs b/src/NuGet.Updater/Entities/NuGetPackage.cs index bf9f8f4..669f0e6 100644 --- a/src/NuGet.Updater/Entities/NuGetPackage.cs +++ b/src/NuGet.Updater/Entities/NuGetPackage.cs @@ -3,7 +3,7 @@ using System.Linq; using NuGet.Protocol.Core.Types; -namespace Nuget.Updater.Entities +namespace NuGet.Updater.Entities { public class NuGetPackage { diff --git a/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs b/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs index cd2c843..7df2e97 100644 --- a/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs +++ b/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using NuGet.Versioning; -namespace Nuget.Updater +namespace NuGet.Updater { public static class NuGetVersionExtensions { diff --git a/src/NuGet.Updater/Entities/UpdateOperation.cs b/src/NuGet.Updater/Entities/UpdateOperation.cs index 1400533..5c41cb9 100644 --- a/src/NuGet.Updater/Entities/UpdateOperation.cs +++ b/src/NuGet.Updater/Entities/UpdateOperation.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using NuGet.Versioning; -namespace Nuget.Updater.Entities +namespace NuGet.Updater.Entities { public class UpdateOperation { diff --git a/src/NuGet.Updater/Entities/UpdateTarget.cs b/src/NuGet.Updater/Entities/UpdateTarget.cs index 16cfa56..d0a319e 100644 --- a/src/NuGet.Updater/Entities/UpdateTarget.cs +++ b/src/NuGet.Updater/Entities/UpdateTarget.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Nuget.Updater +namespace NuGet.Updater { ///

/// The type of files to update diff --git a/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs b/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs index 3eeae8d..5ef9e9f 100644 --- a/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs +++ b/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using NuGet.Common; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { public static class EnumerableAsyncExtensions { diff --git a/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs b/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs index d7f0934..4bd065d 100644 --- a/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs +++ b/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs @@ -4,9 +4,9 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { public static class FeedNuGetVersionExtensions { diff --git a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs index 8896ed1..add86bb 100644 --- a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs +++ b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { public static class NuGetPackageExtensions { diff --git a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs index 9ab9c08..1817e4d 100644 --- a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs @@ -2,13 +2,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol; using NuGet.Protocol.Core.Types; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { public static class PackageSourceExtensions { diff --git a/src/NuGet.Updater/Extensions/ParametersExtension.cs b/src/NuGet.Updater/Extensions/ParametersExtension.cs index 2d9e4c0..a64fc90 100644 --- a/src/NuGet.Updater/Extensions/ParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/ParametersExtension.cs @@ -3,10 +3,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; using NuGet.Configuration; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { internal static class ParametersExtension { diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs index fa7acff..02c668d 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs @@ -4,10 +4,10 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; using NuGet.Versioning; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { partial class XmlDocumentExtensions { diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs index 67019a7..51ce361 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs @@ -7,14 +7,14 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; using NuGet.Versioning; using Windows.Data.Xml.Dom; using Windows.Storage; using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; using XmlElement = Windows.Data.Xml.Dom.XmlElement; -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { partial class XmlDocumentExtensions { diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index def3c42..ff191cf 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; using NuGet.Versioning; #if UAP @@ -10,7 +10,7 @@ using XmlDocument = System.Xml.XmlDocument; #endif -namespace Nuget.Updater.Extensions +namespace NuGet.Updater.Extensions { public static partial class XmlDocumentExtensions { diff --git a/src/NuGet.Updater/Helpers/FileHelper.Net.cs b/src/NuGet.Updater/Helpers/FileHelper.Net.cs index bb0f090..b424d94 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.Net.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Nuget.Updater.Helpers +namespace NuGet.Updater.Helpers { public static class FileHelper { diff --git a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs index ef287c1..021da85 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs @@ -8,7 +8,7 @@ using Windows.Storage; using Windows.Storage.Search; -namespace Nuget.Updater.Helpers +namespace NuGet.Updater.Helpers { public static class FileHelper { diff --git a/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs b/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs index e655d0a..175701d 100644 --- a/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs +++ b/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs @@ -5,7 +5,7 @@ using System.Xml; using NuGet.Versioning; -namespace Nuget.Updater +namespace NuGet.Updater { public class NuGetBranchSwitch { diff --git a/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs b/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs index d21eea7..ecc6493 100644 --- a/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs +++ b/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs @@ -3,7 +3,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Nuget.Updater +namespace NuGet.Updater { public class NuGetBranchSwitchTask : Task { diff --git a/src/NuGet.Updater/NuGetUpdater.Execution.cs b/src/NuGet.Updater/NuGetUpdater.Execution.cs index ed10abb..948173d 100644 --- a/src/NuGet.Updater/NuGetUpdater.Execution.cs +++ b/src/NuGet.Updater/NuGetUpdater.Execution.cs @@ -3,9 +3,9 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; -namespace Nuget.Updater +namespace NuGet.Updater { partial class NuGetUpdater { diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index ae36cc8..4f04ede 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -4,9 +4,9 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Nuget.Updater.Entities; -using Nuget.Updater.Extensions; -using Nuget.Updater.Helpers; +using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using NuGet.Updater.Helpers; using NuGet.Configuration; using NuGet.Versioning; @@ -16,7 +16,7 @@ using XmlDocument = System.Xml.XmlDocument; #endif -namespace Nuget.Updater +namespace NuGet.Updater { public partial class NuGetUpdater { diff --git a/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs b/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs index d3e4ad9..b4b17a3 100644 --- a/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs +++ b/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using Nuget.Updater.Entities; +using NuGet.Updater.Entities; using NuGet.Configuration; -namespace Nuget.Updater +namespace NuGet.Updater { partial class NuGetUpdater { From 92d527613414a5b467c2f03e841170f6676257e4 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 18 Jul 2019 16:26:07 -0400 Subject: [PATCH 084/201] Added stylecop --- src/Directory.Build.props | 49 ++++ src/NuGet.Updater.Tool/Program.cs | 11 +- src/NuGet.Updater/Entities/Logger.cs | 7 +- .../Entities/NuGetVersionExtensions.cs | 2 - src/NuGet.Updater/Entities/UpdateTarget.cs | 43 +++- .../Entities/UpdaterParameters.cs | 79 +++++++ .../Extensions/FeedNuGetVersionExtensions.cs | 2 +- .../Extensions/NuGetPackageExtensions.cs | 2 +- ...nsion.cs => UpdaterParametersExtension.cs} | 20 +- .../Extensions/XmlDocumentExtensions.Net.cs | 28 +-- .../Extensions/XmlDocumentExtensions.UAP.cs | 5 +- .../Extensions/XmlDocumentExtensions.cs | 7 +- src/NuGet.Updater/Helpers/FileHelper.UAP.cs | 4 +- .../Legacy/NuGetBranchSwitchExecution.cs | 212 ------------------ .../Legacy/NuGetBranchSwitchTask.cs | 24 -- src/NuGet.Updater/NuGetUpdater.Execution.cs | 29 ++- src/NuGet.Updater/NuGetUpdater.cs | 83 +++---- .../NuGetUpdater/NuGetUpdater.Parameters.cs | 84 ------- src/nuget.updater.ruleset | 151 +++++++++++++ 19 files changed, 412 insertions(+), 430 deletions(-) create mode 100644 src/Directory.Build.props create mode 100644 src/NuGet.Updater/Entities/UpdaterParameters.cs rename src/NuGet.Updater/Extensions/{ParametersExtension.cs => UpdaterParametersExtension.cs} (71%) delete mode 100644 src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs delete mode 100644 src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs delete mode 100644 src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs create mode 100644 src/nuget.updater.ruleset diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..bac4060 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,49 @@ + + + + portable + True + $(BUILD_REPOSITORY_URI) + nventive + nventive + true + true + + + 255.255.255.255 + + $(AssemblyName) ($(TargetFramework)) + en-US + + true + + + + $(MSBuildProjectName.Contains('Test')) + $(MSBuildProjectName.Contains('Sample')) + $(MSBuildThisFileDirectory)nuget.updater.ruleset + + + + + + + + + + + true + + true + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + + + + + \ No newline at end of file diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index b728f36..5242eab 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -3,8 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Mono.Options; - -using UpdaterParameters = NuGet.Updater.NuGetUpdater.Parameters; +using NuGet.Updater.Entities; namespace NuGet.Updater.Tool { @@ -14,7 +13,7 @@ public static async Task Main(string[] args) { var parameters = new UpdaterParameters { - UpdateTarget = UpdateTarget.All + UpdateTarget = UpdateTarget.All, }; var isHelp = false; @@ -41,7 +40,7 @@ public static async Task Main(string[] args) options.Parse(args); - if (isHelp) + if(isHelp) { Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); options.WriteOptionDescriptions(Console.Out); @@ -54,7 +53,7 @@ public static async Task Main(string[] args) private static bool GetBoolean(string value, bool fallbackValue = false) { - if (bool.TryParse(value, out var boolean)) + if(bool.TryParse(value, out var boolean)) { return boolean; } @@ -66,7 +65,7 @@ private static List GetList(string value) { var list = new List(); - if (value.Contains(",")) + if(value.Contains(",")) { list.AddRange(value.Split(",")); } diff --git a/src/NuGet.Updater/Entities/Logger.cs b/src/NuGet.Updater/Entities/Logger.cs index 4c9a589..04e1f5e 100644 --- a/src/NuGet.Updater/Entities/Logger.cs +++ b/src/NuGet.Updater/Entities/Logger.cs @@ -22,7 +22,7 @@ public class Logger public Logger(TextWriter writer, string summaryFilePath = null) { - writer = writer + _writer = writer #if DEBUG ?? Console.Out; #else @@ -49,7 +49,7 @@ public void Write(UpdateOperation operation) _updateOperations.Add(operation); } - public void WriteSummary(NuGetUpdater.Parameters parameters) + public void WriteSummary(UpdaterParameters parameters) { foreach (var line in GetSummary(parameters)) { @@ -69,7 +69,7 @@ public void WriteSummary(NuGetUpdater.Parameters parameters) } } - private IEnumerable GetSummary(NuGetUpdater.Parameters parameters, bool includeUrl = false) + private IEnumerable GetSummary(UpdaterParameters parameters, bool includeUrl = false) { var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); @@ -122,6 +122,7 @@ private IEnumerable GetSummary(NuGetUpdater.Parameters parameters, bool } } } + private static string GetPackageUrl(string packageId, NuGetVersion version, Uri feedUri) { if (feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) diff --git a/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs b/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs index 7df2e97..fc18728 100644 --- a/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs +++ b/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs @@ -72,7 +72,6 @@ public static bool IsGreaterThan(this NuGetVersion x, NuGetVersion y) if (xLabels != null && yLabels != null) { - result = CompareReleaseLabels(xLabels, yLabels); if (result != 0) { @@ -179,6 +178,5 @@ private static int CompareRelease(string version1, string version2) return result; } - } } diff --git a/src/NuGet.Updater/Entities/UpdateTarget.cs b/src/NuGet.Updater/Entities/UpdateTarget.cs index d0a319e..183465f 100644 --- a/src/NuGet.Updater/Entities/UpdateTarget.cs +++ b/src/NuGet.Updater/Entities/UpdateTarget.cs @@ -1,22 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NuGet.Updater +namespace NuGet.Updater.Entities { /// - /// The type of files to update + /// The type of files to update. /// public enum UpdateTarget { + /// + /// No files will be updated. + /// + Unespecified = 0, + + /// + /// .nuspec files. + /// Nuspec = 2, + + /// + /// project.json files. + /// ProjectJson = 4, + + /// + /// PackageReferences from csproj. + /// PackageReference = 8, - DirectoryProps = 16, - DirectoryTargets = 32, + /// + /// Directory.Build.props files. + /// + DirectoryProps = 16, + + /// + /// Directory.Build.targets files. + /// + DirectoryTargets = 32, + + /// + /// All the supported file types. + /// All = Nuspec | ProjectJson | PackageReference | DirectoryProps | DirectoryTargets, - } + } } diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs new file mode 100644 index 0000000..6caaa1f --- /dev/null +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NuGet.Updater.Entities +{ + public class UpdaterParameters + { + /// + /// The location of the solution to update. + /// + public string SolutionRoot { get; set; } + + /// + /// The URL of the private feed to use. + /// + public string SourceFeed { get; set; } + + /// + /// The Personal Access Token to use to access the private feed. + /// + public string SourceFeedPersonalAccessToken { get; set; } + + /// + /// The target version for the update (stable, dev, beta, etc.) + /// + public string TargetVersion { get; set; } + + /// + /// Whether it should exactly match the target version. + /// + public bool Strict { get; set; } + + /// + /// A specific tag to exclude when looking for versions. + /// + public string TagToExclude { get; set; } + + /// + /// Whether to include packages from NuGet.org. + /// + public bool IncludeNuGetOrg { get; set; } + + /// + /// Whether the packages can be downgraded if the version found is lower. + /// + public bool IsDowngradeAllowed { get; set; } + + /// + /// The type of files with NuGet references to update. + /// + public UpdateTarget UpdateTarget { get; set; } + + /// + /// A list of packages to keep at latest dev. + /// + public IEnumerable PackagesToKeepAtLatestDev { get; set; } + + /// + /// A list of packages to ignore. + /// + public IEnumerable PackagesToIgnore { get; set; } + + /// + /// A list of packages to update. Will update all packages found if nothing is specified. + /// + public IEnumerable PackagesToUpdate { get; set; } + + /// + /// Whether to use the stable version if a more recent version is available. + /// + public bool UseStableIfMoreRecent { get; set; } + + /// + /// The name of the owner of the public packages to update; specify if is set to true. + /// + public string PublickPackageOwner { get; set; } + } +} diff --git a/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs b/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs index 4bd065d..037e091 100644 --- a/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs +++ b/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs @@ -27,7 +27,7 @@ public static bool IsMatchingSpecialVersion(this FeedNuGetVersion version, strin var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; return strict - ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" + ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" } } diff --git a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs index add86bb..85ea8af 100644 --- a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs +++ b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs @@ -11,7 +11,7 @@ public static class NuGetPackageExtensions public static async Task GetLatestVersion( this NuGetPackage package, CancellationToken ct, - NuGetUpdater.Parameters parameters + UpdaterParameters parameters ) { var specialVersion = parameters.TargetVersion; diff --git a/src/NuGet.Updater/Extensions/ParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs similarity index 71% rename from src/NuGet.Updater/Extensions/ParametersExtension.cs rename to src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index a64fc90..79ec4b9 100644 --- a/src/NuGet.Updater/Extensions/ParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -1,34 +1,32 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NuGet.Updater.Entities; using NuGet.Configuration; +using NuGet.Updater.Entities; namespace NuGet.Updater.Extensions { - internal static class ParametersExtension + internal static class UpdaterParametersExtension { - internal static bool HasUpdateTarget(this NuGetUpdater.Parameters parameters, UpdateTarget target) => (parameters.UpdateTarget & target) == target; + internal static bool HasUpdateTarget(this UpdaterParameters parameters, UpdateTarget target) => (parameters.UpdateTarget & target) == target; - internal static PackageSource GetFeedPackageSource(this NuGetUpdater.Parameters parameters) => new PackageSource(parameters.SourceFeed, "Feed") + internal static PackageSource GetFeedPackageSource(this UpdaterParameters parameters) => new PackageSource(parameters.SourceFeed, "Feed") { #if UAP - Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false) + Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false), #else - Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false, null) + Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false, null), #endif }; - internal static bool ShouldUpdatePackage(this NuGetUpdater.Parameters parameters, NuGetPackage package) => + internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, NuGetPackage package) => (parameters.PackagesToIgnore == null || !parameters.PackagesToIgnore.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase)) && (parameters.PackagesToUpdate == null || parameters.PackagesToUpdate.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase)); - internal static bool ShouldKeepPackageAtLatestDev(this NuGetUpdater.Parameters parameters, string packageId) => + internal static bool ShouldKeepPackageAtLatestDev(this UpdaterParameters parameters, string packageId) => parameters.PackagesToKeepAtLatestDev != null && parameters.PackagesToKeepAtLatestDev.Contains(packageId, StringComparer.OrdinalIgnoreCase); - internal static IEnumerable GetSummary(this NuGetUpdater.Parameters parameters) + internal static IEnumerable GetSummary(this UpdaterParameters parameters) { yield return $"## Configuration"; diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs index 02c668d..fb97ad5 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs @@ -9,7 +9,10 @@ namespace NuGet.Updater.Extensions { - partial class XmlDocumentExtensions + /// + /// .Net XmlDocument extension methods. + /// + public partial class XmlDocumentExtensions { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; @@ -44,18 +47,18 @@ bool isDowngradeAllowed { var operations = new List(); - var packageReferences = document.SelectNodes($"//d:PackageReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); - var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); + var packageReferences = document.SelectNodes($"//d:PackageReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); + var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); - foreach (XmlElement packageReference in packageReferences.Concat(dotnetCliReferences)) + foreach(XmlElement packageReference in packageReferences.Concat(dotnetCliReferences)) { - if (packageReference.HasAttribute("Version")) + if(packageReference.HasAttribute("Version")) { var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if (operation.ShouldProceed) + if(operation.ShouldProceed) { packageReference.SetAttribute("Version", version.Version.ToString()); } @@ -66,13 +69,13 @@ bool isDowngradeAllowed { var node = packageReference.SelectSingleNode("d:Version", namespaceManager); - if (node != null) + if(node != null) { var currentVersion = new NuGetVersion(node.InnerText); var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if (operation.ShouldProceed) + if(operation.ShouldProceed) { node.InnerText = version.Version.ToString(); } @@ -99,7 +102,7 @@ public static async Task> GetDocument(this str { var document = new XmlDocument() { - PreserveWhitespace = true + PreserveWhitespace = true, }; document.Load(path); @@ -107,10 +110,7 @@ public static async Task> GetDocument(this str return new KeyValuePair(path, document); } - public static async Task Save(this XmlDocument document, CancellationToken ct, string path) - { - document.Save(path); - } + public static async Task Save(this XmlDocument document, CancellationToken ct, string path) => document.Save(path); } } -#endif \ No newline at end of file +#endif diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs index 51ce361..d951ed9 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs @@ -16,6 +16,9 @@ namespace NuGet.Updater.Extensions { + /// + /// UAP-specific XmlDocument extension methods. + /// partial class XmlDocumentExtensions { public static UpdateOperation[] UpdateProjectReferenceVersions( @@ -99,4 +102,4 @@ public static async Task Save(this XmlDocument document, CancellationToken ct, s } } } -#endif \ No newline at end of file +#endif diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index ff191cf..4a87629 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -12,6 +12,9 @@ namespace NuGet.Updater.Extensions { + /// + /// Shared XmlDocument extension methods. + /// public static partial class XmlDocumentExtensions { public static UpdateOperation[] UpdateNuSpecVersions( @@ -46,7 +49,5 @@ bool isDowngradeAllowed return operations.ToArray(); } - - } -} \ No newline at end of file +} diff --git a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs index 021da85..7fe10ab 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs @@ -33,7 +33,7 @@ public static async Task GetFiles( { UserSearchFilter = searchFilter, IndexerOption = IndexerOption.UseIndexerWhenAvailable, - FolderDepth = FolderDepth.Deep + FolderDepth = FolderDepth.Deep, }; var query = folder.CreateFileQueryWithOptions(queryOptions); @@ -59,4 +59,4 @@ public static async Task SetFileContent(CancellationToken ct, string path, strin } } } -#endif \ No newline at end of file +#endif diff --git a/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs b/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs deleted file mode 100644 index 175701d..0000000 --- a/src/NuGet.Updater/Legacy/NuGetBranchSwitchExecution.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml; -using NuGet.Versioning; - -namespace NuGet.Updater -{ - public class NuGetBranchSwitch - { - private static Action _log; - - private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - - private readonly string[] _packages; - private readonly string _sourceBranch; - private readonly string _targetBranch; - private readonly string _solutionRoot; - - public NuGetBranchSwitch(Action log, string solutionRoot, string[] packages, string sourceBranch, string targetBranch) - { - _log = log; - _packages = packages; - _sourceBranch = sourceBranch?.Trim() ?? ""; - _targetBranch = targetBranch?.Trim() ?? ""; - _solutionRoot = solutionRoot; - } - - public bool Execute() - { - var projectFiles = Directory.GetFiles(_solutionRoot, "*.csproj", SearchOption.AllDirectories); - var nuspecFiles = Directory.GetFiles(_solutionRoot, "*.nuspec", SearchOption.AllDirectories); - - foreach (var package in _packages) - { - UpdateProjects(package, projectFiles); - } - - foreach (var package in _packages) - { - UpdateNuSpecs(package, nuspecFiles); - } - - return true; - } - - private void UpdateProjects(string packageName, string[] projectFiles) - { - for (int i = 0; i < projectFiles.Length; i++) - { - var modified = false; - - var doc = new XmlDocument() - { - PreserveWhitespace = true - }; - doc.Load(File.OpenRead(projectFiles[i])); - - var nsmgr = new XmlNamespaceManager(doc.NameTable); - nsmgr.AddNamespace("d", MsBuildNamespace); - modified |= UpdateProjectReferenceVersions(packageName, modified, doc, nsmgr); - - var nsmgr2 = new XmlNamespaceManager(doc.NameTable); - nsmgr2.AddNamespace("d", ""); - modified |= UpdateProjectReferenceVersions(packageName, modified, doc, nsmgr2); - - if (modified) - { - doc.Save(File.OpenWrite(projectFiles[i])); - } - } - } - - private bool UpdateProjectReferenceVersions(string packageName, bool modified, XmlDocument doc, XmlNamespaceManager nsmgr, string namespaceURI = "") - { - var nodes = doc.SelectNodes($"//d:PackageReference", nsmgr) - .OfType() - .Where(e => Regex.Match(e.GetAttribute("Include"), $"^{packageName}$").Success); - - foreach (var packageReference in nodes) - { - (string version, Action updater) GetVersion() - { - if (packageReference.HasAttribute("Version", namespaceURI)) - { - return ( - packageReference.Attributes["Version"].Value, - v => packageReference.SetAttribute("Version", namespaceURI, v) - ); - } - else if (packageReference.SelectSingleNode("d:Version", nsmgr) is XmlNode node) - { - return ( - node.InnerText, - v => node.InnerText = v - ); - } - else - { - return (null, null); - } - } - - var (version, updater) = GetVersion(); - - var currentVersion = new NuGetVersion(version); - - var hasUpdateableLabel = GetHasUpdateableLabel(currentVersion); - - if (hasUpdateableLabel ?? false) - { - var newVersion = UpdateVersion(currentVersion); - - updater(newVersion.ToFullString()); - - LogUpdate(packageReference.GetAttribute("Include"), currentVersion, newVersion, doc.BaseURI); - - modified = true; - } - } - - return modified; - } - - private bool? GetHasUpdateableLabel(NuGetVersion currentVersion) - { - if (currentVersion.ReleaseLabels?.Any() ?? false) - { - return currentVersion.ReleaseLabels?.Any(l => l.Equals(_sourceBranch, StringComparison.OrdinalIgnoreCase)); - } - else - { - return string.IsNullOrEmpty(_sourceBranch); - } - } - - private NuGetVersion UpdateVersion(NuGetVersion currentVersion) - { - var updatedLabels = string.IsNullOrEmpty(_sourceBranch) - ? new[] { _targetBranch } - : currentVersion.ReleaseLabels.Select(l => l.Replace(_sourceBranch, _targetBranch)).ToArray(); - - var newVersion = new NuGetVersion( - major: currentVersion.Major, - minor: currentVersion.Minor, - patch: currentVersion.Patch, - revision: currentVersion.Revision, - releaseLabels: updatedLabels, - metadata: currentVersion.Metadata - ); - - return newVersion; - } - - private void UpdateNuSpecs(string packageName, string[] nuspecFiles) - { - foreach (var nuspecFile in nuspecFiles) - { - var doc = new XmlDocument() - { - PreserveWhitespace = true - }; - doc.Load(File.OpenRead(nuspecFile)); - - - var mgr = new XmlNamespaceManager(doc.NameTable); - mgr.AddNamespace("x", doc.DocumentElement.NamespaceURI); - - var nodes = doc - .SelectNodes($"//x:dependency", mgr) - .OfType() - .Where(e => Regex.Match(e.GetAttribute("id"), $"^{packageName}$").Success); - - foreach (var node in nodes) - { - var versionNodeValue = node.GetAttribute("version"); - - // only nodes with explicit version, skip expansion. - if (!versionNodeValue.Contains("{")) - { - var currentVersion = new NuGetVersion(versionNodeValue); - - var hasUpdateableLabel = GetHasUpdateableLabel(currentVersion); - - if (hasUpdateableLabel ?? false) - { - var newVersion = UpdateVersion(currentVersion); - - node.SetAttribute("version", newVersion.ToFullString()); - - LogUpdate(node.GetAttribute("id"), currentVersion, newVersion, doc.BaseURI); - } - } - } - - if (nodes.Any()) - { - doc.Save(File.OpenWrite(nuspecFile)); - } - } - } - - private static void LogUpdate(string packageName, NuGetVersion currentVersion, NuGetVersion newVersion, string file) - { - _log($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); -#if DEBUG - Console.WriteLine($"Updating [{packageName}] from [{currentVersion}] to [{newVersion}] in [{file}]"); -#endif - } - } -} \ No newline at end of file diff --git a/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs b/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs deleted file mode 100644 index ecc6493..0000000 --- a/src/NuGet.Updater/Legacy/NuGetBranchSwitchTask.cs +++ /dev/null @@ -1,24 +0,0 @@ -#if !UAP && !NETSTANDARD -using System; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace NuGet.Updater -{ - public class NuGetBranchSwitchTask : Task - { - [Required] - public string SolutionRoot { get; set; } - - [Required] - public string[] Packages { get; set; } - - public string SourceBranch { get; set; } - - [Required] - public string TargetBranch { get; set; } - - public override bool Execute() => new NuGetBranchSwitch(message => Log.LogMessage(message), SolutionRoot, Packages, SourceBranch, TargetBranch).Execute(); - } -} -#endif \ No newline at end of file diff --git a/src/NuGet.Updater/NuGetUpdater.Execution.cs b/src/NuGet.Updater/NuGetUpdater.Execution.cs index 948173d..f499393 100644 --- a/src/NuGet.Updater/NuGetUpdater.Execution.cs +++ b/src/NuGet.Updater/NuGetUpdater.Execution.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -7,14 +6,17 @@ namespace NuGet.Updater { - partial class NuGetUpdater + /// + /// Static execution methods for the NuGetUpdater. + /// + public partial class NuGetUpdater { public static bool Update( string solutionRoot, string sourceFeed, string targetVersion, string excludeTag = "", - string PAT = "", + string feedAccessToken = "", bool includeNuGetOrg = true, string publicPackageOwner = null, bool allowDowngrade = false, @@ -26,15 +28,13 @@ public static bool Update( TextWriter logWriter = null, string summaryOutputFilePath = null, bool useStableIfMoreRecent = false - ) - { - return UpdateAsync( + ) => UpdateAsync( CancellationToken.None, solutionRoot, sourceFeed, targetVersion, excludeTag, - PAT, + feedAccessToken, includeNuGetOrg, publicPackageOwner, allowDowngrade, @@ -47,10 +47,9 @@ public static bool Update( summaryOutputFilePath, useStableIfMoreRecent ).Result; - } public static bool Update( - Parameters parameters, + UpdaterParameters parameters, TextWriter logWriter = null, string summaryOutputFilePath = null ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logWriter, summaryOutputFilePath)).Result; @@ -61,7 +60,7 @@ public static async Task UpdateAsync( string sourceFeed, string targetVersion, string excludeTag = "", - string feedPat = "", + string feedAccessToken = "", bool includeNuGetOrg = true, string publicPackageOwner = null, bool isDowngradeAllowed = false, @@ -75,11 +74,11 @@ public static async Task UpdateAsync( bool useStableIfMoreRecent = false ) { - var parameters = new Parameters + var parameters = new UpdaterParameters { SolutionRoot = solutionRoot, SourceFeed = sourceFeed, - SourceFeedPersonalAccessToken = feedPat, + SourceFeedPersonalAccessToken = feedAccessToken, TargetVersion = targetVersion, Strict = strict, TagToExclude = excludeTag, @@ -98,14 +97,14 @@ public static async Task UpdateAsync( public static Task UpdateAsync( CancellationToken ct, - Parameters parameters, + UpdaterParameters parameters, TextWriter logWriter = null, string summaryOutputFilePath = null ) => UpdateAsync(ct, parameters, new Logger(logWriter, summaryOutputFilePath)); public static async Task UpdateAsync( CancellationToken ct, - Parameters parameters, + UpdaterParameters parameters, Logger log ) { diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 4f04ede..a80de2c 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -18,14 +17,17 @@ namespace NuGet.Updater { + /// + /// NuGet Updater implementation. + /// public partial class NuGetUpdater { private static readonly PackageSource NuGetOrgSource = new PackageSource("https://api.nuget.org/v3/index.json"); - private readonly Parameters _parameters; + private readonly UpdaterParameters _parameters; private readonly Logger _log; - private NuGetUpdater(Parameters parameters, Logger log) + private NuGetUpdater(UpdaterParameters parameters, Logger log) { _parameters = parameters; _log = log; @@ -42,7 +44,7 @@ private async Task UpdatePackages(CancellationToken ct) { var latest = await package.GetLatestVersion(ct, _parameters); - if (latest == null) + if(latest == null) { _log.Write($"Skipping [{package.PackageId}]: no {_parameters.TargetVersion} version found."); continue; @@ -54,7 +56,7 @@ private async Task UpdatePackages(CancellationToken ct) { var updates = new UpdateOperation[0]; - switch (files.Key) + switch(files.Key) { case UpdateTarget.Nuspec: updates = await UpdateNuSpecs(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); @@ -62,10 +64,10 @@ private async Task UpdatePackages(CancellationToken ct) case UpdateTarget.ProjectJson: updates = await UpdateProjectJson(ct, package.PackageId, latest, files.Value.Select(p => p.Key).ToArray(), _parameters.IsDowngradeAllowed); break; - case UpdateTarget.DirectoryProps: - case UpdateTarget.DirectoryTargets: - case UpdateTarget.PackageReference: - updates = await UpdateProjects(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); + case UpdateTarget.DirectoryProps: + case UpdateTarget.DirectoryTargets: + case UpdateTarget.PackageReference: + updates = await UpdateProjects(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); break; default: break; @@ -85,7 +87,7 @@ private async Task GetPackages(CancellationToken ct) //Using search instead of list because the latter forces the v2 api var packages = await _parameters.GetFeedPackageSource().SearchPackages(ct, _log.Write); - if (_parameters.IncludeNuGetOrg) + if(_parameters.IncludeNuGetOrg) { //Using search instead of list because the latter forces the v2 api packages = packages @@ -102,17 +104,18 @@ private async Task>> Ge { var targetFiles = new Dictionary>(); - var updateTarget = new[] { - UpdateTarget.Nuspec, - UpdateTarget.PackageReference, - UpdateTarget.ProjectJson, - UpdateTarget.DirectoryProps, - UpdateTarget.DirectoryTargets, - }; - - foreach (var target in updateTarget) + var updateTarget = new[] + { + UpdateTarget.Nuspec, + UpdateTarget.PackageReference, + UpdateTarget.ProjectJson, + UpdateTarget.DirectoryProps, + UpdateTarget.DirectoryTargets, + }; + + foreach(var target in updateTarget) { - if (_parameters.HasUpdateTarget(target)) + if(_parameters.HasUpdateTarget(target)) { targetFiles.Add(target, await GetFilesForTarget(ct, target)); } @@ -125,7 +128,7 @@ private async Task> GetFilesForTarget(Cancellati { string extensionFilter = null, nameFilter = null; - switch (target) + switch(target) { case UpdateTarget.Nuspec: extensionFilter = ".nuspec"; @@ -135,23 +138,23 @@ private async Task> GetFilesForTarget(Cancellati nameFilter = "project.json"; break; - case UpdateTarget.PackageReference: - extensionFilter = ".csproj"; - break; + case UpdateTarget.PackageReference: + extensionFilter = ".csproj"; + break; - case UpdateTarget.DirectoryTargets: - nameFilter = "Directory.Build.targets"; - break; + case UpdateTarget.DirectoryTargets: + nameFilter = "Directory.Build.targets"; + break; - case UpdateTarget.DirectoryProps: - nameFilter = "Directory.Build.props"; - break; + case UpdateTarget.DirectoryProps: + nameFilter = "Directory.Build.props"; + break; - default: + default: break; } - if (extensionFilter == null && nameFilter == null) + if(extensionFilter == null && nameFilter == null) { return new Dictionary(); } @@ -181,14 +184,14 @@ bool isDowngradeAllowed { var operations = new List(); - foreach (var document in nuspecDocuments) + foreach(var document in nuspecDocuments) { var path = document.Key; var xmlDocument = document.Value; var updates = xmlDocument.UpdateNuSpecVersions(packageId, version, path, isDowngradeAllowed); - if (updates.Any(u => u.ShouldProceed)) + if(updates.Any(u => u.ShouldProceed)) { await xmlDocument.Save(ct, path); } @@ -209,14 +212,14 @@ bool isDowngradeAllowed { var operations = new List(); - foreach (var document in projectDocuments) + foreach(var document in projectDocuments) { var path = document.Key; var xmlDocument = document.Value; var updates = xmlDocument.UpdateProjectReferenceVersions(packageId, version, path, isDowngradeAllowed); - if (updates.Any(u => u.ShouldProceed)) + if(updates.Any(u => u.ShouldProceed)) { await xmlDocument.Save(ct, path); } @@ -240,19 +243,19 @@ bool isDowngradeAllowed var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; var replaced = $@"""{packageName}"": ""{latestVersion.Version.ToString()}"""; - for (int i = 0; i < jsonFiles.Length; i++) + for(var i = 0; i < jsonFiles.Length; i++) { var file = jsonFiles[i]; var fileContent = await FileHelper.ReadFileContent(ct, file); var match = Regex.Match(fileContent, originalMatch, RegexOptions.IgnoreCase); - if (match?.Success ?? false) + if(match?.Success ?? false) { var currentVersion = new NuGetVersion(match.Groups[1].Value); var operation = new UpdateOperation(isDowngradeAllowed, packageName, currentVersion, latestVersion, file); - if (operation.ShouldProceed) + if(operation.ShouldProceed) { var newContent = Regex.Replace( fileContent, diff --git a/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs b/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs deleted file mode 100644 index b4b17a3..0000000 --- a/src/NuGet.Updater/NuGetUpdater/NuGetUpdater.Parameters.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NuGet.Updater.Entities; -using NuGet.Configuration; - -namespace NuGet.Updater -{ - partial class NuGetUpdater - { - public class Parameters - { - /// - /// The location of the solution to update. - /// - public string SolutionRoot { get; set; } - - /// - /// The URL of the private feed to use. - /// - public string SourceFeed { get; set; } - - /// - /// The Personal Access Token to use to access the private feed. - /// - public string SourceFeedPersonalAccessToken { get; set; } - - /// - /// The target version for the update (stable, dev, beta, etc.) - /// - public string TargetVersion { get; set; } - - /// - /// Whether it should exactly match the target version. - /// - public bool Strict { get; set; } - - /// - /// A specific tag to exclude when looking for versions. - /// - public string TagToExclude { get; set; } - - /// - /// Whether to include packages from NuGet.org - /// - public bool IncludeNuGetOrg { get; set; } - - /// - /// Whether the packages can be downgraded if the version found is lower. - /// - public bool IsDowngradeAllowed { get; set; } - - /// - /// The type of files with NuGet references to update. - /// - public UpdateTarget UpdateTarget { get; set; } - - /// - /// A list of packages to keep at latest dev. - /// - public IEnumerable PackagesToKeepAtLatestDev { get; set; } - - /// - /// A list of packages to ignore. - /// - public IEnumerable PackagesToIgnore { get; set; } - - /// - /// A list of packages to update. Will update all packages found if nothing is specified. - /// - public IEnumerable PackagesToUpdate { get; set; } - - /// - /// Whether to use the stable version if a more recent version is available - /// - public bool UseStableIfMoreRecent { get; set; } - - /// - /// The name of the owner of the public packages to update; specify if is set to true - /// - public string PublickPackageOwner { get; set; } - } - } -} diff --git a/src/nuget.updater.ruleset b/src/nuget.updater.ruleset new file mode 100644 index 0000000..b6377ea --- /dev/null +++ b/src/nuget.updater.ruleset @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fa99f98126c96289a58934fa3f2aec2fa7e685d0 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 18 Jul 2019 16:26:21 -0400 Subject: [PATCH 085/201] Added misc MD files --- .editorconfig | 223 +++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE.md | 17 +++ .github/PULL_REQUEST_TEMPLATE.md | 42 ++++++ CHANGELOG.md | 19 +++ CODE_OF_CONDUCT.md | 78 +++++++++++ CONTRIBUTING.md | 86 ++++++++++++ LICENSE | 203 ++++++++++++++++++++++++++++ README.md | 36 +++++ 8 files changed, 704 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..af08010 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,223 @@ +root = true + +[*] +end_of_line = crlf +tab_width = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_indent_block_contents +csharp_indent_block_contents = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_indent_braces +csharp_indent_braces = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_indent_case_contents +csharp_indent_case_contents = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_indent_labels +csharp_indent_labels = one_less_than_current + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_indent_switch_labels +csharp_indent_switch_labels = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_before_catch +csharp_new_line_before_catch = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_before_else +csharp_new_line_before_else = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_before_finally +csharp_new_line_before_finally = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_before_members_in_anonymous_types +csharp_new_line_before_members_in_anonymous_types = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_before_members_in_object_initializers +csharp_new_line_before_members_in_object_initializers = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_before_open_brace +csharp_new_line_before_open_brace = all + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_new_line_between_query_expression_clauses +csharp_new_line_between_query_expression_clauses = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_prefer_braces +csharp_prefer_braces = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_prefer_simple_default_expression +csharp_prefer_simple_default_expression = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_preserve_single_line_blocks +csharp_preserve_single_line_blocks = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_preserve_single_line_statements +csharp_preserve_single_line_statements = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_after_cast +csharp_space_after_cast = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_after_colon_in_inheritance_clause +csharp_space_after_colon_in_inheritance_clause = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_after_comma +csharp_space_after_comma = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_after_dot +csharp_space_after_dot = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_after_keywords_in_control_flow_statements +csharp_space_after_keywords_in_control_flow_statements = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_after_semicolon_in_for_statement +csharp_space_after_semicolon_in_for_statement = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_around_binary_operators +csharp_space_around_binary_operators = before_and_after + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_around_declaration_statements +csharp_space_around_declaration_statements = do_not_ignore + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_before_colon_in_inheritance_clause +csharp_space_before_colon_in_inheritance_clause = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_before_comma +csharp_space_before_comma = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_before_dot +csharp_space_before_dot = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_before_open_square_brackets +csharp_space_before_open_square_brackets = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_before_semicolon_in_for_statement +csharp_space_before_semicolon_in_for_statement = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_empty_square_brackets +csharp_space_between_empty_square_brackets = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_method_call_empty_parameter_list_parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_method_call_name_and_opening_parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_method_call_parameter_list_parentheses +csharp_space_between_method_call_parameter_list_parentheses = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_method_declaration_empty_parameter_list_parentheses +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_method_declaration_name_and_open_parenthesis +csharp_space_between_method_declaration_name_and_open_parenthesis = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_method_declaration_parameter_list_parentheses +csharp_space_between_method_declaration_parameter_list_parentheses = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_parentheses +csharp_space_between_parentheses = none + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_space_between_square_brackets +csharp_space_between_square_brackets = false + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_conditional_delegate_call +csharp_style_conditional_delegate_call = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_accessors +csharp_style_expression_bodied_accessors = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_constructors +csharp_style_expression_bodied_constructors = false:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_indexers +csharp_style_expression_bodied_indexers = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_methods +csharp_style_expression_bodied_methods = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_operators +csharp_style_expression_bodied_operators = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_expression_bodied_properties +csharp_style_expression_bodied_properties = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_inlined_variable_declaration +csharp_style_inlined_variable_declaration = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_pattern_matching_over_as_with_null_check +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_pattern_matching_over_is_with_cast_check +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_throw_expression +csharp_style_throw_expression = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_var_elsewhere +csharp_style_var_elsewhere = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_var_for_built_in_types +csharp_style_var_for_built_in_types = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#csharp_style_var_when_type_is_apparent +csharp_style_var_when_type_is_apparent = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_sort_system_directives_first +dotnet_sort_system_directives_first = true + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_coalesce_expression +dotnet_style_coalesce_expression = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_collection_initializer +dotnet_style_collection_initializer = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_explicit_tuple_names +dotnet_style_explicit_tuple_names = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_null_propagation +dotnet_style_null_propagation = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_object_initializer +dotnet_style_object_initializer = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_predefined_type_for_locals_parameters_members +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_predefined_type_for_member_access +dotnet_style_predefined_type_for_member_access = true:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_qualification_for_event +dotnet_style_qualification_for_event = false:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_qualification_for_field +dotnet_style_qualification_for_field = false:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_qualification_for_method +dotnet_style_qualification_for_method = false:suggestion + +# http://kent-boogaart.com/blog/editorconfig-reference-for-c-developers#dotnet_style_qualification_for_property +dotnet_style_qualification_for_property = false:suggestion + + +[{*.xaml, *.xml, *.targets, *.props}] +indent_style = tab + +[{*.cs, *.tt}] +indent_style = tab + +[{*.ts, *.json}] +indent_style = tab + +[*.csproj] +indent_style = tab +tab_width = 2 +indent_size = 2 + +[*.js] +indent_style = space + +[*.yml] +indent_style = space + +[*.md] +indent_style = space \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..965c7bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,17 @@ +## Expected Behavior + + +## Actual Behavior + + +## Steps to Reproduce the Problem + +1. +2. +3. + +## Specifications + +- Version: +- Platform: +- Subsystem: \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..bc95e67 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,42 @@ +GitHub Issue: # + + +## Proposed Changes + + + + + + + + + + + +## What is the current behavior? + + + +## What is the new behavior? + + + +## Checklist + +Please check if your PR fulfills the following requirements: + +- [ ] Documentation has been added/updated +- [ ] Automated Unit / Integration tests for the changes have been added/updated +- [ ] Contains **NO** breaking changes +- [ ] Updated the Changelog +- [ ] Associated with an issue + + + + +## Other information + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1903f33 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..79037a6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,78 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and +expression, level of experience, education, socio-economic status, nationality, +personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at info@nventive.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an +incident. Further details of specific enforcement policies may be posted +separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..164f871 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,86 @@ +# How to Contribute + +We'd love to accept your patches, contributions and suggestions to this project. +Here are a few small guidelines you need to follow. + +## Code of conduct + +To better foster an open, innovative and inclusive community please refer to our +[Code of Conduct](CODE_OF_CONDUCT.md) when contributing. + +### Report a bug + +If you think you've found a bug, please log a new issue in the [GitHub issue +tracker. When filing issues, please use our [issue +template](.github/ISSUE_TEMPLATE.md). The best way to get your bug fixed is to +be as detailed as you can be about the problem. Providing a minimal project with +steps to reproduce the problem is ideal. Here are questions you can answer +before you file a bug to make sure you're not missing any important information. + +1. Did you read the documentation? +2. Did you include the snippet of broken code in the issue? +3. What are the *EXACT* steps to reproduce this problem? +4. What specific version or build are you using? +5. What operating system are you using? + +GitHub supports +[markdown](https://help.github.com/articles/github-flavored-markdown/), so when +filing bugs make sure you check the formatting before clicking submit. + +### Make a suggestion + +If you have an idea for a new feature or enhancement let us know by filing an +issue. To help us understand and prioritize your idea please provide as much +detail about your scenario and why the feature or enhancement would be useful. + +## Contributing code and content + +This is an open source project and we welcome code and content contributions +from the community. + +**Identifying the scale** + +If you would like to contribute to this project, first identify the scale of +what you would like to contribute. If it is small (grammar/spelling or a bug +fix) feel free to start working on a fix. + +If you are submitting a feature or substantial code contribution, please discuss +it with the team. You might also read these two blogs posts on contributing +code: [Open Source Contribution +Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza +and [Don't "Push" Your Pull +Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by +Ilya Grigorik. Note that all code submissions will be rigorously reviewed and +tested by the project team, and only those that meet an extremely high bar for +both quality and design/roadmap appropriateness will be merged into the source. + +**Obtaining the source code** + +If you are an outside contributor, please fork the repository to your account. +See the GitHub documentation for [forking a +repo](https://help.github.com/articles/fork-a-repo/) if you have any questions +about this. + +**Submitting a pull request** + +If you don't know what a pull request is read this article: +https://help.github.com/articles/using-pull-requests. Make sure the repository +can build and all tests pass, as well as follow the current coding guidelines. +When submitting a pull request, please use our [pull request +template](.github/PULL_REQUEST_TEMPLATE.md). + +Pull requests should all be done to the **master** branch. + +--- + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult [GitHub +Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dace5dd --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 nventive. + All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d54a7a8 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# NuGet Updater + +NuGet updates made easy. + +NuGet Updater allows batch updates of NuGet packages in a solution. + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) + +## Getting Started + +The NuGet Updater can be installed as a standalone .Net Core tool using the following command: `dotnet tool install -g nuget.updater.tool` + +The NuGet updater library can also be installed as a NuGet package in a UWP or WPF application. + +## Features + +Update NuGet packages to the latest version with a specific tag (i.e. dev, alpha, etc.). + +## Changelog + +Please consult the [CHANGELOG](CHANGELOG.md) for more information about version +history. + +## License + +This project is licensed under the Apache 2.0 license - see the +[LICENSE](LICENSE) file for details. + +## Contributing + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the process for +contributing to this project. + +Be mindful of our [Code of Conduct](CODE_OF_CONDUCT.md). + +## Acknowledgments From 284247b50b24b22a0796dcde7d7d653c62b37021 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 18 Jul 2019 16:35:32 -0400 Subject: [PATCH 086/201] Added NuGet metadata --- src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj | 2 ++ src/NuGet.Updater/NuGet.Updater.csproj | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index f495c94..f0fc03d 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -19,6 +19,8 @@ nventive Apache-2.0 https://nv-assets.azurewebsites.net/logos/nv_small_square_logo.jpg + + https://dev.azure.com/nventive/Open%20Source/_git/NuGet.Updater diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj index 8e21f96..fce9b6e 100644 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -7,8 +7,14 @@ + nventive.NuGet.Updater nventive + nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time + Apache-2.0 + https://nv-assets.azurewebsites.net/logos/nv_small_square_logo.jpg + + https://dev.azure.com/nventive/Open%20Source/_git/NuGet.Updater From 6a870f7fde5cd2a801a222ac4fee21bdc2e6661c Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 18 Jul 2019 16:37:00 -0400 Subject: [PATCH 087/201] Added build definition --- .azure-pipelines.yml | 37 +++++++++++++++++++++++++++++++++++++ src/NuGet.Updater.sln | 12 ++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .azure-pipelines.yml diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml new file mode 100644 index 0000000..ec7413f --- /dev/null +++ b/.azure-pipelines.yml @@ -0,0 +1,37 @@ +pool: 'Hosted Windows 2019 with VS2019' + +trigger: + batch: 'true' + branches: + include: + - master + +variables: +- name: PackageOutputPath + value: '$(Build.ArtifactStagingDirectory)' + +steps: +- task: GitVersion@4 + +- task: NuGetToolInstaller@1 + inputs: + versionSpec: 5.0.2 + +- task: NuGetCommand@2 + inputs: + command: restore + restoreSolution: src\Nuget.Updater.sln + includeNuGetOrg: true + +- task: MSBuild@1 + inputs: + solution: src\Nuget.Updater.sln + configuration: Release + platform: 'Any CPU' + msbuildArguments: /p:PackageVersion=$(GitVersion.NuGetVersion) + +- task: PublishBuildArtifacts@1 + inputs: + ArtifactName: Packages + PathtoPublish: $(PackageOutputPath) + publishLocation: Container diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index 0d8b11c..8dfe06d 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -7,6 +7,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater", "Nuget.Upda EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tool", "NuGet.Updater.Tool\NuGet.Updater.Tool.csproj", "{3DAA81DB-00A6-4A84-8180-19A588398F52}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0547178F-3639-4797-940F-265958B7FECA}" + ProjectSection(SolutionItems) = preProject + ..\.azure-pipelines.yml = ..\.azure-pipelines.yml + ..\.editorconfig = ..\.editorconfig + ..\.gitignore = ..\.gitignore + ..\CHANGELOG.md = ..\CHANGELOG.md + ..\CODE_OF_CONDUCT.md = ..\CODE_OF_CONDUCT.md + ..\CONTRIBUTING.md = ..\CONTRIBUTING.md + ..\LICENSE = ..\LICENSE + ..\README.md = ..\README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From 205a668197bd496def88759d225e1e08f282f5e5 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 19 Jul 2019 15:35:45 +0000 Subject: [PATCH 088/201] Removed Umbrella reference from Gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index af54091..6f62953 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ .*/ !.github/ -# Umbrella -umbrella_installlog.txt Resource.Designer.cs *.g.cs From f75f72a3a64a9fb1660b379e4a54f36943d43887 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 23 Jul 2019 10:15:37 -0400 Subject: [PATCH 089/201] Updated icon and package url --- src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj | 5 ++--- src/NuGet.Updater/NuGet.Updater.csproj | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index f0fc03d..d78eb7a 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -18,9 +18,8 @@ nventive nventive Apache-2.0 - https://nv-assets.azurewebsites.net/logos/nv_small_square_logo.jpg - - https://dev.azure.com/nventive/Open%20Source/_git/NuGet.Updater + https://nventive-email-assets.s3.amazonaws.com/packages/nv-package-logo%400%2C25x.png + https://github.com/nventive/NuGet.Updater diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj index fce9b6e..56b698e 100644 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -12,9 +12,8 @@ nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time Apache-2.0 - https://nv-assets.azurewebsites.net/logos/nv_small_square_logo.jpg - - https://dev.azure.com/nventive/Open%20Source/_git/NuGet.Updater + https://nventive-email-assets.s3.amazonaws.com/packages/nv-package-logo%400%2C25x.png + https://github.com/nventive/NuGet.Updater From cbb7b3e9a576dc3b8829dc40ceaca42abe9a856c Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 23 Jul 2019 10:15:55 -0400 Subject: [PATCH 090/201] netcore app now defaults to help --- src/NuGet.Updater.Tool/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 5242eab..dbfdc1d 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -11,6 +11,11 @@ public class Program { public static async Task Main(string[] args) { + if(args == null || args.Length == 0) + { + args = new[] { "help" }; + } + var parameters = new UpdaterParameters { UpdateTarget = UpdateTarget.All, From 86e913b8c778e6efb803e9734c9006817bd08323 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 23 Jul 2019 15:34:02 -0400 Subject: [PATCH 091/201] Changed gitversion mode to mainline --- gitversion.yml | 2 ++ src/NuGet.Updater.sln | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 gitversion.yml diff --git a/gitversion.yml b/gitversion.yml new file mode 100644 index 0000000..733bd80 --- /dev/null +++ b/gitversion.yml @@ -0,0 +1,2 @@ +mode: Mainline +increment: patch \ No newline at end of file diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index 8dfe06d..8957c4f 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.757 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29021.104 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater", "Nuget.Updater\NuGet.Updater.csproj", "{29277270-8EFE-41A3-8CD8-D54EBF514C41}" EndProject @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\CHANGELOG.md = ..\CHANGELOG.md ..\CODE_OF_CONDUCT.md = ..\CODE_OF_CONDUCT.md ..\CONTRIBUTING.md = ..\CONTRIBUTING.md + ..\gitversion.yml = ..\gitversion.yml ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection From 08f0a600efa0405cf240a26e8ff3048a845bbada Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 23 Jul 2019 11:03:03 -0400 Subject: [PATCH 092/201] Added aliases to the netcore app --- src/NuGet.Updater.Tool/Program.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index dbfdc1d..a9904ce 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -26,21 +26,21 @@ public static async Task Main(string[] args) var options = new OptionSet { - { "help", "Displays this help screen", s => isHelp = true }, - { "solution=", "The path to the solution to update", s => parameters.SolutionRoot = s }, - { "feed=", "The URL of a private NuGet feed to use", s => parameters.SourceFeed = s }, - { "pat=", "The PAT used to authenticate to the private NuGet feed", s => parameters.SourceFeedPersonalAccessToken = s }, - { "version=", "The target version to use", s => parameters.TargetVersion = s }, + { "help|h", "Displays this help screen", s => isHelp = true }, + { "solution=|s", "The path to the solution to update", s => parameters.SolutionRoot = s }, + { "feed=|f", "The URL of a private NuGet feed to use", s => parameters.SourceFeed = s }, + { "pat=|t", "The PAT used to authenticate to the private NuGet feed", s => parameters.SourceFeedPersonalAccessToken = s }, + { "version=|v", "The target version to use", s => parameters.TargetVersion = s }, { "strict=", s => parameters.Strict = GetBoolean(s) }, - { "excludeTag=", "A tag to exclude from the search", s => parameters.TagToExclude = s }, - { "useNuGetorg=", "Whether to pull packages from NuGet.org", s => parameters.IncludeNuGetOrg = GetBoolean(s) }, - { "publicPackagesOwner=", "The owner of the public packages to update; must be specified is useNuGetorg is true", s => parameters.PublickPackageOwner = s }, - { "allowDowngrade=", "Whether package downgrade is allowed", s => parameters.IsDowngradeAllowed = GetBoolean(s) }, - { "keepLatestDev=", "A comma-separated list of packages to keep at latest dev", s => parameters.PackagesToKeepAtLatestDev = GetList(s) }, - { "ignore=", "A comma-separated list of packages to ignore", s => parameters.PackagesToIgnore = GetList(s) }, - { "update=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => parameters.PackagesToUpdate = GetList(s) }, - { "useStableIfMoreRecent=", "Whether to use the latest stable if a more recent version is found", s => parameters.UseStableIfMoreRecent = GetBoolean(s) }, - { "outputFile=", "The path to a file where the update summary will be written", s => summaryFile = s }, + { "excludeTag=|e", "A tag to exclude from the search", s => parameters.TagToExclude = s }, + { "useNuGetorg=|n", "Whether to pull packages from NuGet.org", s => parameters.IncludeNuGetOrg = GetBoolean(s) }, + { "publicPackagesOwner=|o", "The owner of the public packages to update; must be specified is useNuGetorg is true", s => parameters.PublickPackageOwner = s }, + { "allowDowngrade=|d", "Whether package downgrade is allowed", s => parameters.IsDowngradeAllowed = GetBoolean(s) }, + { "keepLatestDev=|k", "A comma-separated list of packages to keep at latest dev", s => parameters.PackagesToKeepAtLatestDev = GetList(s) }, + { "ignore=|i", "A comma-separated list of packages to ignore", s => parameters.PackagesToIgnore = GetList(s) }, + { "update=|u", "A comma-separated list of packages to update; not specifying this will update all packages found", s => parameters.PackagesToUpdate = GetList(s) }, + { "useStableIfMoreRecent=|us", "Whether to use the latest stable if a more recent version is found", s => parameters.UseStableIfMoreRecent = GetBoolean(s) }, + { "outputFile=|of", "The path to a file where the update summary will be written", s => summaryFile = s }, }; options.Parse(args); From 40359516317e07050fffa8af6daf9e160d013c57 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 23 Jul 2019 11:11:00 -0400 Subject: [PATCH 093/201] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1903f33..7296680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added - +- Added aliases for .Net Core application ### Changed ### Deprecated From 3057f453f011e66a0661e264eac64b61a2616b87 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 23 Jul 2019 17:00:13 -0400 Subject: [PATCH 094/201] Changed targetversion from a string to an array This allows more control over the version selection and removes the need for "useStableIfMoreRecent --- src/NuGet.Updater.Tool/Program.cs | 30 +++++++------ .../Entities/UpdaterParameters.cs | 11 ++--- .../Extensions/NuGetPackageExtensions.cs | 44 +++++++------------ src/NuGet.Updater/NuGetUpdater.Execution.cs | 18 +++----- src/NuGet.Updater/NuGetUpdater.cs | 6 +-- 5 files changed, 46 insertions(+), 63 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index a9904ce..47e5c6c 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -27,20 +27,18 @@ public static async Task Main(string[] args) var options = new OptionSet { { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s", "The path to the solution to update", s => parameters.SolutionRoot = s }, - { "feed=|f", "The URL of a private NuGet feed to use", s => parameters.SourceFeed = s }, - { "pat=|t", "The PAT used to authenticate to the private NuGet feed", s => parameters.SourceFeedPersonalAccessToken = s }, - { "version=|v", "The target version to use", s => parameters.TargetVersion = s }, - { "strict=", s => parameters.Strict = GetBoolean(s) }, - { "excludeTag=|e", "A tag to exclude from the search", s => parameters.TagToExclude = s }, - { "useNuGetorg=|n", "Whether to pull packages from NuGet.org", s => parameters.IncludeNuGetOrg = GetBoolean(s) }, - { "publicPackagesOwner=|o", "The owner of the public packages to update; must be specified is useNuGetorg is true", s => parameters.PublickPackageOwner = s }, - { "allowDowngrade=|d", "Whether package downgrade is allowed", s => parameters.IsDowngradeAllowed = GetBoolean(s) }, - { "keepLatestDev=|k", "A comma-separated list of packages to keep at latest dev", s => parameters.PackagesToKeepAtLatestDev = GetList(s) }, - { "ignore=|i", "A comma-separated list of packages to ignore", s => parameters.PackagesToIgnore = GetList(s) }, - { "update=|u", "A comma-separated list of packages to update; not specifying this will update all packages found", s => parameters.PackagesToUpdate = GetList(s) }, - { "useStableIfMoreRecent=|us", "Whether to use the latest stable if a more recent version is found", s => parameters.UseStableIfMoreRecent = GetBoolean(s) }, - { "outputFile=|of", "The path to a file where the update summary will be written", s => summaryFile = s }, + { "solution=|s=", "The path to the solution to update", s => parameters.SolutionRoot = s }, + { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => parameters.SourceFeed = s }, + { "version=|versions=|v=", "The target versions to use", s => parameters.TargetVersions = GetList(s)}, + { "strict", s => parameters.Strict = GetBoolean(s) }, + { "excludeTag=|e=", "A tag to exclude from the search", s => parameters.TagToExclude = s }, + { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => parameters.IncludeNuGetOrg = true }, + { "packagesOwner=|o=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => parameters.PublicPackageOwner = s }, + { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => parameters.IsDowngradeAllowed = GetBoolean(s) }, + { "keepLatestDev=|k=", "A comma-separated list of packages to keep at latest dev", s => parameters.PackagesToKeepAtLatestDev = GetList(s) }, + { "ignore=|i=", "A comma-separated list of packages to ignore", s => parameters.PackagesToIgnore = GetList(s) }, + { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => parameters.PackagesToUpdate = GetList(s) }, + { "outputFile=|of=", "The path to a file where the update summary will be written", s => summaryFile = s }, }; options.Parse(args); @@ -74,6 +72,10 @@ private static List GetList(string value) { list.AddRange(value.Split(",")); } + else + { + list.Add(value); + } return list; } diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 6caaa1f..0c619b6 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -22,9 +22,9 @@ public class UpdaterParameters public string SourceFeedPersonalAccessToken { get; set; } /// - /// The target version for the update (stable, dev, beta, etc.) + /// The versions to update to (stable, dev, beta, etc.), in order of priority. /// - public string TargetVersion { get; set; } + public IEnumerable TargetVersions { get; set; } /// /// Whether it should exactly match the target version. @@ -66,14 +66,9 @@ public class UpdaterParameters /// public IEnumerable PackagesToUpdate { get; set; } - /// - /// Whether to use the stable version if a more recent version is available. - /// - public bool UseStableIfMoreRecent { get; set; } - /// /// The name of the owner of the public packages to update; specify if is set to true. /// - public string PublickPackageOwner { get; set; } + public string PublicPackageOwner { get; set; } } } diff --git a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs index 85ea8af..54ded90 100644 --- a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs +++ b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs @@ -14,37 +14,27 @@ public static async Task GetLatestVersion( UpdaterParameters parameters ) { - var specialVersion = parameters.TargetVersion; - - if (parameters.ShouldKeepPackageAtLatestDev(package.PackageId)) - { - specialVersion = "dev"; - } - - if (specialVersion == "stable") - { - specialVersion = ""; - } - - var versions = (await package.GetVersions(ct)).OrderByDescending(v => v.Version); - - var version = versions - .Where(v => v.IsMatchingSpecialVersion(specialVersion, parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) + var versions = (await package.GetVersions(ct)) .OrderByDescending(v => v.Version) - .FirstOrDefault(); - - if(parameters.UseStableIfMoreRecent && specialVersion != "") - { - var stableVersion = versions - .Where(v => v.IsMatchingSpecialVersion("", parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) - .OrderByDescending(v => v.Version) - .FirstOrDefault(); + .ToArray(); - if (version == null || (stableVersion?.Version.IsGreaterThan(version.Version) ?? false)) + var version = parameters + .TargetVersions + .Select(tv => { - return stableVersion; + if(tv == "stable") + { + tv = ""; + } + + return versions + .Where(v => v.IsMatchingSpecialVersion(tv, parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) + .OrderByDescending(v => v.Version) + .FirstOrDefault(); } - } + ) + .Where(v => v != null) + .FirstOrDefault(); return version; } diff --git a/src/NuGet.Updater/NuGetUpdater.Execution.cs b/src/NuGet.Updater/NuGetUpdater.Execution.cs index f499393..1aa9e28 100644 --- a/src/NuGet.Updater/NuGetUpdater.Execution.cs +++ b/src/NuGet.Updater/NuGetUpdater.Execution.cs @@ -14,7 +14,7 @@ public partial class NuGetUpdater public static bool Update( string solutionRoot, string sourceFeed, - string targetVersion, + IEnumerable targetVersions, string excludeTag = "", string feedAccessToken = "", bool includeNuGetOrg = true, @@ -26,13 +26,12 @@ public static bool Update( IEnumerable updatePackages = null, UpdateTarget target = UpdateTarget.All, TextWriter logWriter = null, - string summaryOutputFilePath = null, - bool useStableIfMoreRecent = false + string summaryOutputFilePath = null ) => UpdateAsync( CancellationToken.None, solutionRoot, sourceFeed, - targetVersion, + targetVersions, excludeTag, feedAccessToken, includeNuGetOrg, @@ -44,8 +43,7 @@ public static bool Update( updatePackages, target, logWriter, - summaryOutputFilePath, - useStableIfMoreRecent + summaryOutputFilePath ).Result; public static bool Update( @@ -58,7 +56,7 @@ public static async Task UpdateAsync( CancellationToken ct, string solutionRoot, string sourceFeed, - string targetVersion, + IEnumerable targetVersions, string excludeTag = "", string feedAccessToken = "", bool includeNuGetOrg = true, @@ -70,8 +68,7 @@ public static async Task UpdateAsync( IEnumerable packagesToUpdate = null, UpdateTarget updateTarget = UpdateTarget.All, TextWriter logWriter = null, - string summaryOutputFilePath = null, - bool useStableIfMoreRecent = false + string summaryOutputFilePath = null ) { var parameters = new UpdaterParameters @@ -79,7 +76,7 @@ public static async Task UpdateAsync( SolutionRoot = solutionRoot, SourceFeed = sourceFeed, SourceFeedPersonalAccessToken = feedAccessToken, - TargetVersion = targetVersion, + TargetVersions = targetVersions, Strict = strict, TagToExclude = excludeTag, UpdateTarget = updateTarget, @@ -89,7 +86,6 @@ public static async Task UpdateAsync( PackagesToKeepAtLatestDev = packagesTokeepAtLatestDev, PackagesToIgnore = packagesToIgnore, PackagesToUpdate = packagesToUpdate, - UseStableIfMoreRecent = useStableIfMoreRecent, }; return await UpdateAsync(ct, parameters, logWriter, summaryOutputFilePath); diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index a80de2c..905981b 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -46,11 +46,11 @@ private async Task UpdatePackages(CancellationToken ct) if(latest == null) { - _log.Write($"Skipping [{package.PackageId}]: no {_parameters.TargetVersion} version found."); + _log.Write($"Skipping [{package.PackageId}]: no {string.Join(" or ", _parameters.TargetVersions)} version found."); continue; } - _log.Write($"Latest {_parameters.TargetVersion} version for [{package.PackageId}] is [{latest.Version}]"); + _log.Write($"Latest matching version for [{package.PackageId}] is [{latest.Version}]"); foreach(var files in targetFiles) { @@ -91,7 +91,7 @@ private async Task GetPackages(CancellationToken ct) { //Using search instead of list because the latter forces the v2 api packages = packages - .Concat(await NuGetOrgSource.SearchPackages(ct, _log.Write, searchTerm: $"owner:{_parameters.PublickPackageOwner}")) + .Concat(await NuGetOrgSource.SearchPackages(ct, _log.Write, searchTerm: $"owner:{_parameters.PublicPackageOwner}")) .GroupBy(p => p.PackageId) .Select(g => new NuGetPackage(g.Key, g.ToArray())) .ToArray(); From db60b1d5c3d07703c2238bc26423cff76da511e2 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 29 Jul 2019 11:20:43 -0400 Subject: [PATCH 095/201] Reworked the updater - Package sources can be passed from the outside - Multiple private sources can be used - Removed unecessary parameters - Improved net core app experience --- src/NuGet.Updater.Tool/Program.cs | 57 ++++--- src/NuGet.Updater/Entities/IUpdaterSource.cs | 11 ++ src/NuGet.Updater/Entities/Logger.cs | 153 ------------------ .../Entities/PrivateUpdaterSource.cs | 34 ++++ .../Entities/PublicUpdaterSource.cs | 22 +++ src/NuGet.Updater/Entities/UpdateOperation.cs | 59 ------- src/NuGet.Updater/Entities/UpdateTarget.cs | 2 +- .../Entities/UpdaterParameters.cs | 43 ++--- ...{FeedNuGetVersion.cs => UpdaterVersion.cs} | 6 +- .../Extensions/EnumerableAsyncExtensions.cs | 22 --- .../Extensions/FeedNuGetVersionExtensions.cs | 35 ---- .../Extensions/NuGetPackageExtensions.cs | 37 ++--- .../NuGetVersionExtensions.cs | 42 +++-- .../Extensions/PackageSourceExtensions.cs | 25 +-- .../Extensions/UpdaterParametersExtension.cs | 60 ++++--- .../Extensions/UpdaterVersionExtensions.cs | 35 ++++ .../Extensions/XmlDocumentExtensions.Net.cs | 9 +- .../Extensions/XmlDocumentExtensions.UAP.cs | 7 +- .../Extensions/XmlDocumentExtensions.cs | 7 +- src/NuGet.Updater/Helpers/FileHelper.Net.cs | 4 +- src/NuGet.Updater/Helpers/FileHelper.UAP.cs | 2 +- src/NuGet.Updater/Helpers/FileHelper.cs | 95 +++++++++++ src/NuGet.Updater/Helpers/PackageHelper.cs | 41 +++++ src/NuGet.Updater/Log/Logger.cs | 122 ++++++++++++++ .../SimpleTextWriter.cs} | 6 +- src/NuGet.Updater/Log/UpdateOperation.cs | 62 +++++++ src/NuGet.Updater/NuGet.Updater.csproj | 1 + src/NuGet.Updater/NuGetUpdater.Execution.cs | 87 +--------- src/NuGet.Updater/NuGetUpdater.cs | 121 +++----------- 29 files changed, 588 insertions(+), 619 deletions(-) create mode 100644 src/NuGet.Updater/Entities/IUpdaterSource.cs delete mode 100644 src/NuGet.Updater/Entities/Logger.cs create mode 100644 src/NuGet.Updater/Entities/PrivateUpdaterSource.cs create mode 100644 src/NuGet.Updater/Entities/PublicUpdaterSource.cs delete mode 100644 src/NuGet.Updater/Entities/UpdateOperation.cs rename src/NuGet.Updater/Entities/{FeedNuGetVersion.cs => UpdaterVersion.cs} (68%) delete mode 100644 src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs rename src/NuGet.Updater/{Entities => Extensions}/NuGetVersionExtensions.cs (81%) create mode 100644 src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs create mode 100644 src/NuGet.Updater/Helpers/FileHelper.cs create mode 100644 src/NuGet.Updater/Helpers/PackageHelper.cs create mode 100644 src/NuGet.Updater/Log/Logger.cs rename src/NuGet.Updater/{Entities/ActionTextWriter.cs => Log/SimpleTextWriter.cs} (70%) create mode 100644 src/NuGet.Updater/Log/UpdateOperation.cs diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 47e5c6c..b457161 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -9,39 +9,37 @@ namespace NuGet.Updater.Tool { public class Program { - public static async Task Main(string[] args) + private static readonly UpdaterParameters _parameters = new UpdaterParameters { - if(args == null || args.Length == 0) - { - args = new[] { "help" }; - } - - var parameters = new UpdaterParameters - { - UpdateTarget = UpdateTarget.All, - }; + UpdateTarget = UpdateTarget.All, + PrivateFeeds = new Dictionary(), + }; + public static async Task Main(string[] args) + { var isHelp = false; + var isSilent = false; string summaryFile = default; var options = new OptionSet { { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The path to the solution to update", s => parameters.SolutionRoot = s }, - { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => parameters.SourceFeed = s }, - { "version=|versions=|v=", "The target versions to use", s => parameters.TargetVersions = GetList(s)}, - { "strict", s => parameters.Strict = GetBoolean(s) }, - { "excludeTag=|e=", "A tag to exclude from the search", s => parameters.TagToExclude = s }, - { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => parameters.IncludeNuGetOrg = true }, - { "packagesOwner=|o=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => parameters.PublicPackageOwner = s }, - { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => parameters.IsDowngradeAllowed = GetBoolean(s) }, - { "keepLatestDev=|k=", "A comma-separated list of packages to keep at latest dev", s => parameters.PackagesToKeepAtLatestDev = GetList(s) }, - { "ignore=|i=", "A comma-separated list of packages to ignore", s => parameters.PackagesToIgnore = GetList(s) }, - { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => parameters.PackagesToUpdate = GetList(s) }, + { "solution=|s=", "The path to the solution to update", s => _parameters.SolutionRoot = s }, + { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => ParsePrivateFeed(s, '|') }, + { "version=|versions=|v=", "The target versions to use", s => _parameters.TargetVersions = GetList(s)}, + { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => _parameters.IncludeNuGetOrg = true }, + { "packagesOwner=|o=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => _parameters.PackagesOwner = s }, + { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => _parameters.IsDowngradeAllowed = GetBoolean(s) }, + { "ignore=|i=", "A comma-separated list of packages to ignore", s => _parameters.PackagesToIgnore = GetList(s) }, + { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => _parameters.PackagesToUpdate = GetList(s) }, { "outputFile=|of=", "The path to a file where the update summary will be written", s => summaryFile = s }, + { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, }; - options.Parse(args); + if(options.Parse(args).Count == 0) + { + isHelp = true; + } if(isHelp) { @@ -50,7 +48,7 @@ public static async Task Main(string[] args) } else { - await NuGetUpdater.UpdateAsync(CancellationToken.None, parameters, Console.Out, summaryFile); + await NuGetUpdater.UpdateAsync(CancellationToken.None, _parameters, isSilent ? null : Console.Out, summaryFile); } } @@ -79,5 +77,18 @@ private static List GetList(string value) return list; } + + private static void ParsePrivateFeed(string value, char separator) + { + if(value.Contains(separator)) + { + var parts = value.Split(separator); + _parameters.PrivateFeeds.Add(parts[0], parts[1]); + } + else + { + _parameters.PrivateFeeds.Add(value, null); + } + } } } diff --git a/src/NuGet.Updater/Entities/IUpdaterSource.cs b/src/NuGet.Updater/Entities/IUpdaterSource.cs new file mode 100644 index 0000000..0442cf8 --- /dev/null +++ b/src/NuGet.Updater/Entities/IUpdaterSource.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; +using NuGet.Updater.Log; + +namespace NuGet.Updater.Entities +{ + public interface IUpdaterSource + { + Task GetPackages(CancellationToken ct, Logger log = null); + } +} diff --git a/src/NuGet.Updater/Entities/Logger.cs b/src/NuGet.Updater/Entities/Logger.cs deleted file mode 100644 index 04e1f5e..0000000 --- a/src/NuGet.Updater/Entities/Logger.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using NuGet.Updater.Extensions; -using NuGet.Updater.Helpers; -using NuGet.Versioning; - -namespace NuGet.Updater.Entities -{ - public class Logger - { - private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; - private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; - - private readonly List _updateOperations = new List(); - private readonly TextWriter _writer; - private readonly string _summaryFilePath; - - public Logger(TextWriter writer, string summaryFilePath = null) - { - _writer = writer -#if DEBUG - ?? Console.Out; -#else - ?? TextWriter.Null; -#endif - _summaryFilePath = summaryFilePath; - } - - public void Clear() => _updateOperations.Clear(); - - public void Write(string message) => _writer.Write(message); - - public void Write(IEnumerable operations) - { - foreach (var o in operations) - { - Write(o); - } - } - - public void Write(UpdateOperation operation) - { - Write(operation.GetLogMessage()); - _updateOperations.Add(operation); - } - - public void WriteSummary(UpdaterParameters parameters) - { - foreach (var line in GetSummary(parameters)) - { - Write(line); - } - - if (_summaryFilePath != null) - { - try - { - FileHelper.LogToFile(_summaryFilePath, GetSummary(parameters, includeUrl: true)); - } - catch (Exception ex) - { - Write($"Failed to write to {_summaryFilePath}. Reason : {ex.Message}"); - } - } - } - - private IEnumerable GetSummary(UpdaterParameters parameters, bool includeUrl = false) - { - var completedUpdates = _updateOperations.Where(o => o.ShouldProceed).ToArray(); - var skippedUpdates = _updateOperations.Where(o => !o.ShouldProceed).ToArray(); - - yield return $"# Package update summary"; - - if (_updateOperations.Count == 0) - { - yield return $"No packages have been updated."; - } - - foreach(var line in parameters.GetSummary()) - { - yield return line; - } - - if (completedUpdates.Any()) - { - var updatedPackages = completedUpdates - .Select(o => (o.PackageName, o.UpdatedVersion, o.FeedUri)) - .Distinct() - .ToArray(); - - yield return $"## Updated {updatedPackages.Length} packages:"; - - foreach (var p in updatedPackages) - { - var logMessage = $"[{p.PackageName}] to [{p.UpdatedVersion}]"; - var url = includeUrl ? GetPackageUrl(p.PackageName, p.UpdatedVersion, p.FeedUri) : default; - - yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; - } - } - - if (skippedUpdates.Any()) - { - var skippedPackages = skippedUpdates - .Select(o => (o.PackageName, o.PreviousVersion, o.FeedUri)) - .Distinct() - .ToArray(); - - yield return $"## Skipped {skippedPackages.Length} packages:"; - - foreach (var p in skippedPackages) - { - var logMessage = $"[{p.PackageName}] is at version [{p.PreviousVersion}]"; - var url = includeUrl ? GetPackageUrl(p.PackageName, p.PreviousVersion, p.FeedUri) : default; - - yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; - } - } - } - - private static string GetPackageUrl(string packageId, NuGetVersion version, Uri feedUri) - { - if (feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) - { - return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; - } - - var pattern = LegacyAzureArtifactsFeedUrlPattern; - - if (feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com")) - { - pattern = AzureArtifactsFeedUrlPattern; - } - - var match = Regex.Match(feedUri.AbsoluteUri, pattern); - - if (match.Length > 0) - { - string accountName = match.Groups["account"].Value; - string feedName = match.Groups["feed"].Value; - - return $"https://dev.azure.com/{accountName}/_packaging?_a=package&feed={feedName}&package={packageId}&version={version.ToFullString()}&protocolType=NuGet"; - } - - return default; - } - } -} diff --git a/src/NuGet.Updater/Entities/PrivateUpdaterSource.cs b/src/NuGet.Updater/Entities/PrivateUpdaterSource.cs new file mode 100644 index 0000000..66614c6 --- /dev/null +++ b/src/NuGet.Updater/Entities/PrivateUpdaterSource.cs @@ -0,0 +1,34 @@ +using System.Threading; +using System.Threading.Tasks; +using NuGet.Configuration; +using NuGet.Updater.Extensions; +using NuGet.Updater.Log; + +namespace NuGet.Updater.Entities +{ + public class PrivateUpdaterSource : IUpdaterSource + { + private readonly PackageSource _packageSource; + + public PrivateUpdaterSource(string url, string accessToken) + { + _packageSource = GetPackageSource(url, accessToken); + } + + public Task GetPackages(CancellationToken ct, Logger log = null) => _packageSource.SearchPackages(ct, log: log); + + private static PackageSource GetPackageSource(string url, string accessToken) + { + var name = url.GetHashCode().ToString(); + + return new PackageSource(url, name) + { +#if UAP + Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false), +#else + Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false, null), +#endif + }; + } + } +} diff --git a/src/NuGet.Updater/Entities/PublicUpdaterSource.cs b/src/NuGet.Updater/Entities/PublicUpdaterSource.cs new file mode 100644 index 0000000..8f32078 --- /dev/null +++ b/src/NuGet.Updater/Entities/PublicUpdaterSource.cs @@ -0,0 +1,22 @@ +using System.Threading; +using System.Threading.Tasks; +using NuGet.Configuration; +using NuGet.Updater.Extensions; +using NuGet.Updater.Log; + +namespace NuGet.Updater.Entities +{ + public class PublicUpdaterSource : IUpdaterSource + { + private readonly PackageSource _packageSource; + private readonly string _packageOwner; + + public PublicUpdaterSource(string url, string packageOwner) + { + _packageSource = new PackageSource(url); + _packageOwner = packageOwner; + } + + public Task GetPackages(CancellationToken ct, Logger log = null) => _packageSource.SearchPackages(ct, $"owner:{_packageOwner}", log); + } +} diff --git a/src/NuGet.Updater/Entities/UpdateOperation.cs b/src/NuGet.Updater/Entities/UpdateOperation.cs deleted file mode 100644 index 5c41cb9..0000000 --- a/src/NuGet.Updater/Entities/UpdateOperation.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NuGet.Versioning; - -namespace NuGet.Updater.Entities -{ - public class UpdateOperation - { - private readonly bool _isDowngradeAllowed; - - public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, FeedNuGetVersion updatedVersion, string filePath) - { - _isDowngradeAllowed = isDowngradeAllowed; - - Date = DateTimeOffset.Now; - - PackageName = packageName; - PreviousVersion = previousVersion; - UpdatedVersion = updatedVersion.Version; - FilePath = filePath; - FeedUri = updatedVersion.FeedUri; - } - - public DateTimeOffset Date { get; } - - public string PackageName { get; } - - public NuGetVersion PreviousVersion { get; } - - public NuGetVersion UpdatedVersion { get; } - - public string FilePath { get; } - - public Uri FeedUri { get; } - - public bool ShouldProceed => UpdatedVersion.IsGreaterThan(PreviousVersion) || _isDowngradeAllowed; - - public bool IsLatestVersion => PreviousVersion == UpdatedVersion; - - public string GetLogMessage() - { - if (ShouldProceed) - { - return UpdatedVersion.IsGreaterThan(PreviousVersion) - ? $"Updating [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]" - : $"Downgrading [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; - } - else - { - return IsLatestVersion - ? $"Version [{UpdatedVersion}] of [{PackageName}] already found in [{FilePath}]. Skipping." - : $"Higher verson of [{PackageName}] ([{UpdatedVersion}]) found in [{FilePath}]. Skipping."; - } - } - } -} diff --git a/src/NuGet.Updater/Entities/UpdateTarget.cs b/src/NuGet.Updater/Entities/UpdateTarget.cs index 183465f..d7c6601 100644 --- a/src/NuGet.Updater/Entities/UpdateTarget.cs +++ b/src/NuGet.Updater/Entities/UpdateTarget.cs @@ -8,7 +8,7 @@ public enum UpdateTarget /// /// No files will be updated. /// - Unespecified = 0, + Unspecified = 0, /// /// .nuspec files. diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 0c619b6..e950ecc 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -1,74 +1,57 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace NuGet.Updater.Entities { public class UpdaterParameters { /// - /// The location of the solution to update. + /// Gets or sets the location of the solution to update. /// public string SolutionRoot { get; set; } /// - /// The URL of the private feed to use. + /// Gets or sets a list of private feed URLs and access tokens to get packages from. /// - public string SourceFeed { get; set; } + public Dictionary PrivateFeeds { get; set; } /// - /// The Personal Access Token to use to access the private feed. - /// - public string SourceFeedPersonalAccessToken { get; set; } - - /// - /// The versions to update to (stable, dev, beta, etc.), in order of priority. + /// Gets or sets the versions to update to (stable, dev, beta, etc.), in order of priority. /// public IEnumerable TargetVersions { get; set; } /// - /// Whether it should exactly match the target version. + /// Gets or sets a value indicating whether the version should exactly match the target version. /// public bool Strict { get; set; } /// - /// A specific tag to exclude when looking for versions. - /// - public string TagToExclude { get; set; } - - /// - /// Whether to include packages from NuGet.org. + /// Gets or sets a value indicating whether whether to include packages from NuGet.org. /// public bool IncludeNuGetOrg { get; set; } /// - /// Whether the packages can be downgraded if the version found is lower. + /// Gets or sets a value indicating whether whether the packages can be downgraded if the version found is lower than the current one. /// public bool IsDowngradeAllowed { get; set; } /// - /// The type of files with NuGet references to update. + /// Gets or sets the type of files to update. /// public UpdateTarget UpdateTarget { get; set; } /// - /// A list of packages to keep at latest dev. - /// - public IEnumerable PackagesToKeepAtLatestDev { get; set; } - - /// - /// A list of packages to ignore. + /// Gets or sets a list of packages to ignore. /// public IEnumerable PackagesToIgnore { get; set; } /// - /// A list of packages to update. Will update all packages found if nothing is specified. + /// Gets or sets a list of packages to update; all packages found will be updated if nothing is specified. /// public IEnumerable PackagesToUpdate { get; set; } /// - /// The name of the owner of the public packages to update; specify if is set to true. + /// Gets or sets the name of the owner of the packages to update; used with NuGet.org. /// - public string PublicPackageOwner { get; set; } + public string PackagesOwner { get; set; } } } diff --git a/src/NuGet.Updater/Entities/FeedNuGetVersion.cs b/src/NuGet.Updater/Entities/UpdaterVersion.cs similarity index 68% rename from src/NuGet.Updater/Entities/FeedNuGetVersion.cs rename to src/NuGet.Updater/Entities/UpdaterVersion.cs index 9b40807..6a65cd6 100644 --- a/src/NuGet.Updater/Entities/FeedNuGetVersion.cs +++ b/src/NuGet.Updater/Entities/UpdaterVersion.cs @@ -3,9 +3,9 @@ namespace NuGet.Updater.Entities { - public class FeedNuGetVersion - { - public FeedNuGetVersion(Uri feedUri, NuGetVersion version) + public class UpdaterVersion + { + public UpdaterVersion(Uri feedUri, NuGetVersion version) { FeedUri = feedUri; Version = version; diff --git a/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs b/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs deleted file mode 100644 index 5ef9e9f..0000000 --- a/src/NuGet.Updater/Extensions/EnumerableAsyncExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using NuGet.Common; - -namespace NuGet.Updater.Extensions -{ - public static class EnumerableAsyncExtensions - { - public static async Task ToArray(this IEnumerableAsync enumerable) - { - var list = new List(); - var enumerator = enumerable.GetEnumeratorAsync(); - - while(await enumerator.MoveNextAsync()) - { - list.Add(enumerator.Current); - } - - return list.ToArray(); - } - } -} diff --git a/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs b/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs deleted file mode 100644 index 037e091..0000000 --- a/src/NuGet.Updater/Extensions/FeedNuGetVersionExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using NuGet.Updater.Entities; - -namespace NuGet.Updater.Extensions -{ - public static class FeedNuGetVersionExtensions - { - public static bool ContainsTag(this FeedNuGetVersion version, string tag) => - !string.IsNullOrEmpty(tag) - && (version?.Version?.ReleaseLabels?.Contains(tag) ?? false); - - public static bool IsMatchingSpecialVersion(this FeedNuGetVersion version, string specialVersion, bool strict) - { - var releaseLabels = version?.Version?.ReleaseLabels; - - if (string.IsNullOrEmpty(specialVersion)) - { - return !releaseLabels?.Any() ?? true; - } - else - { - var isMatchingSpecialVersion = releaseLabels?.Any(label => Regex.IsMatch(label, specialVersion, RegexOptions.IgnoreCase)) ?? false; - - return strict - ? releaseLabels?.Count() == 2 && isMatchingSpecialVersion // Check strictly for packages with versions "dev.XXXX" - : isMatchingSpecialVersion; // Allow packages with versions "dev.XXXX.XXXX" - } - } - } -} diff --git a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs index 54ded90..accae9f 100644 --- a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs +++ b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs @@ -3,50 +3,39 @@ using System.Threading; using System.Threading.Tasks; using NuGet.Updater.Entities; +using Uno.Extensions; namespace NuGet.Updater.Extensions { public static class NuGetPackageExtensions { - public static async Task GetLatestVersion( + public static async Task GetLatestVersion( this NuGetPackage package, CancellationToken ct, UpdaterParameters parameters ) { - var versions = (await package.GetVersions(ct)) - .OrderByDescending(v => v.Version) - .ToArray(); + var versions = await package.GetVersions(ct); - var version = parameters - .TargetVersions - .Select(tv => - { - if(tv == "stable") - { - tv = ""; - } + var versionsPerTarget = versions + .OrderByDescending(v => v.Version) + .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) + .Where(g => g.Key.HasValue()); - return versions - .Where(v => v.IsMatchingSpecialVersion(tv, parameters.Strict) && !v.ContainsTag(parameters.TagToExclude)) - .OrderByDescending(v => v.Version) - .FirstOrDefault(); - } - ) - .Where(v => v != null) + return versionsPerTarget + .Select(g => g.FirstOrDefault()) + .OrderByDescending(v => v.Version) .FirstOrDefault(); - - return version; } - private static async Task> GetVersions(this NuGetPackage package, CancellationToken ct) + private static async Task> GetVersions(this NuGetPackage package, CancellationToken ct) { - var versions = new List(); + var versions = new List(); foreach(var p in package.Packages) { foreach(var v in await p.Value.GetVersionsAsync()) { - versions.Add(new FeedNuGetVersion(p.Key, v.Version)); + versions.Add(new UpdaterVersion(p.Key, v.Version)); } } diff --git a/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs b/src/NuGet.Updater/Extensions/NuGetVersionExtensions.cs similarity index 81% rename from src/NuGet.Updater/Entities/NuGetVersionExtensions.cs rename to src/NuGet.Updater/Extensions/NuGetVersionExtensions.cs index fc18728..f8a82ce 100644 --- a/src/NuGet.Updater/Entities/NuGetVersionExtensions.cs +++ b/src/NuGet.Updater/Extensions/NuGetVersionExtensions.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using NuGet.Versioning; namespace NuGet.Updater @@ -17,38 +13,38 @@ public static bool IsEqualTo(this NuGetVersion x, NuGetVersion y) public static bool IsGreaterThan(this NuGetVersion x, NuGetVersion y) { - if (ReferenceEquals(x, y)) + if(ReferenceEquals(x, y)) { return false; } - if (ReferenceEquals(y, null)) + if(ReferenceEquals(y, null)) { return false; } - if (ReferenceEquals(x, null)) + if(ReferenceEquals(x, null)) { return true; } // compare version var result = x.Major.CompareTo(y.Major); - if (result != 0) + if(result != 0) { // If Major is higher then x is greater. return result == 1; } result = x.Minor.CompareTo(y.Minor); - if (result != 0) + if(result != 0) { // If Minor is higher then x is greater. return result == 1; } result = x.Patch.CompareTo(y.Patch); - if (result != 0) + if(result != 0) { return result == 1; } @@ -57,23 +53,23 @@ public static bool IsGreaterThan(this NuGetVersion x, NuGetVersion y) var xLabels = GetReleaseLabelsOrNull(x); var yLabels = GetReleaseLabelsOrNull(y); - if (xLabels != null + if(xLabels != null && yLabels == null) { return false; } - if (xLabels == null + if(xLabels == null && yLabels != null) { return true; } - if (xLabels != null + if(xLabels != null && yLabels != null) { result = CompareReleaseLabels(xLabels, yLabels); - if (result != 0) + if(result != 0) { return result == 1; } @@ -90,13 +86,13 @@ private static string[] GetReleaseLabelsOrNull(SemanticVersion version) string[] labels = null; // Check if labels exist - if (version.IsPrerelease) + if(version.IsPrerelease) { // Try to use string[] which is how labels are normally stored. var enumerable = version.ReleaseLabels; labels = enumerable as string[]; - if (labels != null && enumerable != null) + if(labels != null && enumerable != null) { // This is not the expected type, enumerate and convert to an array. labels = enumerable.ToArray(); @@ -115,17 +111,17 @@ private static int CompareReleaseLabels(string[] xLabels, string[] yLabels) var count = Math.Max(xLabels.Length, yLabels.Length); - for (var i = 0; i < count; i++) + for(var i = 0; i < count; i++) { var aExists = i < xLabels.Length; var bExists = i < yLabels.Length; - if (!aExists && bExists) + if(!aExists && bExists) { return -1; } - if (aExists && !bExists) + if(aExists && !bExists) { return 1; } @@ -133,7 +129,7 @@ private static int CompareReleaseLabels(string[] xLabels, string[] yLabels) // compare the labels result = CompareRelease(xLabels[i], yLabels[i]); - if (result != 0) + if(result != 0) { return result; } @@ -155,14 +151,14 @@ private static int CompareRelease(string version1, string version2) var v2IsNumeric = int.TryParse(version2, out var version2Num); // if both are numeric compare them as numbers - if (v1IsNumeric && v2IsNumeric) + if(v1IsNumeric && v2IsNumeric) { result = version1Num.CompareTo(version2Num); } - else if (v1IsNumeric || v2IsNumeric) + else if(v1IsNumeric || v2IsNumeric) { // numeric labels come before alpha labels - if (v1IsNumeric) + if(v1IsNumeric) { result = -1; } diff --git a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs index 1817e4d..488e82f 100644 --- a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs @@ -7,43 +7,26 @@ using NuGet.Configuration; using NuGet.Protocol; using NuGet.Protocol.Core.Types; +using NuGet.Updater.Log; namespace NuGet.Updater.Extensions { public static class PackageSourceExtensions { - public static async Task SearchPackages(this PackageSource source, CancellationToken ct, Action logAction, string searchTerm = "") + public static async Task SearchPackages(this PackageSource source, CancellationToken ct, string searchTerm = "", Logger log = null) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); - logAction($"Pulling NuGet packages from {source.SourceUri}"); + log?.Write($"Pulling packages from {source.SourceUri}"); var searchResource = repository.GetResource(); var packages = (await searchResource.SearchAsync(searchTerm, new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), skip: 0, take: 1000, log: new NullLogger(), cancellationToken: ct)).ToArray(); - logAction($"Found {packages.Length} packages"); - - return source.ToNuGetPackages(packages); - } - - public static async Task ListPackages(this PackageSource source, CancellationToken ct, Action logAction, string searchTerm = "") - { - var settings = Settings.LoadDefaultSettings(null); - var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); - - var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); - - logAction($"Pulling NuGet packages from {source.SourceUri}"); - - var listResource = repository.GetResource(); - - var packages = await (await listResource.ListAsync(searchTerm, prerelease: true, allVersions: false, includeDelisted: false, log: new NullLogger(), token: ct)).ToArray(); - - logAction($"Found {packages.Length} packages"); + log?.Write($"Found {packages.Length} packages"); return source.ToNuGetPackages(packages); } diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 79ec4b9..fd65a58 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -3,54 +3,62 @@ using System.Linq; using NuGet.Configuration; using NuGet.Updater.Entities; +using Uno.Extensions; namespace NuGet.Updater.Extensions { internal static class UpdaterParametersExtension { - internal static bool HasUpdateTarget(this UpdaterParameters parameters, UpdateTarget target) => (parameters.UpdateTarget & target) == target; - - internal static PackageSource GetFeedPackageSource(this UpdaterParameters parameters) => new PackageSource(parameters.SourceFeed, "Feed") + internal static IUpdaterSource[] GetSources(this UpdaterParameters parameters) { -#if UAP - Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false), -#else - Credentials = PackageSourceCredential.FromUserInput("Feed", "user", parameters.SourceFeedPersonalAccessToken, false, null), -#endif - }; + var packageSources = new List(); + + if(parameters.PrivateFeeds?.Any() ?? false) + { + packageSources.AddRange(parameters.PrivateFeeds.Select(g => new PrivateUpdaterSource(g.Key, g.Value))); + } + + if(parameters.IncludeNuGetOrg) + { + packageSources.Add(new PublicUpdaterSource("https://api.nuget.org/v3/index.json", parameters.PackagesOwner)); + } + + return packageSources.ToArray(); + } - internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, NuGetPackage package) => - (parameters.PackagesToIgnore == null || !parameters.PackagesToIgnore.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase)) - && (parameters.PackagesToUpdate == null || parameters.PackagesToUpdate.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase)); + internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, NuGetPackage package) + { + var isPackageToIgnore = parameters.PackagesToIgnore?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? false; + var isPackageToUpdate = parameters.PackagesToUpdate?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? true; - internal static bool ShouldKeepPackageAtLatestDev(this UpdaterParameters parameters, string packageId) => - parameters.PackagesToKeepAtLatestDev != null && parameters.PackagesToKeepAtLatestDev.Contains(packageId, StringComparer.OrdinalIgnoreCase); + return isPackageToUpdate && !isPackageToIgnore; + } internal static IEnumerable GetSummary(this UpdaterParameters parameters) { yield return $"## Configuration"; - yield return $"- Update targetting {parameters.SolutionRoot}"; - - var packageSources = parameters.IncludeNuGetOrg ? $"NuGet.org and {parameters.SourceFeed}" : parameters.SourceFeed; - yield return $"- Using NuGet packages from {packageSources}"; + yield return $"- Update targetting files under {parameters.SolutionRoot}"; - var targetVersion = parameters.UseStableIfMoreRecent ? $"{parameters.TargetVersion} with fallback to stable if a more recent version is available" : parameters.TargetVersion; - yield return $"- Targeting version {targetVersion}"; + var packageSources = new List(); - if (parameters.IsDowngradeAllowed) + if(parameters.IncludeNuGetOrg) { - yield return $"- Allowing package downgrade if a lower version is found"; + packageSources.Add("NuGet.org"); } - if(parameters.TagToExclude != null && parameters.TagToExclude != "") + if(parameters.PrivateFeeds?.Any() ?? false) { - yield return $"- Excluding versions with the {parameters.TagToExclude} tag"; + packageSources.AddRange(parameters.PrivateFeeds.Keys); } - if(parameters.PackagesToKeepAtLatestDev?.Any() ?? false) + yield return $"- Using NuGet packages from {string.Join(", ", packageSources)}"; + + yield return $"- Using target version {string.Join(", then ", parameters.TargetVersions)}"; + + if (parameters.IsDowngradeAllowed) { - yield return $"- Keeping {string.Join(",", parameters.PackagesToKeepAtLatestDev)} at latest dev"; + yield return $"- Allowing package downgrade if a lower version is found"; } if (parameters.PackagesToIgnore?.Any() ?? false) diff --git a/src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs b/src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs new file mode 100644 index 0000000..3ee4692 --- /dev/null +++ b/src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using NuGet.Updater.Entities; +using Uno.Extensions; + +namespace NuGet.Updater.Extensions +{ + public static class UpdaterVersionExtensions + { + public static bool IsMatchingVersion( + this UpdaterVersion version, + string tag, + bool isStrict + ) + { + var releaseLabels = version?.Version?.ReleaseLabels; + + if(tag.IsNullOrEmpty() || tag == "stable") + { + return releaseLabels?.None() ?? true; //Stable versions have no release labels + } + + var hasTag = ContainsTag(releaseLabels, tag); + + return isStrict + ? releaseLabels?.Count() == 2 && hasTag // Check strictly for packages with versions "dev.XXXX" + : hasTag; // Allow packages with versions "dev.XXXX.XXXX" + } + + private static bool ContainsTag(IEnumerable releaseLabels, string tag) + => tag.HasValue() + && (releaseLabels?.Any(label => Regex.IsMatch(label, tag, RegexOptions.IgnoreCase)) ?? false); + } +} diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs index fb97ad5..31a52af 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Xml; using NuGet.Updater.Entities; +using NuGet.Updater.Log; using NuGet.Versioning; namespace NuGet.Updater.Extensions @@ -19,7 +20,7 @@ public partial class XmlDocumentExtensions public static UpdateOperation[] UpdateProjectReferenceVersions( this XmlDocument document, string packageId, - FeedNuGetVersion version, + UpdaterVersion version, string path, bool isDowngradeAllowed) { @@ -39,7 +40,7 @@ public static UpdateOperation[] UpdateProjectReferenceVersions( private static UpdateOperation[] UpdateProjectReferenceVersions( this XmlDocument document, string packageId, - FeedNuGetVersion version, + UpdaterVersion version, string path, XmlNamespaceManager namespaceManager, bool isDowngradeAllowed @@ -58,7 +59,7 @@ bool isDowngradeAllowed var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if(operation.ShouldProceed) + if(operation.IsUpdate) { packageReference.SetAttribute("Version", version.Version.ToString()); } @@ -75,7 +76,7 @@ bool isDowngradeAllowed var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if(operation.ShouldProceed) + if(operation.IsUpdate) { node.InnerText = version.Version.ToString(); } diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs index d951ed9..aac4b16 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Xml; using NuGet.Updater.Entities; +using NuGet.Updater.Log; using NuGet.Versioning; using Windows.Data.Xml.Dom; using Windows.Storage; @@ -24,7 +25,7 @@ partial class XmlDocumentExtensions public static UpdateOperation[] UpdateProjectReferenceVersions( this XmlDocument document, string packageId, - FeedNuGetVersion version, + UpdaterVersion version, string path, bool isDowngradeAllowed ) @@ -47,7 +48,7 @@ bool isDowngradeAllowed var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if (operation.ShouldProceed) + if (operation.IsUpdate) { packageReference.SetAttribute("Version", version.Version.ToString()); } @@ -64,7 +65,7 @@ bool isDowngradeAllowed var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if (operation.ShouldProceed) + if (operation.IsUpdate) { node.InnerText = version.Version.ToString(); } diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index 4a87629..bbc896e 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using NuGet.Updater.Entities; +using NuGet.Updater.Log; using NuGet.Versioning; #if UAP @@ -20,7 +19,7 @@ public static partial class XmlDocumentExtensions public static UpdateOperation[] UpdateNuSpecVersions( this XmlDocument document, string packageId, - FeedNuGetVersion version, + UpdaterVersion version, string path, bool isDowngradeAllowed ) @@ -38,7 +37,7 @@ bool isDowngradeAllowed var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if (operation.ShouldProceed) + if (operation.IsUpdate) { node.SetAttribute("version", version.Version.ToString()); } diff --git a/src/NuGet.Updater/Helpers/FileHelper.Net.cs b/src/NuGet.Updater/Helpers/FileHelper.Net.cs index b424d94..c44ad02 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.Net.cs @@ -7,7 +7,7 @@ namespace NuGet.Updater.Helpers { - public static class FileHelper + partial class FileHelper { public static void LogToFile(string outputFilePath, IEnumerable log) { @@ -51,4 +51,4 @@ public static async Task SetFileContent(CancellationToken ct, string path, strin } } } -#endif \ No newline at end of file +#endif diff --git a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs index 7fe10ab..6ad574e 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs @@ -10,7 +10,7 @@ namespace NuGet.Updater.Helpers { - public static class FileHelper + partial class FileHelper { public static void LogToFile(string outputFilePath, IEnumerable log) { diff --git a/src/NuGet.Updater/Helpers/FileHelper.cs b/src/NuGet.Updater/Helpers/FileHelper.cs new file mode 100644 index 0000000..d3f8108 --- /dev/null +++ b/src/NuGet.Updater/Helpers/FileHelper.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using NuGet.Updater.Log; + +#if UAP +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +#else +using XmlDocument = System.Xml.XmlDocument; +#endif + +namespace NuGet.Updater.Helpers +{ + public static partial class FileHelper + { + public static async Task>> GetTargetFiles( + CancellationToken ct, + UpdateTarget updateTarget, + string solutionRoot, + Logger log + ) + { + var targetFiles = new Dictionary>(); + + var allTargets = new[] + { + UpdateTarget.Nuspec, + UpdateTarget.PackageReference, + UpdateTarget.ProjectJson, + UpdateTarget.DirectoryProps, + UpdateTarget.DirectoryTargets, + }; + + foreach(var target in allTargets.Where(t => (updateTarget & t) == t)) + { + targetFiles.Add(target, await GetFilesForTarget(ct, target, solutionRoot, log)); + } + + return targetFiles; + } + + private static async Task> GetFilesForTarget(CancellationToken ct, UpdateTarget target, string solutionRoot, Logger log) + { + string extensionFilter = null, nameFilter = null; + + switch(target) + { + case UpdateTarget.Nuspec: + extensionFilter = ".nuspec"; + break; + + case UpdateTarget.ProjectJson: + nameFilter = "project.json"; + break; + + case UpdateTarget.PackageReference: + extensionFilter = ".csproj"; + break; + + case UpdateTarget.DirectoryTargets: + nameFilter = "Directory.Build.targets"; + break; + + case UpdateTarget.DirectoryProps: + nameFilter = "Directory.Build.props"; + break; + + default: + break; + } + + if(extensionFilter == null && nameFilter == null) + { + return new Dictionary(); + } + + log.Write($"Retrieving {nameFilter ?? extensionFilter} files"); + + var files = await FileHelper.GetFiles(ct, solutionRoot, extensionFilter, nameFilter); + + log.Write($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); + + if(target == UpdateTarget.ProjectJson) + { + return files.ToDictionary(f => f, f => default(XmlDocument)); + } + + return (await Task.WhenAll(files.Select(f => f.GetDocument(ct)))) + .ToDictionary(p => p.Key, p => p.Value); + } + } +} diff --git a/src/NuGet.Updater/Helpers/PackageHelper.cs b/src/NuGet.Updater/Helpers/PackageHelper.cs new file mode 100644 index 0000000..eabf02e --- /dev/null +++ b/src/NuGet.Updater/Helpers/PackageHelper.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using NuGet.Versioning; + +namespace NuGet.Updater.Helpers +{ + internal static class PackageHelper + { + private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; + private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; + + public static string GetUrl(string packageId, NuGetVersion version, Uri feedUri) + { + if(feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) + { + return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; + } + + var pattern = LegacyAzureArtifactsFeedUrlPattern; + + if(feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com")) + { + pattern = AzureArtifactsFeedUrlPattern; + } + + var match = Regex.Match(feedUri.AbsoluteUri, pattern); + + if(match.Length > 0) + { + string accountName = match.Groups["account"].Value; + string feedName = match.Groups["feed"].Value; + + return $"https://dev.azure.com/{accountName}/_packaging?_a=package&feed={feedName}&package={packageId}&version={version.ToFullString()}&protocolType=NuGet"; + } + + return default; + } + } +} diff --git a/src/NuGet.Updater/Log/Logger.cs b/src/NuGet.Updater/Log/Logger.cs new file mode 100644 index 0000000..fae7ca4 --- /dev/null +++ b/src/NuGet.Updater/Log/Logger.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using NuGet.Updater.Helpers; +using Uno.Extensions; + +namespace NuGet.Updater.Log +{ + public class Logger + { + private readonly List _updateOperations = new List(); + private readonly TextWriter _writer; + private readonly string _summaryFilePath; + + public Logger(TextWriter writer, string summaryFilePath = null) + { + _writer = writer +#if DEBUG + ?? Console.Out; +#else + ?? TextWriter.Null; +#endif + _summaryFilePath = summaryFilePath; + } + + public void Clear() => _updateOperations.Clear(); + + internal IEnumerable GetUpdates() => _updateOperations; + + public void Write(string message) => _writer.WriteLine(message); + + public void Write(IEnumerable operations) + { + foreach (var o in operations) + { + Write(o); + } + } + + public void Write(UpdateOperation operation) + { + Write(operation.GetLogMessage()); + _updateOperations.Add(operation); + } + + public void WriteSummary(UpdaterParameters parameters) + { + foreach (var line in GetSummary(parameters)) + { + Write(line); + } + + if (_summaryFilePath != null) + { + try + { + FileHelper.LogToFile(_summaryFilePath, GetSummary(parameters, includeUrl: true)); + } + catch (Exception ex) + { + Write($"Failed to write to {_summaryFilePath}. Reason : {ex.Message}"); + } + } + } + + private IEnumerable GetSummary(UpdaterParameters parameters, bool includeUrl = false) + { + yield return $"# Package update summary"; + + if (_updateOperations.Count == 0) + { + yield return $"No packages have been updated."; + } + + foreach(var line in parameters.GetSummary()) + { + yield return line; + } + + foreach(var message in LogPackageOperations(_updateOperations.Where(o => o.IsUpdate), isUpdate: true, includeUrl)) + { + yield return message; + } + + foreach(var message in LogPackageOperations(_updateOperations.Where(o => !o.IsUpdate), isUpdate: false, includeUrl)) + { + yield return message; + } + } + + private IEnumerable LogPackageOperations(IEnumerable operations, bool isUpdate, bool includeUrl) + { + if(operations.None()) + { + yield break; + } + + var packages = operations + .Select(o => ( + name: o.PackageName, + version: isUpdate ? o.UpdatedVersion : o.PreviousVersion, + uri: o.FeedUri + )) + .Distinct() + .ToArray(); + + yield return $"## {(isUpdate ? "Updated" : "Skipped")} {packages.Length} packages:"; + + foreach(var p in packages) + { + var logMessage = $"[{p.name}] {(isUpdate ? "to" : "is at version")} [{p.version}]"; + + var url = includeUrl ? PackageHelper.GetUrl(p.name, p.version, p.uri) : default; + + yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; + } + } + } +} diff --git a/src/NuGet.Updater/Entities/ActionTextWriter.cs b/src/NuGet.Updater/Log/SimpleTextWriter.cs similarity index 70% rename from src/NuGet.Updater/Entities/ActionTextWriter.cs rename to src/NuGet.Updater/Log/SimpleTextWriter.cs index b307a32..a2272d8 100644 --- a/src/NuGet.Updater/Entities/ActionTextWriter.cs +++ b/src/NuGet.Updater/Log/SimpleTextWriter.cs @@ -2,13 +2,13 @@ using System.IO; using System.Text; -namespace NuGet.Updater.Entities +namespace NuGet.Updater.Log { - public class ActionTextWriter : TextWriter + public class SimpleTextWriter : TextWriter { private readonly Action _writeAction; - public ActionTextWriter(Action writeAction) + public SimpleTextWriter(Action writeAction) { _writeAction = writeAction ?? new Action(_ => { }); } diff --git a/src/NuGet.Updater/Log/UpdateOperation.cs b/src/NuGet.Updater/Log/UpdateOperation.cs new file mode 100644 index 0000000..354d4ca --- /dev/null +++ b/src/NuGet.Updater/Log/UpdateOperation.cs @@ -0,0 +1,62 @@ +using System; +using NuGet.Updater.Entities; +using NuGet.Versioning; + +namespace NuGet.Updater.Log +{ + public class UpdateOperation + { + public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, UpdaterVersion updatedVersion, string filePath) + { + Date = DateTimeOffset.Now; + + PackageName = packageName; + PreviousVersion = previousVersion; + UpdatedVersion = updatedVersion.Version; + FilePath = filePath; + FeedUri = updatedVersion.FeedUri; + + IsLatestVersion = PreviousVersion == UpdatedVersion; + IsDowngrade = PreviousVersion.IsGreaterThan(UpdatedVersion) && isDowngradeAllowed; + IsUpdate = UpdatedVersion.IsGreaterThan(PreviousVersion) || (!IsLatestVersion && isDowngradeAllowed); + } + + public DateTimeOffset Date { get; } + + public string PackageName { get; } + + public NuGetVersion PreviousVersion { get; } + + public NuGetVersion UpdatedVersion { get; } + + public string FilePath { get; } + + public Uri FeedUri { get; } + + public bool IsUpdate { get; } + + public bool IsLatestVersion { get; } + + public bool IsDowngrade { get; } + + public string GetLogMessage() + { + if(IsLatestVersion) + { + return $"Version [{UpdatedVersion}] of [{PackageName}] already found in [{FilePath}]. Skipping."; + } + else if(IsDowngrade) + { + return $"Downgrading [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; + } + else if(IsUpdate) + { + return $"Updating [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; + } + else + { + return $"Higher verson of [{PackageName}] ([{UpdatedVersion}]) found in [{FilePath}]. Skipping."; + } + } + } +} diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj index 56b698e..0badd97 100644 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -22,6 +22,7 @@ + diff --git a/src/NuGet.Updater/NuGetUpdater.Execution.cs b/src/NuGet.Updater/NuGetUpdater.Execution.cs index 1aa9e28..1a3ec71 100644 --- a/src/NuGet.Updater/NuGetUpdater.Execution.cs +++ b/src/NuGet.Updater/NuGetUpdater.Execution.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Threading; using System.Threading.Tasks; using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using NuGet.Updater.Log; namespace NuGet.Updater { @@ -11,86 +12,6 @@ namespace NuGet.Updater /// public partial class NuGetUpdater { - public static bool Update( - string solutionRoot, - string sourceFeed, - IEnumerable targetVersions, - string excludeTag = "", - string feedAccessToken = "", - bool includeNuGetOrg = true, - string publicPackageOwner = null, - bool allowDowngrade = false, - bool strict = true, - IEnumerable keepLatestDev = null, - IEnumerable ignorePackages = null, - IEnumerable updatePackages = null, - UpdateTarget target = UpdateTarget.All, - TextWriter logWriter = null, - string summaryOutputFilePath = null - ) => UpdateAsync( - CancellationToken.None, - solutionRoot, - sourceFeed, - targetVersions, - excludeTag, - feedAccessToken, - includeNuGetOrg, - publicPackageOwner, - allowDowngrade, - strict, - keepLatestDev, - ignorePackages, - updatePackages, - target, - logWriter, - summaryOutputFilePath - ).Result; - - public static bool Update( - UpdaterParameters parameters, - TextWriter logWriter = null, - string summaryOutputFilePath = null - ) => UpdateAsync(CancellationToken.None, parameters, new Logger(logWriter, summaryOutputFilePath)).Result; - - public static async Task UpdateAsync( - CancellationToken ct, - string solutionRoot, - string sourceFeed, - IEnumerable targetVersions, - string excludeTag = "", - string feedAccessToken = "", - bool includeNuGetOrg = true, - string publicPackageOwner = null, - bool isDowngradeAllowed = false, - bool strict = true, - IEnumerable packagesTokeepAtLatestDev = null, - IEnumerable packagesToIgnore = null, - IEnumerable packagesToUpdate = null, - UpdateTarget updateTarget = UpdateTarget.All, - TextWriter logWriter = null, - string summaryOutputFilePath = null - ) - { - var parameters = new UpdaterParameters - { - SolutionRoot = solutionRoot, - SourceFeed = sourceFeed, - SourceFeedPersonalAccessToken = feedAccessToken, - TargetVersions = targetVersions, - Strict = strict, - TagToExclude = excludeTag, - UpdateTarget = updateTarget, - IncludeNuGetOrg = includeNuGetOrg, - PublickPackageOwner = publicPackageOwner, - IsDowngradeAllowed = isDowngradeAllowed, - PackagesToKeepAtLatestDev = packagesTokeepAtLatestDev, - PackagesToIgnore = packagesToIgnore, - PackagesToUpdate = packagesToUpdate, - }; - - return await UpdateAsync(ct, parameters, logWriter, summaryOutputFilePath); - } - public static Task UpdateAsync( CancellationToken ct, UpdaterParameters parameters, @@ -104,7 +25,7 @@ public static async Task UpdateAsync( Logger log ) { - var updater = new NuGetUpdater(parameters, log); + var updater = new NuGetUpdater(parameters, parameters.GetSources(), log); return await updater.UpdatePackages(ct); } } diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 905981b..c9f7376 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -6,8 +6,9 @@ using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; -using NuGet.Configuration; using NuGet.Versioning; +using Uno.Extensions; +using NuGet.Updater.Log; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; @@ -15,6 +16,8 @@ using XmlDocument = System.Xml.XmlDocument; #endif +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NuGet.Updater.Tests")] + namespace NuGet.Updater { /// @@ -22,23 +25,24 @@ namespace NuGet.Updater /// public partial class NuGetUpdater { - private static readonly PackageSource NuGetOrgSource = new PackageSource("https://api.nuget.org/v3/index.json"); - private readonly UpdaterParameters _parameters; private readonly Logger _log; + private readonly IUpdaterSource[] _packageSources; - private NuGetUpdater(UpdaterParameters parameters, Logger log) + internal NuGetUpdater(UpdaterParameters parameters, IUpdaterSource[] packageSources, Logger log) { + //TODO validate parameters _parameters = parameters; _log = log; + _packageSources = packageSources; } - private async Task UpdatePackages(CancellationToken ct) + internal async Task UpdatePackages(CancellationToken ct) { _log.Clear(); var packages = await GetPackages(ct); - var targetFiles = await GetTargetFiles(ct); + var targetFiles = await FileHelper.GetTargetFiles(ct, _parameters.UpdateTarget, _parameters.SolutionRoot, _log); foreach(var package in packages.Where(p => _parameters.ShouldUpdatePackage(p))) { @@ -84,100 +88,19 @@ private async Task UpdatePackages(CancellationToken ct) private async Task GetPackages(CancellationToken ct) { - //Using search instead of list because the latter forces the v2 api - var packages = await _parameters.GetFeedPackageSource().SearchPackages(ct, _log.Write); - - if(_parameters.IncludeNuGetOrg) - { - //Using search instead of list because the latter forces the v2 api - packages = packages - .Concat(await NuGetOrgSource.SearchPackages(ct, _log.Write, searchTerm: $"owner:{_parameters.PublicPackageOwner}")) - .GroupBy(p => p.PackageId) - .Select(g => new NuGetPackage(g.Key, g.ToArray())) - .ToArray(); - } - - return packages; - } - - private async Task>> GetTargetFiles(CancellationToken ct) - { - var targetFiles = new Dictionary>(); - - var updateTarget = new[] - { - UpdateTarget.Nuspec, - UpdateTarget.PackageReference, - UpdateTarget.ProjectJson, - UpdateTarget.DirectoryProps, - UpdateTarget.DirectoryTargets, - }; - - foreach(var target in updateTarget) - { - if(_parameters.HasUpdateTarget(target)) - { - targetFiles.Add(target, await GetFilesForTarget(ct, target)); - } - } - - return targetFiles; - } - - private async Task> GetFilesForTarget(CancellationToken ct, UpdateTarget target) - { - string extensionFilter = null, nameFilter = null; - - switch(target) - { - case UpdateTarget.Nuspec: - extensionFilter = ".nuspec"; - break; - - case UpdateTarget.ProjectJson: - nameFilter = "project.json"; - break; - - case UpdateTarget.PackageReference: - extensionFilter = ".csproj"; - break; - - case UpdateTarget.DirectoryTargets: - nameFilter = "Directory.Build.targets"; - break; - - case UpdateTarget.DirectoryProps: - nameFilter = "Directory.Build.props"; - break; - - default: - break; - } - - if(extensionFilter == null && nameFilter == null) - { - return new Dictionary(); - } - - _log.Write($"Retrieving {nameFilter ?? extensionFilter} files"); - - var files = await FileHelper.GetFiles(ct, _parameters.SolutionRoot, extensionFilter, nameFilter); - - _log.Write($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); - - if(target == UpdateTarget.ProjectJson) - { - return files.ToDictionary(f => f, f => default(XmlDocument)); - } + var packagesPerSource = await Task.WhenAll(_packageSources.Select(s => s.GetPackages(ct))); - return (await Task.WhenAll(files.Select(f => f.GetDocument(ct)))) - .ToDictionary(p => p.Key, p => p.Value); + return packagesPerSource + .SelectMany(x => x) + .GroupBy(p => p.PackageId) + .Select(g => new NuGetPackage(g.Key, g.ToArray())) + .ToArray(); } private static async Task UpdateNuSpecs( CancellationToken ct, string packageId, - FeedNuGetVersion version, + UpdaterVersion version, Dictionary nuspecDocuments, bool isDowngradeAllowed ) @@ -191,7 +114,7 @@ bool isDowngradeAllowed var updates = xmlDocument.UpdateNuSpecVersions(packageId, version, path, isDowngradeAllowed); - if(updates.Any(u => u.ShouldProceed)) + if(updates.Any(u => u.IsUpdate)) { await xmlDocument.Save(ct, path); } @@ -205,7 +128,7 @@ bool isDowngradeAllowed private static async Task UpdateProjects( CancellationToken ct, string packageId, - FeedNuGetVersion version, + UpdaterVersion version, Dictionary projectDocuments, bool isDowngradeAllowed ) @@ -219,7 +142,7 @@ bool isDowngradeAllowed var updates = xmlDocument.UpdateProjectReferenceVersions(packageId, version, path, isDowngradeAllowed); - if(updates.Any(u => u.ShouldProceed)) + if(updates.Any(u => u.IsUpdate)) { await xmlDocument.Save(ct, path); } @@ -233,7 +156,7 @@ bool isDowngradeAllowed private static async Task UpdateProjectJson( CancellationToken ct, string packageName, - FeedNuGetVersion latestVersion, + UpdaterVersion latestVersion, string[] jsonFiles, bool isDowngradeAllowed ) @@ -255,7 +178,7 @@ bool isDowngradeAllowed var operation = new UpdateOperation(isDowngradeAllowed, packageName, currentVersion, latestVersion, file); - if(operation.ShouldProceed) + if(operation.IsUpdate) { var newContent = Regex.Replace( fileContent, From e00c325b976769081bf8c2a97cfca2218e67c30c Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 29 Jul 2019 11:20:49 -0400 Subject: [PATCH 096/201] Added test project --- .../Entities/TestPackage.cs | 61 +++++++++++++++++++ .../Entities/TestPackageSource.cs | 24 ++++++++ .../NuGet.Updater.Tests.csproj | 19 ++++++ src/NuGet.Updater.Tests/NuGetPackageTests.cs | 46 ++++++++++++++ src/NuGet.Updater.Tests/NuGetUpdaterTests.cs | 41 +++++++++++++ src/NuGet.Updater.sln | 6 ++ 6 files changed, 197 insertions(+) create mode 100644 src/NuGet.Updater.Tests/Entities/TestPackage.cs create mode 100644 src/NuGet.Updater.Tests/Entities/TestPackageSource.cs create mode 100644 src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj create mode 100644 src/NuGet.Updater.Tests/NuGetPackageTests.cs create mode 100644 src/NuGet.Updater.Tests/NuGetUpdaterTests.cs diff --git a/src/NuGet.Updater.Tests/Entities/TestPackage.cs b/src/NuGet.Updater.Tests/Entities/TestPackage.cs new file mode 100644 index 0000000..7069d64 --- /dev/null +++ b/src/NuGet.Updater.Tests/Entities/TestPackage.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Protocol.Core.Types; + +namespace NuGet.Updater.Tests.Entities +{ + public class TestPackage : IPackageSearchMetadata + { + private string[] _versions; + + public TestPackage(string id, params string[] versions) + { + Identity = new PackageIdentity(id, null); + _versions = versions; + } + + public string Authors { get; set; } + + public IEnumerable DependencySets { get; set; } + + public string Description { get; set; } + + public long? DownloadCount { get; set; } + + public Uri IconUrl { get; set; } + + public PackageIdentity Identity { get; set; } + + public Uri LicenseUrl { get; set; } + + public Uri ProjectUrl { get; set; } + + public Uri ReportAbuseUrl { get; set; } + + public Uri PackageDetailsUrl { get; set; } + + public DateTimeOffset? Published { get; set; } + + public string Owners { get; set; } + + public bool RequireLicenseAcceptance { get; set; } + + public string Summary { get; set; } + + public string Tags { get; set; } + + public string Title { get; set; } + + public bool IsListed { get; set; } + + public bool PrefixReserved { get; set; } + + public LicenseMetadata LicenseMetadata { get; set; } + + public async Task> GetVersionsAsync() => _versions.Select(v => new VersionInfo(new Versioning.NuGetVersion(v))); + } +} diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs new file mode 100644 index 0000000..28e8253 --- /dev/null +++ b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Updater.Entities; +using NuGet.Updater.Log; + +namespace NuGet.Updater.Tests.Entities +{ + public class TestPackageSource : IUpdaterSource + { + public TestPackageSource(Uri uri, params TestPackage[] packages) + { + Uri = uri; + Packages = packages?.Select(p => new NuGetPackage(p, Uri)).ToArray(); + } + + public Uri Uri { get; } + + public NuGetPackage[] Packages { get; } + + public async Task GetPackages(CancellationToken ct, Logger log = null) => Packages; + } +} diff --git a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj b/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj new file mode 100644 index 0000000..cdbb4f9 --- /dev/null +++ b/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + diff --git a/src/NuGet.Updater.Tests/NuGetPackageTests.cs b/src/NuGet.Updater.Tests/NuGetPackageTests.cs new file mode 100644 index 0000000..deb5984 --- /dev/null +++ b/src/NuGet.Updater.Tests/NuGetPackageTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using NuGet.Updater.Tests.Entities; + +namespace NuGet.Updater.Tests +{ + [TestClass] + public class NuGetPackageTests + { + [TestMethod] + public async Task GivenPackageWithMatchingVersion_VersionIsFound() + { + var parameters = new UpdaterParameters + { + TargetVersions = new[] { "beta" }, + }; + + var packageVersion = "1.0-beta.1"; + var package = new NuGetPackage(new TestPackage("nventive.NuGet.Updater", packageVersion), new Uri("http://localhost")); + + var version = await package.GetLatestVersion(CancellationToken.None, parameters); + + Assert.IsNotNull(version); + Assert.AreEqual(version.Version.OriginalVersion, packageVersion); + } + + [TestMethod] + public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() + { + var parameters = new UpdaterParameters + { + TargetVersions = new[] { "stable" }, + }; + + var package = new NuGetPackage(new TestPackage("nventive.NuGet.Updater", "1.0-beta.1"), new Uri("http://localhost")); + + var version = await package.GetLatestVersion(CancellationToken.None, parameters); + + Assert.IsNull(version); + } + } +} diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs new file mode 100644 index 0000000..5769d32 --- /dev/null +++ b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Updater.Entities; +using NuGet.Updater.Log; +using NuGet.Updater.Tests.Entities; +using Uno.Extensions; + +namespace NuGet.Updater.Tests +{ + [TestClass] + public class NuGetUpdaterTests + { + private static readonly TestPackageSource TestSource = new TestPackageSource( + new Uri("http://localhost"), + new TestPackage("Uno.UI", "1.0", "1.1-dev.1"), + new TestPackage("Uno.Core", "1.0", "1.0-beta.1"), + new TestPackage("nventive.NuGet.Updater", "1.0-beta.1") + ); + + [TestMethod] + public async Task GivenUnspecifiedTarget_NoUpdateIsMade() + { + var parameters = new UpdaterParameters + { + SolutionRoot = "MySolution.sln", + UpdateTarget = UpdateTarget.Unspecified, + TargetVersions = new[] { "stable" }, + }; + + var logger = new Logger(Console.Out); + + var updater = new NuGetUpdater(parameters, new[] { TestSource }, logger); + + await updater.UpdatePackages(CancellationToken.None); + + Assert.IsTrue(logger.GetUpdates().None()); + } + } +} diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index 8957c4f..cd85ee5 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tests", "NuGet.Updater.Tests\NuGet.Updater.Tests.csproj", "{D240ABFD-6A48-4E4B-A946-9EBF9840FADA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +36,10 @@ Global {3DAA81DB-00A6-4A84-8180-19A588398F52}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DAA81DB-00A6-4A84-8180-19A588398F52}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DAA81DB-00A6-4A84-8180-19A588398F52}.Release|Any CPU.Build.0 = Release|Any CPU + {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 7195a8e887deb99b69b521230eafb8455db9ab40 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 29 Jul 2019 13:32:57 -0400 Subject: [PATCH 097/201] Updated changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7296680..1753839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added aliases for .Net Core application +- Added support for multiple target versions + ### Changed ### Deprecated ### Removed +- Removed superfluous arguments ### Fixed From 8120e60eb7053b0aec2cc1e6fe264410ad74fd57 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 29 Jul 2019 14:48:53 -0400 Subject: [PATCH 098/201] Fixed detection of no parameters set --- src/NuGet.Updater.Tool/Program.cs | 70 ++++++++++++++++++------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index b457161..b2f0044 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -9,11 +9,8 @@ namespace NuGet.Updater.Tool { public class Program { - private static readonly UpdaterParameters _parameters = new UpdaterParameters - { - UpdateTarget = UpdateTarget.All, - PrivateFeeds = new Dictionary(), - }; + private static bool _isParameterSet = default; + private static UpdaterParameters _parameters = default; public static async Task Main(string[] args) { @@ -24,24 +21,28 @@ public static async Task Main(string[] args) var options = new OptionSet { { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The path to the solution to update", s => _parameters.SolutionRoot = s }, - { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => ParsePrivateFeed(s, '|') }, - { "version=|versions=|v=", "The target versions to use", s => _parameters.TargetVersions = GetList(s)}, - { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => _parameters.IncludeNuGetOrg = true }, - { "packagesOwner=|o=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => _parameters.PackagesOwner = s }, - { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => _parameters.IsDowngradeAllowed = GetBoolean(s) }, - { "ignore=|i=", "A comma-separated list of packages to ignore", s => _parameters.PackagesToIgnore = GetList(s) }, - { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => _parameters.PackagesToUpdate = GetList(s) }, + { "solution=|s=", "The path to the solution to update", s => Set(p => p.SolutionRoot = s) }, + { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => AddPrivateFeed(s) }, + { "version=|versions=|v=", "The target versions to use", s => Set(p => p.TargetVersions = GetList(s))}, + { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, + { "packagesOwner=|o=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => Set(p => p.PackagesOwner = s)}, + { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = GetBoolean(s))}, + { "ignore=|i=", "A comma-separated list of packages to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, + { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, { "outputFile=|of=", "The path to a file where the update summary will be written", s => summaryFile = s }, { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, }; - if(options.Parse(args).Count == 0) + _isParameterSet = false; + _parameters = new UpdaterParameters { - isHelp = true; - } + UpdateTarget = UpdateTarget.All, + PrivateFeeds = new Dictionary(), + }; + + options.Parse(args); - if(isHelp) + if(isHelp || !_isParameterSet) { Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); options.WriteOptionDescriptions(Console.Out); @@ -52,6 +53,28 @@ public static async Task Main(string[] args) } } + private static void Set(Action set) + { + set(_parameters); + _isParameterSet = true; + } + + private static void AddPrivateFeed(string value) + { + const char separator = '|'; + if(value.Contains(separator)) + { + var parts = value.Split(separator); + _parameters.PrivateFeeds.Add(parts[0], parts[1]); + } + else + { + _parameters.PrivateFeeds.Add(value, null); + } + + _isParameterSet = true; + } + private static bool GetBoolean(string value, bool fallbackValue = false) { if(bool.TryParse(value, out var boolean)) @@ -77,18 +100,5 @@ private static List GetList(string value) return list; } - - private static void ParsePrivateFeed(string value, char separator) - { - if(value.Contains(separator)) - { - var parts = value.Split(separator); - _parameters.PrivateFeeds.Add(parts[0], parts[1]); - } - else - { - _parameters.PrivateFeeds.Add(value, null); - } - } } } From f01cf6f410ea14615ef38f01c5eed3545e8bf3f4 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 29 Jul 2019 15:32:55 -0400 Subject: [PATCH 099/201] Added more information in the summary --- .../Extensions/UpdateTargetExtensions.cs | 26 +++++++++++++++++++ .../Extensions/UpdaterParametersExtension.cs | 15 ++++++++--- src/NuGet.Updater/Helpers/FileHelper.cs | 15 +++-------- 3 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs diff --git a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs new file mode 100644 index 0000000..e687911 --- /dev/null +++ b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs @@ -0,0 +1,26 @@ +using NuGet.Updater.Entities; + +namespace NuGet.Updater.Extensions +{ + public static class UpdateTargetExtensions + { + public static string GetDescription(this UpdateTarget target) + { + switch(target) + { + case UpdateTarget.Nuspec: + return ".nuspec"; + case UpdateTarget.ProjectJson: + return "project.json"; + case UpdateTarget.PackageReference: + return ".csproj"; + case UpdateTarget.DirectoryProps: + return "Directory.Build.targets"; + case UpdateTarget.DirectoryTargets: + return "Directory.Build.props"; + default: + return default; + } + } + } +} diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index fd65a58..5a66fbf 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -38,13 +38,22 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters { yield return $"## Configuration"; - yield return $"- Update targetting files under {parameters.SolutionRoot}"; + var files = parameters.UpdateTarget == UpdateTarget.All + ? string.Join(", ", Enum + .GetValues(typeof(UpdateTarget)) + .Cast() + .Select(t => t.GetDescription()) + .Trim() + ) + : parameters.UpdateTarget.GetDescription(); + + yield return $"- Update targeting {files} files under {parameters.SolutionRoot}"; var packageSources = new List(); if(parameters.IncludeNuGetOrg) { - packageSources.Add("NuGet.org"); + packageSources.Add(parameters.PackagesOwner.SelectOrDefault(o => $"NuGet.org (limited to packages owned by {o})", "NuGet.org")); } if(parameters.PrivateFeeds?.Any() ?? false) @@ -54,7 +63,7 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Using NuGet packages from {string.Join(", ", packageSources)}"; - yield return $"- Using target version {string.Join(", then ", parameters.TargetVersions)}"; + yield return $"- Using {(parameters.Strict ? "exact " : "")}target version {string.Join(", then ", parameters.TargetVersions)}"; if (parameters.IsDowngradeAllowed) { diff --git a/src/NuGet.Updater/Helpers/FileHelper.cs b/src/NuGet.Updater/Helpers/FileHelper.cs index d3f8108..def0ce6 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.cs @@ -49,23 +49,14 @@ private static async Task> GetFilesForTarget(Can switch(target) { case UpdateTarget.Nuspec: - extensionFilter = ".nuspec"; - break; - - case UpdateTarget.ProjectJson: - nameFilter = "project.json"; - break; - case UpdateTarget.PackageReference: - extensionFilter = ".csproj"; + extensionFilter = target.GetDescription(); break; + case UpdateTarget.ProjectJson: case UpdateTarget.DirectoryTargets: - nameFilter = "Directory.Build.targets"; - break; - case UpdateTarget.DirectoryProps: - nameFilter = "Directory.Build.props"; + nameFilter = target.GetDescription(); break; default: From 3c1b173c12a74a212e3e0bd67f08a32ade269a18 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 30 Jul 2019 14:04:29 -0400 Subject: [PATCH 100/201] Ensured SimpleTextWriter implements WriteLine --- src/NuGet.Updater/Log/SimpleTextWriter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NuGet.Updater/Log/SimpleTextWriter.cs b/src/NuGet.Updater/Log/SimpleTextWriter.cs index a2272d8..7b18731 100644 --- a/src/NuGet.Updater/Log/SimpleTextWriter.cs +++ b/src/NuGet.Updater/Log/SimpleTextWriter.cs @@ -15,6 +15,8 @@ public SimpleTextWriter(Action writeAction) public override void Write(string value) => _writeAction(value); + public override void WriteLine(string value) => _writeAction(value); + public override Encoding Encoding => Encoding.Default; } } From 4c40aaf7bd4e8ba1eebcce69b2b34e3ae843658a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 30 Jul 2019 15:18:01 -0400 Subject: [PATCH 101/201] Added missing logs --- src/NuGet.Updater/NuGetUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index c9f7376..6f500b6 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -88,7 +88,7 @@ internal async Task UpdatePackages(CancellationToken ct) private async Task GetPackages(CancellationToken ct) { - var packagesPerSource = await Task.WhenAll(_packageSources.Select(s => s.GetPackages(ct))); + var packagesPerSource = await Task.WhenAll(_packageSources.Select(s => s.GetPackages(ct, _log))); return packagesPerSource .SelectMany(x => x) From e7929135ebd9dadf6b1adec398223b0facf31482 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 1 Aug 2019 11:49:05 -0400 Subject: [PATCH 102/201] Added solutionhelper to retrieve package version from the code --- .../SolutionHelperTests.cs | 33 +++++++ .../Properties/launchSettings.json | 7 ++ .../Entities/PackageReference.cs | 17 ++++ .../Extensions/XmlDocumentExtensions.Net.cs | 31 +++++++ src/NuGet.Updater/Helpers/FileHelper.cs | 28 +----- src/NuGet.Updater/Helpers/SolutionHelper.cs | 91 +++++++++++++++++++ 6 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 src/NuGet.Updater.Tests/SolutionHelperTests.cs create mode 100644 src/NuGet.Updater.Tool/Properties/launchSettings.json create mode 100644 src/NuGet.Updater/Entities/PackageReference.cs create mode 100644 src/NuGet.Updater/Helpers/SolutionHelper.cs diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs new file mode 100644 index 0000000..1ffafe2 --- /dev/null +++ b/src/NuGet.Updater.Tests/SolutionHelperTests.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Updater.Entities; +using NuGet.Updater.Helpers; + +namespace NuGet.Updater.Tests +{ + [TestClass] + public class SolutionHelperTests + { + [TestMethod] + public async Task GivenSolution_TargetFilesAreFound() + { + var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; + + var files = await SolutionHelper.GetTargetFilePaths(CancellationToken.None, solution, UpdateTarget.All); + + Assert.IsTrue(files.Any()); + } + + [TestMethod] + public async Task GivenSolution_PackageReferencesAreFound() + { + var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; + + var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution); + + Assert.IsTrue(references.Any()); + } + } +} diff --git a/src/NuGet.Updater.Tool/Properties/launchSettings.json b/src/NuGet.Updater.Tool/Properties/launchSettings.json new file mode 100644 index 0000000..bc41851 --- /dev/null +++ b/src/NuGet.Updater.Tool/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "NuGet.Updater.Tool": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/src/NuGet.Updater/Entities/PackageReference.cs b/src/NuGet.Updater/Entities/PackageReference.cs new file mode 100644 index 0000000..f2632a5 --- /dev/null +++ b/src/NuGet.Updater/Entities/PackageReference.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NuGet.Updater.Entities +{ + public class PackageReference + { + public string Id { get; set; } + + public string Version { get; set; } + + public string[] Files { get; set; } + + public override string ToString() => $"{Id} {Version}"; + } +} diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs index 31a52af..495b31e 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs @@ -17,6 +17,37 @@ public partial class XmlDocumentExtensions { private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + public static Dictionary GetPackageReferences(this XmlDocument document) + { + var references = new Dictionary(); + + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace("d", MsBuildNamespace); + + var packageReferences = document.SelectNodes($"//d:PackageReference", namespaceManager).OfType(); + var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference", namespaceManager).OfType(); + + foreach(XmlElement packageReference in packageReferences.Concat(dotnetCliReferences)) + { + var packageId = packageReference.Attributes["Include"].Value; + + if(packageReference.HasAttribute("Version")) + { + references.Add(packageId, packageReference.Attributes["Version"].Value); + } + else + { + var node = packageReference.SelectSingleNode("d:Version", namespaceManager); + if(node != null) + { + references.Add(packageId, node.InnerText); + } + } + } + + return references; + } + public static UpdateOperation[] UpdateProjectReferenceVersions( this XmlDocument document, string packageId, diff --git a/src/NuGet.Updater/Helpers/FileHelper.cs b/src/NuGet.Updater/Helpers/FileHelper.cs index def0ce6..c824601 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.cs @@ -46,39 +46,17 @@ private static async Task> GetFilesForTarget(Can { string extensionFilter = null, nameFilter = null; - switch(target) - { - case UpdateTarget.Nuspec: - case UpdateTarget.PackageReference: - extensionFilter = target.GetDescription(); - break; - - case UpdateTarget.ProjectJson: - case UpdateTarget.DirectoryTargets: - case UpdateTarget.DirectoryProps: - nameFilter = target.GetDescription(); - break; - - default: - break; - } - - if(extensionFilter == null && nameFilter == null) - { - return new Dictionary(); - } - - log.Write($"Retrieving {nameFilter ?? extensionFilter} files"); - - var files = await FileHelper.GetFiles(ct, solutionRoot, extensionFilter, nameFilter); + var files = await SolutionHelper.GetTargetFilePaths(ct, solutionRoot, target); log.Write($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); + //All the other supported files are XML files if(target == UpdateTarget.ProjectJson) { return files.ToDictionary(f => f, f => default(XmlDocument)); } + //Get the xml document for all the files return (await Task.WhenAll(files.Select(f => f.GetDocument(ct)))) .ToDictionary(p => p.Key, p => p.Value); } diff --git a/src/NuGet.Updater/Helpers/SolutionHelper.cs b/src/NuGet.Updater/Helpers/SolutionHelper.cs new file mode 100644 index 0000000..e6654c4 --- /dev/null +++ b/src/NuGet.Updater/Helpers/SolutionHelper.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using Uno.Extensions; + +namespace NuGet.Updater.Helpers +{ + public static class SolutionHelper + { + internal static async Task GetTargetFilePaths(CancellationToken ct, string solutionPath, UpdateTarget updateTarget) + { + switch(updateTarget) + { + case UpdateTarget.PackageReference: + return await GetProjectFiles(ct, solutionPath); + case UpdateTarget.DirectoryProps: + case UpdateTarget.DirectoryTargets: + return new[] { GetDirectoryFile(solutionPath, updateTarget) }; + case UpdateTarget.Nuspec: + return new string[0]; // find all matching files in the solution folder + case UpdateTarget.ProjectJson: + return new string[0]; //find all csproj, look for project.json next to them + case UpdateTarget.Unspecified: + case UpdateTarget.All: + default: + return new string[0]; + } + } + + internal static async Task GetPackageReferences(CancellationToken ct, string solutionPath) + { + var packages = new List<(string path, string package, string version)>(); + + var files = await GetTargetFilePaths(ct, solutionPath, UpdateTarget.PackageReference); + + foreach(var f in files) + { + var document = (await f.GetDocument(ct)).Value; + + var references = document.GetPackageReferences(); + + packages.AddRange(references.Select(g => (path: f, package: g.Key, version: g.Value))); + } + + return packages + .GroupBy(x => x.package) + .Select(g => g + .GroupBy(x => x.version) + .Select(v => new PackageReference + { + Id = g.Key, + Version = v.Key, + Files = v.Select(x => x.path).ToArray(), + }) + ) + .SelectMany(x => x) + .ToArray(); + } + + private static async Task GetProjectFiles(CancellationToken ct, string solutionPath) + { + var solutionContent = await FileHelper.ReadFileContent(ct, solutionPath); + var solutionFolder = Path.GetDirectoryName(solutionPath); + + var matches = Regex.Matches(solutionContent, "[^\\s\"]*\\.csproj"); + + return matches + .Cast() + .Select(m => Path.GetDirectoryName(solutionPath)) + .ToArray(); + } + + private static string GetDirectoryFile(string solutionPath, UpdateTarget target) + { + var solutionFolder = Path.GetDirectoryName(solutionPath); + var file = Path.Combine(solutionFolder, target.GetDescription()); + + if(File.Exists(file)) + { + return file; + } + + return null; + } + } +} From f2a2949eff0177be75accfbbd9af11b3381e9701 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 1 Aug 2019 18:20:00 -0400 Subject: [PATCH 103/201] NuGet package are now retrieved from the solution --- .../Entities/TestPackageSource.cs | 8 ++- src/NuGet.Updater.Tests/NuGetUpdaterTests.cs | 26 +++++++ .../SolutionHelperTests.cs | 2 +- ...Tests.cs => UpdaterPackagePackageTests.cs} | 10 +-- src/NuGet.Updater/Entities/IUpdaterSource.cs | 4 +- src/NuGet.Updater/Entities/NuGetPackage.cs | 31 -------- .../Entities/PackageReference.cs | 23 +++++- .../Entities/PrivateUpdaterSource.cs | 34 --------- .../Entities/PublicUpdaterSource.cs | 22 ------ src/NuGet.Updater/Entities/UpdateTarget.cs | 4 +- src/NuGet.Updater/Entities/UpdaterPackage.cs | 40 +++++++++++ src/NuGet.Updater/Entities/UpdaterSource.cs | 47 ++++++++++++ src/NuGet.Updater/Entities/UpdaterVersion.cs | 4 +- .../Extensions/DictionaryExtensions.cs | 16 +++++ .../Extensions/NuGetPackageExtensions.cs | 45 ------------ .../Extensions/PackageSourceExtensions.cs | 29 +++++++- .../Extensions/UpdateTargetExtensions.cs | 10 ++- .../Extensions/UpdaterPackageExtensions.cs | 25 +++++++ .../Extensions/UpdaterParametersExtension.cs | 6 +- .../Extensions/XmlDocumentExtensions.Net.cs | 22 ++++-- .../Extensions/XmlDocumentExtensions.UAP.cs | 32 ++++++++- src/NuGet.Updater/Helpers/FileHelper.cs | 64 ----------------- src/NuGet.Updater/Helpers/PackageHelper.cs | 2 - src/NuGet.Updater/Helpers/SolutionHelper.cs | 60 ++++++++++------ src/NuGet.Updater/Log/UpdateOperation.cs | 2 +- src/NuGet.Updater/NuGetUpdater.cs | 71 +++++++++++++------ 26 files changed, 367 insertions(+), 272 deletions(-) rename src/NuGet.Updater.Tests/{NuGetPackageTests.cs => UpdaterPackagePackageTests.cs} (64%) delete mode 100644 src/NuGet.Updater/Entities/NuGetPackage.cs delete mode 100644 src/NuGet.Updater/Entities/PrivateUpdaterSource.cs delete mode 100644 src/NuGet.Updater/Entities/PublicUpdaterSource.cs create mode 100644 src/NuGet.Updater/Entities/UpdaterPackage.cs create mode 100644 src/NuGet.Updater/Entities/UpdaterSource.cs create mode 100644 src/NuGet.Updater/Extensions/DictionaryExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs create mode 100644 src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs delete mode 100644 src/NuGet.Updater/Helpers/FileHelper.cs diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs index 28e8253..6f44662 100644 --- a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs +++ b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs @@ -12,13 +12,15 @@ public class TestPackageSource : IUpdaterSource public TestPackageSource(Uri uri, params TestPackage[] packages) { Uri = uri; - Packages = packages?.Select(p => new NuGetPackage(p, Uri)).ToArray(); + //Packages = packages?.Select(p => new NuGetPackage(p, Uri)).ToArray(); } public Uri Uri { get; } - public NuGetPackage[] Packages { get; } + public UpdaterPackage[] Packages { get; } - public async Task GetPackages(CancellationToken ct, Logger log = null) => Packages; + public async Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null) => Packages.FirstOrDefault(p => p.PackageId == reference.Id); + + public async Task GetPackages(CancellationToken ct, Logger log = null) => Packages; } } diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs index 5769d32..78527fc 100644 --- a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs +++ b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs @@ -1,8 +1,10 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; using NuGet.Updater.Log; using NuGet.Updater.Tests.Entities; using Uno.Extensions; @@ -37,5 +39,29 @@ public async Task GivenUnspecifiedTarget_NoUpdateIsMade() Assert.IsTrue(logger.GetUpdates().None()); } + + [TestMethod] + public async Task GivenParameters_PackagesAreFound() + { + var parameters = new UpdaterParameters + { + SolutionRoot = "MySolution.sln", + UpdateTarget = UpdateTarget.DirectoryProps | UpdateTarget.DirectoryTargets, + IncludeNuGetOrg = true, + TargetVersions = new[] { "stable" }, + }; + + var logger = new Logger(Console.Out); + + var updater = new NuGetUpdater(parameters, parameters.GetSources(), logger); + + var packages = await updater.GetPackages(CancellationToken.None); + + //foreach(var p in packages.Where(p => p.Versions.Any())) + //{ + //} + + Assert.IsTrue(packages.Any()); + } } } diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs index 1ffafe2..63fcdbc 100644 --- a/src/NuGet.Updater.Tests/SolutionHelperTests.cs +++ b/src/NuGet.Updater.Tests/SolutionHelperTests.cs @@ -25,7 +25,7 @@ public async Task GivenSolution_PackageReferencesAreFound() { var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; - var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution); + var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution, UpdateTarget.Csproj); Assert.IsTrue(references.Any()); } diff --git a/src/NuGet.Updater.Tests/NuGetPackageTests.cs b/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs similarity index 64% rename from src/NuGet.Updater.Tests/NuGetPackageTests.cs rename to src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs index deb5984..79d3f6e 100644 --- a/src/NuGet.Updater.Tests/NuGetPackageTests.cs +++ b/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs @@ -9,7 +9,7 @@ namespace NuGet.Updater.Tests { [TestClass] - public class NuGetPackageTests + public class UpdaterPackagePackageTests { [TestMethod] public async Task GivenPackageWithMatchingVersion_VersionIsFound() @@ -20,9 +20,9 @@ public async Task GivenPackageWithMatchingVersion_VersionIsFound() }; var packageVersion = "1.0-beta.1"; - var package = new NuGetPackage(new TestPackage("nventive.NuGet.Updater", packageVersion), new Uri("http://localhost")); + var package = new UpdaterPackage(new TestPackage("nventive.NuGet.Updater", packageVersion), new Uri("http://localhost")); - var version = await package.GetLatestVersion(CancellationToken.None, parameters); + var version = package.GetLatestVersion(parameters); Assert.IsNotNull(version); Assert.AreEqual(version.Version.OriginalVersion, packageVersion); @@ -36,9 +36,9 @@ public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() TargetVersions = new[] { "stable" }, }; - var package = new NuGetPackage(new TestPackage("nventive.NuGet.Updater", "1.0-beta.1"), new Uri("http://localhost")); + var package = new UpdaterPackage(new TestPackage("nventive.NuGet.Updater", "1.0-beta.1"), new Uri("http://localhost")); - var version = await package.GetLatestVersion(CancellationToken.None, parameters); + var version = package.GetLatestVersion(parameters); Assert.IsNull(version); } diff --git a/src/NuGet.Updater/Entities/IUpdaterSource.cs b/src/NuGet.Updater/Entities/IUpdaterSource.cs index 0442cf8..6b8cb77 100644 --- a/src/NuGet.Updater/Entities/IUpdaterSource.cs +++ b/src/NuGet.Updater/Entities/IUpdaterSource.cs @@ -6,6 +6,8 @@ namespace NuGet.Updater.Entities { public interface IUpdaterSource { - Task GetPackages(CancellationToken ct, Logger log = null); + Task GetPackages(CancellationToken ct, Logger log = null); + + Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null); } } diff --git a/src/NuGet.Updater/Entities/NuGetPackage.cs b/src/NuGet.Updater/Entities/NuGetPackage.cs deleted file mode 100644 index 669f0e6..0000000 --- a/src/NuGet.Updater/Entities/NuGetPackage.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NuGet.Protocol.Core.Types; - -namespace NuGet.Updater.Entities -{ - public class NuGetPackage - { - public NuGetPackage(string packageId, params NuGetPackage[] packages) - { - PackageId = packageId; - Packages = packages - .SelectMany(p => p.Packages) - .ToDictionary(p => p.Key, p => p.Value); - } - - public NuGetPackage(IPackageSearchMetadata package, Uri packageSourceUri) - { - PackageId = package.Identity.Id; - Packages = new Dictionary - { - { packageSourceUri, package }, - }; - } - - public string PackageId { get; } - - public Dictionary Packages { get; } - } -} diff --git a/src/NuGet.Updater/Entities/PackageReference.cs b/src/NuGet.Updater/Entities/PackageReference.cs index f2632a5..dd79272 100644 --- a/src/NuGet.Updater/Entities/PackageReference.cs +++ b/src/NuGet.Updater/Entities/PackageReference.cs @@ -6,11 +6,28 @@ namespace NuGet.Updater.Entities { public class PackageReference { - public string Id { get; set; } + public PackageReference(string id, string version, string file, UpdateTarget target) + { + Id = id; + Version = version; + Files = new Dictionary + { + { target, new[] { file } }, + }; + } - public string Version { get; set; } + public PackageReference(string id, string version, Dictionary files) + { + Id = id; + Version = version; + Files = files; + } + + public string Id { get; } + + public string Version { get; } - public string[] Files { get; set; } + public Dictionary Files { get; } public override string ToString() => $"{Id} {Version}"; } diff --git a/src/NuGet.Updater/Entities/PrivateUpdaterSource.cs b/src/NuGet.Updater/Entities/PrivateUpdaterSource.cs deleted file mode 100644 index 66614c6..0000000 --- a/src/NuGet.Updater/Entities/PrivateUpdaterSource.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using NuGet.Configuration; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; - -namespace NuGet.Updater.Entities -{ - public class PrivateUpdaterSource : IUpdaterSource - { - private readonly PackageSource _packageSource; - - public PrivateUpdaterSource(string url, string accessToken) - { - _packageSource = GetPackageSource(url, accessToken); - } - - public Task GetPackages(CancellationToken ct, Logger log = null) => _packageSource.SearchPackages(ct, log: log); - - private static PackageSource GetPackageSource(string url, string accessToken) - { - var name = url.GetHashCode().ToString(); - - return new PackageSource(url, name) - { -#if UAP - Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false), -#else - Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false, null), -#endif - }; - } - } -} diff --git a/src/NuGet.Updater/Entities/PublicUpdaterSource.cs b/src/NuGet.Updater/Entities/PublicUpdaterSource.cs deleted file mode 100644 index 8f32078..0000000 --- a/src/NuGet.Updater/Entities/PublicUpdaterSource.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using NuGet.Configuration; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; - -namespace NuGet.Updater.Entities -{ - public class PublicUpdaterSource : IUpdaterSource - { - private readonly PackageSource _packageSource; - private readonly string _packageOwner; - - public PublicUpdaterSource(string url, string packageOwner) - { - _packageSource = new PackageSource(url); - _packageOwner = packageOwner; - } - - public Task GetPackages(CancellationToken ct, Logger log = null) => _packageSource.SearchPackages(ct, $"owner:{_packageOwner}", log); - } -} diff --git a/src/NuGet.Updater/Entities/UpdateTarget.cs b/src/NuGet.Updater/Entities/UpdateTarget.cs index d7c6601..4a7f759 100644 --- a/src/NuGet.Updater/Entities/UpdateTarget.cs +++ b/src/NuGet.Updater/Entities/UpdateTarget.cs @@ -23,7 +23,7 @@ public enum UpdateTarget /// /// PackageReferences from csproj. /// - PackageReference = 8, + Csproj = 8, /// /// Directory.Build.props files. @@ -38,6 +38,6 @@ public enum UpdateTarget /// /// All the supported file types. /// - All = Nuspec | ProjectJson | PackageReference | DirectoryProps | DirectoryTargets, + All = Nuspec | ProjectJson | Csproj | DirectoryProps | DirectoryTargets, } } diff --git a/src/NuGet.Updater/Entities/UpdaterPackage.cs b/src/NuGet.Updater/Entities/UpdaterPackage.cs new file mode 100644 index 0000000..4c0a766 --- /dev/null +++ b/src/NuGet.Updater/Entities/UpdaterPackage.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using NuGet.Protocol.Core.Types; + +namespace NuGet.Updater.Entities +{ + public class UpdaterPackage + { + public UpdaterPackage(IPackageSearchMetadata version, Uri sourceUri) + { + PackageId = version.Identity.Id; + //Versions = new[] { version }; + SourceUri = sourceUri; + + Packages = new Dictionary + { + { sourceUri, version }, + }; + } + + public UpdaterPackage(string id, UpdaterVersion[] versions, Uri sourceUri, PackageReference reference) + { + PackageId = id; + AvailableVersions = versions; + SourceUri = sourceUri; + Reference = reference; + } + + public Uri SourceUri { get; set; } + + public string PackageId { get; } + + public PackageReference Reference { get; set; } + + public UpdaterVersion[] AvailableVersions { get; } + + //TO Remove + public Dictionary Packages { get; } + } +} diff --git a/src/NuGet.Updater/Entities/UpdaterSource.cs b/src/NuGet.Updater/Entities/UpdaterSource.cs new file mode 100644 index 0000000..c0d13a0 --- /dev/null +++ b/src/NuGet.Updater/Entities/UpdaterSource.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Configuration; +using NuGet.Updater.Extensions; +using NuGet.Updater.Log; + +namespace NuGet.Updater.Entities +{ + public class UpdaterSource : IUpdaterSource + { + private readonly PackageSource _packageSource; + private readonly string _packageOwner; + + public UpdaterSource(string url, string packageOwner) + { + _packageOwner = packageOwner; + _packageSource = new PackageSource(url); + } + + public UpdaterSource(string url, string accessToken, string packageOwner) + { + _packageOwner = packageOwner; + _packageSource = GetPackageSource(url, accessToken); + } + + public Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null) => _packageSource.GetPackage(ct, reference, log); + + public Task GetPackages(CancellationToken ct, Logger log = null) => _packageSource.SearchPackages(ct, log: log); + + private static PackageSource GetPackageSource(string url, string accessToken) + { + var name = url.GetHashCode().ToString(); + + return new PackageSource(url, name) + { +#if UAP + Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false), +#else + Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false, null), +#endif + }; + } + } +} diff --git a/src/NuGet.Updater/Entities/UpdaterVersion.cs b/src/NuGet.Updater/Entities/UpdaterVersion.cs index 6a65cd6..d149beb 100644 --- a/src/NuGet.Updater/Entities/UpdaterVersion.cs +++ b/src/NuGet.Updater/Entities/UpdaterVersion.cs @@ -3,7 +3,7 @@ namespace NuGet.Updater.Entities { - public class UpdaterVersion + public class UpdaterVersion : IComparable { public UpdaterVersion(Uri feedUri, NuGetVersion version) { @@ -14,5 +14,7 @@ public UpdaterVersion(Uri feedUri, NuGetVersion version) public Uri FeedUri { get; } public NuGetVersion Version { get; } + + public int CompareTo(UpdaterVersion other) => Version.CompareTo(other.Version); } } diff --git a/src/NuGet.Updater/Extensions/DictionaryExtensions.cs b/src/NuGet.Updater/Extensions/DictionaryExtensions.cs new file mode 100644 index 0000000..93a3eaa --- /dev/null +++ b/src/NuGet.Updater/Extensions/DictionaryExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NuGet.Updater.Extensions +{ + public static class DictionaryExtensions + { + public static Dictionary GetItems( + this IDictionary dictionary, + params TKey[] keys + ) => dictionary + .Where(g => keys.Contains(g.Key)) + .ToDictionary(g => g.Key, g => g.Value); + } +} diff --git a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs b/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs deleted file mode 100644 index accae9f..0000000 --- a/src/NuGet.Updater/Extensions/NuGetPackageExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Updater.Entities; -using Uno.Extensions; - -namespace NuGet.Updater.Extensions -{ - public static class NuGetPackageExtensions - { - public static async Task GetLatestVersion( - this NuGetPackage package, - CancellationToken ct, - UpdaterParameters parameters - ) - { - var versions = await package.GetVersions(ct); - - var versionsPerTarget = versions - .OrderByDescending(v => v.Version) - .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) - .Where(g => g.Key.HasValue()); - - return versionsPerTarget - .Select(g => g.FirstOrDefault()) - .OrderByDescending(v => v.Version) - .FirstOrDefault(); - } - - private static async Task> GetVersions(this NuGetPackage package, CancellationToken ct) - { - var versions = new List(); - foreach(var p in package.Packages) - { - foreach(var v in await p.Value.GetVersionsAsync()) - { - versions.Add(new UpdaterVersion(p.Key, v.Version)); - } - } - - return versions; - } - } -} diff --git a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs index 488e82f..bf96ce8 100644 --- a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs @@ -13,7 +13,7 @@ namespace NuGet.Updater.Extensions { public static class PackageSourceExtensions { - public static async Task SearchPackages(this PackageSource source, CancellationToken ct, string searchTerm = "", Logger log = null) + public static async Task SearchPackages(this PackageSource source, CancellationToken ct, string searchTerm = "", Logger log = null) { var settings = Settings.LoadDefaultSettings(null); var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); @@ -31,9 +31,32 @@ public static async Task SearchPackages(this PackageSource sourc return source.ToNuGetPackages(packages); } - private static NuGetPackage[] ToNuGetPackages(this PackageSource source, IPackageSearchMetadata[] packages) => + public static async Task GetPackage(this PackageSource source, CancellationToken ct, PackageReference reference, Logger log = null) + { + var settings = Settings.LoadDefaultSettings(null); + var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + + var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); + + var packageId = reference.Id; + + log?.Write($"Getting package {packageId} from {source.SourceUri}"); + + var resource = repository.GetResource(); + + var metadata = await resource.GetMetadataAsync(packageId, true, false, new SourceCacheContext(), new NullLogger(), ct); + + var versions = metadata + .Cast() + .Select(m => new UpdaterVersion(source.SourceUri, m.Version)) + .ToArray(); + + return new UpdaterPackage(packageId, versions, source.SourceUri, reference); + } + + private static UpdaterPackage[] ToNuGetPackages(this PackageSource source, IPackageSearchMetadata[] packages) => packages - .Select(p => new NuGetPackage(p, source.SourceUri)) + .Select(p => new UpdaterPackage(p, source.SourceUri)) .ToArray(); } } diff --git a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs index e687911..fcbe215 100644 --- a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs +++ b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs @@ -1,4 +1,5 @@ -using NuGet.Updater.Entities; +using System.Linq; +using NuGet.Updater.Entities; namespace NuGet.Updater.Extensions { @@ -12,7 +13,7 @@ public static string GetDescription(this UpdateTarget target) return ".nuspec"; case UpdateTarget.ProjectJson: return "project.json"; - case UpdateTarget.PackageReference: + case UpdateTarget.Csproj: return ".csproj"; case UpdateTarget.DirectoryProps: return "Directory.Build.targets"; @@ -22,5 +23,10 @@ public static string GetDescription(this UpdateTarget target) return default; } } + + public static bool Matches(this UpdateTarget target, params UpdateTarget[] others) + { + return others.Any(t => (target & t) == t); + } } } diff --git a/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs b/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs new file mode 100644 index 0000000..9ec37ff --- /dev/null +++ b/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs @@ -0,0 +1,25 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Updater.Entities; +using Uno.Extensions; + +namespace NuGet.Updater.Extensions +{ + public static class UpdaterPackageExtensions + { + public static UpdaterVersion GetLatestVersion(this UpdaterPackage package, UpdaterParameters parameters) + { + var versionsPerTarget = package + .AvailableVersions + .OrderByDescending(v => v) + .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) + .Where(g => g.Key.HasValue()); + + return versionsPerTarget + .Select(g => g.FirstOrDefault()) + .OrderByDescending(v => v.Version) + .FirstOrDefault(); + } + } +} diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 5a66fbf..9dd3421 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -15,18 +15,18 @@ internal static IUpdaterSource[] GetSources(this UpdaterParameters parameters) if(parameters.PrivateFeeds?.Any() ?? false) { - packageSources.AddRange(parameters.PrivateFeeds.Select(g => new PrivateUpdaterSource(g.Key, g.Value))); + packageSources.AddRange(parameters.PrivateFeeds.Select(g => new UpdaterSource(g.Key, g.Value, parameters.PackagesOwner))); } if(parameters.IncludeNuGetOrg) { - packageSources.Add(new PublicUpdaterSource("https://api.nuget.org/v3/index.json", parameters.PackagesOwner)); + packageSources.Add(new UpdaterSource("https://api.nuget.org/v3/index.json", parameters.PackagesOwner)); } return packageSources.ToArray(); } - internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, NuGetPackage package) + internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, UpdaterPackage package) { var isPackageToIgnore = parameters.PackagesToIgnore?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? false; var isPackageToUpdate = parameters.PackagesToUpdate?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? true; diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs index 495b31e..da9a322 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs @@ -19,11 +19,25 @@ public partial class XmlDocumentExtensions public static Dictionary GetPackageReferences(this XmlDocument document) { - var references = new Dictionary(); - var namespaceManager = new XmlNamespaceManager(document.NameTable); namespaceManager.AddNamespace("d", MsBuildNamespace); + var references = document.GetPackageReferences(namespaceManager); + + namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace("d", ""); + + var otherReferences = document.GetPackageReferences(namespaceManager); + + return references + .Concat(otherReferences) + .ToDictionary(g => g.Key, g => g.Value); + } + + private static Dictionary GetPackageReferences(this XmlDocument document, XmlNamespaceManager namespaceManager) + { + var references = new Dictionary(); + var packageReferences = document.SelectNodes($"//d:PackageReference", namespaceManager).OfType(); var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference", namespaceManager).OfType(); @@ -130,7 +144,7 @@ private static IEnumerable GetElements(this XmlDocument document, st .OfType(); } - public static async Task> GetDocument(this string path, CancellationToken ct) + public static async Task GetDocument(this string path, CancellationToken ct) { var document = new XmlDocument() { @@ -139,7 +153,7 @@ public static async Task> GetDocument(this str document.Load(path); - return new KeyValuePair(path, document); + return document; } public static async Task Save(this XmlDocument document, CancellationToken ct, string path) => document.Save(path); diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs index aac4b16..e3626ae 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs @@ -10,6 +10,7 @@ using NuGet.Updater.Entities; using NuGet.Updater.Log; using NuGet.Versioning; +using Uno.Extensions; using Windows.Data.Xml.Dom; using Windows.Storage; using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; @@ -22,6 +23,33 @@ namespace NuGet.Updater.Extensions /// partial class XmlDocumentExtensions { + public static Dictionary GetPackageReferences(this XmlDocument document) + { + var references = new Dictionary(); + + foreach(var packageReference in document.GetElementsByTagName("PackageReference").Cast()) + { + var packageId = packageReference.GetAttribute("Include"); + + var version = packageReference.GetAttribute("Version"); + if(version.HasValue()) + { + references.Add(packageId, version); + } + else + { + var node = packageReference.GetElementsByTagName("Version").SingleOrDefault(); + + if(node != null) + { + references.Add(packageId, node.InnerText); + } + } + } + + return references; + } + public static UpdateOperation[] UpdateProjectReferenceVersions( this XmlDocument document, string packageId, @@ -82,11 +110,11 @@ private static IEnumerable GetElements(this XmlDocument document, st .SelectNodes(xpath) .OfType(); - public static async Task> GetDocument(this string path, CancellationToken ct) + public static async Task GetDocument(this string path, CancellationToken ct) { var document = await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path), new XmlLoadSettings { ElementContentWhiteSpace = true }); - return new KeyValuePair(path, document); + return document; } public static async Task Save(this XmlDocument document, CancellationToken ct, string path) diff --git a/src/NuGet.Updater/Helpers/FileHelper.cs b/src/NuGet.Updater/Helpers/FileHelper.cs deleted file mode 100644 index c824601..0000000 --- a/src/NuGet.Updater/Helpers/FileHelper.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; - -#if UAP -using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; -#else -using XmlDocument = System.Xml.XmlDocument; -#endif - -namespace NuGet.Updater.Helpers -{ - public static partial class FileHelper - { - public static async Task>> GetTargetFiles( - CancellationToken ct, - UpdateTarget updateTarget, - string solutionRoot, - Logger log - ) - { - var targetFiles = new Dictionary>(); - - var allTargets = new[] - { - UpdateTarget.Nuspec, - UpdateTarget.PackageReference, - UpdateTarget.ProjectJson, - UpdateTarget.DirectoryProps, - UpdateTarget.DirectoryTargets, - }; - - foreach(var target in allTargets.Where(t => (updateTarget & t) == t)) - { - targetFiles.Add(target, await GetFilesForTarget(ct, target, solutionRoot, log)); - } - - return targetFiles; - } - - private static async Task> GetFilesForTarget(CancellationToken ct, UpdateTarget target, string solutionRoot, Logger log) - { - string extensionFilter = null, nameFilter = null; - - var files = await SolutionHelper.GetTargetFilePaths(ct, solutionRoot, target); - - log.Write($"Found {files.Length} {nameFilter ?? extensionFilter} file(s)"); - - //All the other supported files are XML files - if(target == UpdateTarget.ProjectJson) - { - return files.ToDictionary(f => f, f => default(XmlDocument)); - } - - //Get the xml document for all the files - return (await Task.WhenAll(files.Select(f => f.GetDocument(ct)))) - .ToDictionary(p => p.Key, p => p.Value); - } - } -} diff --git a/src/NuGet.Updater/Helpers/PackageHelper.cs b/src/NuGet.Updater/Helpers/PackageHelper.cs index eabf02e..05612cd 100644 --- a/src/NuGet.Updater/Helpers/PackageHelper.cs +++ b/src/NuGet.Updater/Helpers/PackageHelper.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Text.RegularExpressions; using NuGet.Versioning; diff --git a/src/NuGet.Updater/Helpers/SolutionHelper.cs b/src/NuGet.Updater/Helpers/SolutionHelper.cs index e6654c4..56afbcd 100644 --- a/src/NuGet.Updater/Helpers/SolutionHelper.cs +++ b/src/NuGet.Updater/Helpers/SolutionHelper.cs @@ -16,7 +16,7 @@ internal static async Task GetTargetFilePaths(CancellationToken ct, st { switch(updateTarget) { - case UpdateTarget.PackageReference: + case UpdateTarget.Csproj: return await GetProjectFiles(ct, solutionPath); case UpdateTarget.DirectoryProps: case UpdateTarget.DirectoryTargets: @@ -32,33 +32,35 @@ internal static async Task GetTargetFilePaths(CancellationToken ct, st } } - internal static async Task GetPackageReferences(CancellationToken ct, string solutionPath) + internal static async Task GetPackageReferences(CancellationToken ct, string solutionPath, UpdateTarget updateTarget) { - var packages = new List<(string path, string package, string version)>(); + var packages = new List(); - var files = await GetTargetFilePaths(ct, solutionPath, UpdateTarget.PackageReference); - - foreach(var f in files) + if(updateTarget.Matches(UpdateTarget.Csproj)) { - var document = (await f.GetDocument(ct)).Value; + foreach(var f in await GetProjectFiles(ct, solutionPath)) + { + packages.AddRange(await GetFileReferences(ct, f, UpdateTarget.Csproj)); + } + } - var references = document.GetPackageReferences(); + if(updateTarget.Matches(UpdateTarget.DirectoryProps)) + { + packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryProps), UpdateTarget.DirectoryProps)); + } - packages.AddRange(references.Select(g => (path: f, package: g.Key, version: g.Value))); + if(updateTarget.Matches(UpdateTarget.DirectoryTargets)) + { + packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryTargets), UpdateTarget.DirectoryTargets)); } return packages - .GroupBy(x => x.package) - .Select(g => g - .GroupBy(x => x.version) - .Select(v => new PackageReference - { - Id = g.Key, - Version = v.Key, - Files = v.Select(x => x.path).ToArray(), - }) - ) - .SelectMany(x => x) + .GroupBy(p => p.Id) + .Select(g => new PackageReference( + g.Key, + g.Select(p => p.Version).OrderBy(v => v).FirstOrDefault(), + g.SelectMany(p => p.Files).GroupBy(f => f.Key).ToDictionary(f => f.Key, f => f.SelectMany(x => x.Value).Distinct().ToArray()) + )) .ToArray(); } @@ -71,7 +73,7 @@ private static async Task GetProjectFiles(CancellationToken ct, string return matches .Cast() - .Select(m => Path.GetDirectoryName(solutionPath)) + .Select(m => Path.Combine(solutionFolder, m.Value)) .ToArray(); } @@ -87,5 +89,21 @@ private static string GetDirectoryFile(string solutionPath, UpdateTarget target) return null; } + + private static async Task GetFileReferences(CancellationToken ct, string file, UpdateTarget target) + { + if(file.IsNullOrEmpty()) + { + return new PackageReference[0]; + } + + var document = await file.GetDocument(ct); + + var references = document.GetPackageReferences(); + + return references + .Select(g => new PackageReference(g.Key, g.Value, file, target)) + .ToArray(); + } } } diff --git a/src/NuGet.Updater/Log/UpdateOperation.cs b/src/NuGet.Updater/Log/UpdateOperation.cs index 354d4ca..6af6568 100644 --- a/src/NuGet.Updater/Log/UpdateOperation.cs +++ b/src/NuGet.Updater/Log/UpdateOperation.cs @@ -55,7 +55,7 @@ public string GetLogMessage() } else { - return $"Higher verson of [{PackageName}] ([{UpdatedVersion}]) found in [{FilePath}]. Skipping."; + return $"Version [{PreviousVersion}] of [{PackageName}] found in [{FilePath}]. Higher than [{UpdatedVersion}]. Skipping."; } } } diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 6f500b6..aa03b78 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -9,6 +9,7 @@ using NuGet.Versioning; using Uno.Extensions; using NuGet.Updater.Log; +using System; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; @@ -42,11 +43,11 @@ internal async Task UpdatePackages(CancellationToken ct) _log.Clear(); var packages = await GetPackages(ct); - var targetFiles = await FileHelper.GetTargetFiles(ct, _parameters.UpdateTarget, _parameters.SolutionRoot, _log); + var documents = await OpenFiles(ct, packages); foreach(var package in packages.Where(p => _parameters.ShouldUpdatePackage(p))) { - var latest = await package.GetLatestVersion(ct, _parameters); + var latest = package.GetLatestVersion(_parameters); if(latest == null) { @@ -54,31 +55,31 @@ internal async Task UpdatePackages(CancellationToken ct) continue; } - _log.Write($"Latest matching version for [{package.PackageId}] is [{latest.Version}]"); + _log.Write($"Latest matching version for [{package.PackageId}] is [{latest.Version}] on {package.SourceUri}"); - foreach(var files in targetFiles) - { - var updates = new UpdateOperation[0]; + var updates = new UpdateOperation[0]; + foreach(var files in package.Reference.Files) + { switch(files.Key) { - case UpdateTarget.Nuspec: - updates = await UpdateNuSpecs(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); + case var t when t.Matches(UpdateTarget.Nuspec): + updates = await UpdateNuSpecs(ct, package.PackageId, latest, documents.GetItems(files.Value), _parameters.IsDowngradeAllowed); break; - case UpdateTarget.ProjectJson: - updates = await UpdateProjectJson(ct, package.PackageId, latest, files.Value.Select(p => p.Key).ToArray(), _parameters.IsDowngradeAllowed); + case var t when t.Matches(UpdateTarget.ProjectJson): + updates = await UpdateProjectJson(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); break; - case UpdateTarget.DirectoryProps: - case UpdateTarget.DirectoryTargets: - case UpdateTarget.PackageReference: - updates = await UpdateProjects(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); + case var t when t.Matches(UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets, UpdateTarget.Csproj): + updates = await UpdateProjects(ct, package.PackageId, latest, documents.GetItems(files.Value), _parameters.IsDowngradeAllowed); break; default: break; } - - _log.Write(updates); } + + _log.Write(updates); + + _log.Write(""); } _log.WriteSummary(_parameters); @@ -86,17 +87,43 @@ internal async Task UpdatePackages(CancellationToken ct) return true; } - private async Task GetPackages(CancellationToken ct) + internal async Task GetPackages(CancellationToken ct) { - var packagesPerSource = await Task.WhenAll(_packageSources.Select(s => s.GetPackages(ct, _log))); + var packages = new List(); + var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget); - return packagesPerSource - .SelectMany(x => x) - .GroupBy(p => p.PackageId) - .Select(g => new NuGetPackage(g.Key, g.ToArray())) + _log.Write($"Found {references.Length} references"); + + foreach(var reference in references.OrderBy(r => r.Id)) + { + foreach(var source in _packageSources) + { + packages.Add(await source.GetPackage(ct, reference, _log)); + } + } + + return packages + .Where(p => p.AvailableVersions?.Any() ?? false) .ToArray(); } + private async Task> OpenFiles(CancellationToken ct, UpdaterPackage[] packages) + { + var files = packages + .SelectMany(p => p.Reference.Files) + .SelectMany(g => g.Value) + .Distinct(); + + var documents = new Dictionary(); + + foreach(var file in files) + { + documents.Add(file, await file.GetDocument(ct)); + } + + return documents; + } + private static async Task UpdateNuSpecs( CancellationToken ct, string packageId, From eeff248d8b15218a6bb2ff8dd7bf48cb46d9309f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 2 Aug 2019 13:30:15 -0400 Subject: [PATCH 104/201] Added more logging and fixed remaining issues --- CHANGELOG.md | 2 + .../Entities/TestPackage.cs | 61 ------------------- .../Entities/TestPackageSource.cs | 19 ++++-- src/NuGet.Updater.Tests/NuGetUpdaterTests.cs | 39 +++--------- .../SolutionHelperTests.cs | 10 --- .../UpdaterPackagePackageTests.cs | 8 +-- src/NuGet.Updater.Tool/Program.cs | 6 +- src/NuGet.Updater/Entities/IUpdaterSource.cs | 2 - .../Entities/PackageReference.cs | 11 +--- src/NuGet.Updater/Entities/UpdaterPackage.cs | 27 +++----- .../Entities/UpdaterParameters.cs | 4 +- src/NuGet.Updater/Entities/UpdaterSource.cs | 22 +++---- src/NuGet.Updater/Entities/UpdaterVersion.cs | 15 +++-- .../PackageSearchMetadataExtensions.cs | 24 ++++++++ .../Extensions/PackageSourceExtensions.cs | 61 +++++++++---------- .../Extensions/UpdaterPackageExtensions.cs | 2 - .../Extensions/UpdaterParametersExtension.cs | 6 +- src/NuGet.Updater/Helpers/FileHelper.Net.cs | 14 ----- src/NuGet.Updater/Helpers/FileHelper.UAP.cs | 27 -------- src/NuGet.Updater/Helpers/SolutionHelper.cs | 47 ++++++-------- src/NuGet.Updater/NuGetUpdater.Execution.cs | 32 ---------- src/NuGet.Updater/NuGetUpdater.cs | 39 +++++++++--- 22 files changed, 171 insertions(+), 307 deletions(-) delete mode 100644 src/NuGet.Updater.Tests/Entities/TestPackage.cs create mode 100644 src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs delete mode 100644 src/NuGet.Updater/NuGetUpdater.Execution.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1753839..805899f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added support for multiple target versions ### Changed +- Replaced package owner with package author +- Reworked internal logic of the updater: package list is now fetched from the files before being looked up online. ### Deprecated diff --git a/src/NuGet.Updater.Tests/Entities/TestPackage.cs b/src/NuGet.Updater.Tests/Entities/TestPackage.cs deleted file mode 100644 index 7069d64..0000000 --- a/src/NuGet.Updater.Tests/Entities/TestPackage.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using NuGet.Packaging; -using NuGet.Packaging.Core; -using NuGet.Protocol.Core.Types; - -namespace NuGet.Updater.Tests.Entities -{ - public class TestPackage : IPackageSearchMetadata - { - private string[] _versions; - - public TestPackage(string id, params string[] versions) - { - Identity = new PackageIdentity(id, null); - _versions = versions; - } - - public string Authors { get; set; } - - public IEnumerable DependencySets { get; set; } - - public string Description { get; set; } - - public long? DownloadCount { get; set; } - - public Uri IconUrl { get; set; } - - public PackageIdentity Identity { get; set; } - - public Uri LicenseUrl { get; set; } - - public Uri ProjectUrl { get; set; } - - public Uri ReportAbuseUrl { get; set; } - - public Uri PackageDetailsUrl { get; set; } - - public DateTimeOffset? Published { get; set; } - - public string Owners { get; set; } - - public bool RequireLicenseAcceptance { get; set; } - - public string Summary { get; set; } - - public string Tags { get; set; } - - public string Title { get; set; } - - public bool IsListed { get; set; } - - public bool PrefixReserved { get; set; } - - public LicenseMetadata LicenseMetadata { get; set; } - - public async Task> GetVersionsAsync() => _versions.Select(v => new VersionInfo(new Versioning.NuGetVersion(v))); - } -} diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs index 6f44662..7eb12a6 100644 --- a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs +++ b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -9,18 +10,26 @@ namespace NuGet.Updater.Tests.Entities { public class TestPackageSource : IUpdaterSource { - public TestPackageSource(Uri uri, params TestPackage[] packages) + private readonly Dictionary _packages; + + public TestPackageSource(Uri uri, Dictionary packages) { Uri = uri; - //Packages = packages?.Select(p => new NuGetPackage(p, Uri)).ToArray(); + _packages = packages; } public Uri Uri { get; } - public UpdaterPackage[] Packages { get; } + public async Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null) + { + var packageId = reference.Id; - public async Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null) => Packages.FirstOrDefault(p => p.PackageId == reference.Id); + var versions = _packages + .GetValueOrDefault(reference.Id) + ?.Select(v => new UpdaterVersion(v, Uri)) + .ToArray() ?? new UpdaterVersion[0]; - public async Task GetPackages(CancellationToken ct, Logger log = null) => Packages; + return new UpdaterPackage(reference, versions); + } } } diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs index 78527fc..9f3c8c8 100644 --- a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs +++ b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,12 +15,14 @@ namespace NuGet.Updater.Tests [TestClass] public class NuGetUpdaterTests { - private static readonly TestPackageSource TestSource = new TestPackageSource( - new Uri("http://localhost"), - new TestPackage("Uno.UI", "1.0", "1.1-dev.1"), - new TestPackage("Uno.Core", "1.0", "1.0-beta.1"), - new TestPackage("nventive.NuGet.Updater", "1.0-beta.1") - ); + private static readonly Dictionary TestPackages = new Dictionary + { + {"Uno.UI", new[] { "1.0", "1.1-dev.1" } }, + {"Uno.Core", new[] { "1.0", "1.0-beta.1" } }, + {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, + }; + + private static readonly TestPackageSource TestSource = new TestPackageSource(new Uri("http://localhost"), TestPackages); [TestMethod] public async Task GivenUnspecifiedTarget_NoUpdateIsMade() @@ -39,29 +42,5 @@ public async Task GivenUnspecifiedTarget_NoUpdateIsMade() Assert.IsTrue(logger.GetUpdates().None()); } - - [TestMethod] - public async Task GivenParameters_PackagesAreFound() - { - var parameters = new UpdaterParameters - { - SolutionRoot = "MySolution.sln", - UpdateTarget = UpdateTarget.DirectoryProps | UpdateTarget.DirectoryTargets, - IncludeNuGetOrg = true, - TargetVersions = new[] { "stable" }, - }; - - var logger = new Logger(Console.Out); - - var updater = new NuGetUpdater(parameters, parameters.GetSources(), logger); - - var packages = await updater.GetPackages(CancellationToken.None); - - //foreach(var p in packages.Where(p => p.Versions.Any())) - //{ - //} - - Assert.IsTrue(packages.Any()); - } } } diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs index 63fcdbc..f732a2d 100644 --- a/src/NuGet.Updater.Tests/SolutionHelperTests.cs +++ b/src/NuGet.Updater.Tests/SolutionHelperTests.cs @@ -10,16 +10,6 @@ namespace NuGet.Updater.Tests [TestClass] public class SolutionHelperTests { - [TestMethod] - public async Task GivenSolution_TargetFilesAreFound() - { - var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; - - var files = await SolutionHelper.GetTargetFilePaths(CancellationToken.None, solution, UpdateTarget.All); - - Assert.IsTrue(files.Any()); - } - [TestMethod] public async Task GivenSolution_PackageReferencesAreFound() { diff --git a/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs b/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs index 79d3f6e..24a3d21 100644 --- a/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs +++ b/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs @@ -1,16 +1,16 @@ using System; -using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; -using NuGet.Updater.Tests.Entities; namespace NuGet.Updater.Tests { [TestClass] public class UpdaterPackagePackageTests { + private static readonly Uri TestFeedUri = new Uri("http://localhost"); + [TestMethod] public async Task GivenPackageWithMatchingVersion_VersionIsFound() { @@ -20,7 +20,7 @@ public async Task GivenPackageWithMatchingVersion_VersionIsFound() }; var packageVersion = "1.0-beta.1"; - var package = new UpdaterPackage(new TestPackage("nventive.NuGet.Updater", packageVersion), new Uri("http://localhost")); + var package = new UpdaterPackage("nventive.NuGet.Updater", TestFeedUri, packageVersion); var version = package.GetLatestVersion(parameters); @@ -36,7 +36,7 @@ public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() TargetVersions = new[] { "stable" }, }; - var package = new UpdaterPackage(new TestPackage("nventive.NuGet.Updater", "1.0-beta.1"), new Uri("http://localhost")); + var package = new UpdaterPackage("nventive.NuGet.Updater", TestFeedUri, "1.0-beta.1"); var version = package.GetLatestVersion(parameters); diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index b2f0044..24ef7e7 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -25,7 +25,7 @@ public static async Task Main(string[] args) { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => AddPrivateFeed(s) }, { "version=|versions=|v=", "The target versions to use", s => Set(p => p.TargetVersions = GetList(s))}, { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, - { "packagesOwner=|o=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => Set(p => p.PackagesOwner = s)}, + { "packageAuthor=|a=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => Set(p => p.PackageAuthor = s)}, { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = GetBoolean(s))}, { "ignore=|i=", "A comma-separated list of packages to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, @@ -49,7 +49,9 @@ public static async Task Main(string[] args) } else { - await NuGetUpdater.UpdateAsync(CancellationToken.None, _parameters, isSilent ? null : Console.Out, summaryFile); + var updater = new NuGetUpdater(_parameters, isSilent ? null : Console.Out, summaryFile); + + await updater.UpdatePackages(CancellationToken.None); } } diff --git a/src/NuGet.Updater/Entities/IUpdaterSource.cs b/src/NuGet.Updater/Entities/IUpdaterSource.cs index 6b8cb77..670744b 100644 --- a/src/NuGet.Updater/Entities/IUpdaterSource.cs +++ b/src/NuGet.Updater/Entities/IUpdaterSource.cs @@ -6,8 +6,6 @@ namespace NuGet.Updater.Entities { public interface IUpdaterSource { - Task GetPackages(CancellationToken ct, Logger log = null); - Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null); } } diff --git a/src/NuGet.Updater/Entities/PackageReference.cs b/src/NuGet.Updater/Entities/PackageReference.cs index dd79272..c96ad45 100644 --- a/src/NuGet.Updater/Entities/PackageReference.cs +++ b/src/NuGet.Updater/Entities/PackageReference.cs @@ -1,19 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace NuGet.Updater.Entities { public class PackageReference { public PackageReference(string id, string version, string file, UpdateTarget target) + : this(id, version, new Dictionary() { { target, new[] { file } } }) { - Id = id; - Version = version; - Files = new Dictionary - { - { target, new[] { file } }, - }; } public PackageReference(string id, string version, Dictionary files) diff --git a/src/NuGet.Updater/Entities/UpdaterPackage.cs b/src/NuGet.Updater/Entities/UpdaterPackage.cs index 4c0a766..6139839 100644 --- a/src/NuGet.Updater/Entities/UpdaterPackage.cs +++ b/src/NuGet.Updater/Entities/UpdaterPackage.cs @@ -1,40 +1,31 @@ using System; -using System.Collections.Generic; -using NuGet.Protocol.Core.Types; +using System.Linq; namespace NuGet.Updater.Entities { public class UpdaterPackage { - public UpdaterPackage(IPackageSearchMetadata version, Uri sourceUri) + public UpdaterPackage(PackageReference reference, params UpdaterVersion[] versions) + : this(reference.Id, versions) { - PackageId = version.Identity.Id; - //Versions = new[] { version }; - SourceUri = sourceUri; + Reference = reference; + } - Packages = new Dictionary - { - { sourceUri, version }, - }; + internal UpdaterPackage(string id, Uri sourceUri, params string[] versions) + : this(id, versions?.Select(v => new UpdaterVersion(v, sourceUri)).ToArray()) + { } - public UpdaterPackage(string id, UpdaterVersion[] versions, Uri sourceUri, PackageReference reference) + private UpdaterPackage(string id, params UpdaterVersion[] versions) { PackageId = id; AvailableVersions = versions; - SourceUri = sourceUri; - Reference = reference; } - public Uri SourceUri { get; set; } - public string PackageId { get; } public PackageReference Reference { get; set; } public UpdaterVersion[] AvailableVersions { get; } - - //TO Remove - public Dictionary Packages { get; } } } diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index e950ecc..2013c63 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -50,8 +50,8 @@ public class UpdaterParameters public IEnumerable PackagesToUpdate { get; set; } /// - /// Gets or sets the name of the owner of the packages to update; used with NuGet.org. + /// Gets or sets the name of the author of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. /// - public string PackagesOwner { get; set; } + public string PackageAuthor { get; set; } } } diff --git a/src/NuGet.Updater/Entities/UpdaterSource.cs b/src/NuGet.Updater/Entities/UpdaterSource.cs index c0d13a0..b926855 100644 --- a/src/NuGet.Updater/Entities/UpdaterSource.cs +++ b/src/NuGet.Updater/Entities/UpdaterSource.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using NuGet.Configuration; using NuGet.Updater.Extensions; @@ -12,23 +9,26 @@ namespace NuGet.Updater.Entities public class UpdaterSource : IUpdaterSource { private readonly PackageSource _packageSource; - private readonly string _packageOwner; + private readonly string _packageAuthor; public UpdaterSource(string url, string packageOwner) { - _packageOwner = packageOwner; + _packageAuthor = packageOwner; _packageSource = new PackageSource(url); } - public UpdaterSource(string url, string accessToken, string packageOwner) + public UpdaterSource(string url, string accessToken, string packageAuthor) { - _packageOwner = packageOwner; + //Not filtering packages from private feeds + _packageAuthor = null; _packageSource = GetPackageSource(url, accessToken); } - public Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null) => _packageSource.GetPackage(ct, reference, log); - - public Task GetPackages(CancellationToken ct, Logger log = null) => _packageSource.SearchPackages(ct, log: log); + public Task GetPackage( + CancellationToken ct, + PackageReference reference, + Logger log = null + ) => _packageSource.GetPackage(ct, reference, _packageAuthor, log); private static PackageSource GetPackageSource(string url, string accessToken) { diff --git a/src/NuGet.Updater/Entities/UpdaterVersion.cs b/src/NuGet.Updater/Entities/UpdaterVersion.cs index d149beb..a4e4b08 100644 --- a/src/NuGet.Updater/Entities/UpdaterVersion.cs +++ b/src/NuGet.Updater/Entities/UpdaterVersion.cs @@ -4,17 +4,22 @@ namespace NuGet.Updater.Entities { public class UpdaterVersion : IComparable - { - public UpdaterVersion(Uri feedUri, NuGetVersion version) + { + internal UpdaterVersion(string version, Uri feedUri) + : this(new NuGetVersion(version), feedUri) { - FeedUri = feedUri; - Version = version; } - public Uri FeedUri { get; } + public UpdaterVersion(NuGetVersion version, Uri feedUri) + { + Version = version; + FeedUri = feedUri; + } public NuGetVersion Version { get; } + public Uri FeedUri { get; } + public int CompareTo(UpdaterVersion other) => Version.CompareTo(other.Version); } } diff --git a/src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs b/src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs new file mode 100644 index 0000000..544b7d7 --- /dev/null +++ b/src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; +using NuGet.Protocol.Core.Types; +using Uno.Extensions; + +namespace NuGet.Updater.Extensions +{ + public static class PackageSearchMetadataExtensions + { + public static bool HasAuthor(this IPackageSearchMetadata metadata, string author) + { + var authors = metadata?.Authors; + + if(authors.IsNullOrEmpty()) + { + return false; + } + + return authors.Contains(",") + ? authors.Split(',').Any(a => a.Equals(author, StringComparison.OrdinalIgnoreCase)) + : authors.Equals(author, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs index bf96ce8..30cd488 100644 --- a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs @@ -1,62 +1,57 @@ -using System; -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using NuGet.Updater.Entities; using NuGet.Common; using NuGet.Configuration; using NuGet.Protocol; using NuGet.Protocol.Core.Types; +using NuGet.Updater.Entities; using NuGet.Updater.Log; +using Uno.Extensions; namespace NuGet.Updater.Extensions { public static class PackageSourceExtensions { - public static async Task SearchPackages(this PackageSource source, CancellationToken ct, string searchTerm = "", Logger log = null) + public static async Task GetPackage( + this PackageSource source, + CancellationToken ct, + PackageReference reference, + string author = null, + Logger log = null + ) { - var settings = Settings.LoadDefaultSettings(null); - var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + var repositoryProvider = new SourceRepositoryProvider( + Settings.LoadDefaultSettings(null), + Repository.Provider.GetCoreV3() + ); var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); - log?.Write($"Pulling packages from {source.SourceUri}"); - - var searchResource = repository.GetResource(); - - var packages = (await searchResource.SearchAsync(searchTerm, new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion), skip: 0, take: 1000, log: new NullLogger(), cancellationToken: ct)).ToArray(); - - log?.Write($"Found {packages.Length} packages"); - - return source.ToNuGetPackages(packages); - } - - public static async Task GetPackage(this PackageSource source, CancellationToken ct, PackageReference reference, Logger log = null) - { - var settings = Settings.LoadDefaultSettings(null); - var repositoryProvider = new SourceRepositoryProvider(settings, Repository.Provider.GetCoreV3()); + var packageId = reference.Id; - var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); + log?.Write($"Retrieving package {packageId} from {source.SourceUri}"); - var packageId = reference.Id; + var metadata = (await repository + .GetResource() + .GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, new NullLogger(), ct)) + .ToArray(); - log?.Write($"Getting package {packageId} from {source.SourceUri}"); + log?.Write(metadata.Length > 0 ? $"Found {metadata.Length} versions" : "No versions found"); - var resource = repository.GetResource(); + if(author.HasValue() && metadata.Any()) + { + metadata = metadata.Where(m => m.HasAuthor(author)).ToArray(); - var metadata = await resource.GetMetadataAsync(packageId, true, false, new SourceCacheContext(), new NullLogger(), ct); + log?.Write(metadata.Length > 0 ? $"Found {metadata.Length} version from {author}" : $"No versions from {author} found"); + } var versions = metadata .Cast() - .Select(m => new UpdaterVersion(source.SourceUri, m.Version)) + .Select(m => new UpdaterVersion(m.Version, source.SourceUri)) .ToArray(); - return new UpdaterPackage(packageId, versions, source.SourceUri, reference); + return new UpdaterPackage(reference, versions); } - - private static UpdaterPackage[] ToNuGetPackages(this PackageSource source, IPackageSearchMetadata[] packages) => - packages - .Select(p => new UpdaterPackage(p, source.SourceUri)) - .ToArray(); } } diff --git a/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs b/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs index 9ec37ff..4caf2af 100644 --- a/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs +++ b/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs @@ -1,6 +1,4 @@ using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NuGet.Updater.Entities; using Uno.Extensions; diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 9dd3421..c96852d 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -15,12 +15,12 @@ internal static IUpdaterSource[] GetSources(this UpdaterParameters parameters) if(parameters.PrivateFeeds?.Any() ?? false) { - packageSources.AddRange(parameters.PrivateFeeds.Select(g => new UpdaterSource(g.Key, g.Value, parameters.PackagesOwner))); + packageSources.AddRange(parameters.PrivateFeeds.Select(g => new UpdaterSource(g.Key, g.Value, parameters.PackageAuthor))); } if(parameters.IncludeNuGetOrg) { - packageSources.Add(new UpdaterSource("https://api.nuget.org/v3/index.json", parameters.PackagesOwner)); + packageSources.Add(new UpdaterSource("https://api.nuget.org/v3/index.json", parameters.PackageAuthor)); } return packageSources.ToArray(); @@ -53,7 +53,7 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters if(parameters.IncludeNuGetOrg) { - packageSources.Add(parameters.PackagesOwner.SelectOrDefault(o => $"NuGet.org (limited to packages owned by {o})", "NuGet.org")); + packageSources.Add(parameters.PackageAuthor.SelectOrDefault(o => $"NuGet.org (limited to packages by {o})", "NuGet.org")); } if(parameters.PrivateFeeds?.Any() ?? false) diff --git a/src/NuGet.Updater/Helpers/FileHelper.Net.cs b/src/NuGet.Updater/Helpers/FileHelper.Net.cs index c44ad02..84daae4 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.Net.cs @@ -26,20 +26,6 @@ public static void LogToFile(string outputFilePath, IEnumerable log) } } - public static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) - { - var filter = extensionFilter != null - ? "*" + extensionFilter - : null; - - if (nameFilter != null && filter == null) - { - filter = nameFilter; - } - - return Directory.GetFiles(path, filter, SearchOption.AllDirectories); - } - public static async Task ReadFileContent(CancellationToken ct, string path) { return File.ReadAllText(path); diff --git a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs index 6ad574e..d92b16c 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs @@ -16,33 +16,6 @@ public static void LogToFile(string outputFilePath, IEnumerable log) { } - public static async Task GetFiles( - CancellationToken ct, - string path, - string extensionFilter = null, - string nameFilter = null - ) - { - var folder = await StorageFolder.GetFolderFromPathAsync(path); - - var searchFilter = extensionFilter == null - ? $"filename:\"{nameFilter}\"" - : $"extension:{extensionFilter}"; - - var queryOptions = new QueryOptions - { - UserSearchFilter = searchFilter, - IndexerOption = IndexerOption.UseIndexerWhenAvailable, - FolderDepth = FolderDepth.Deep, - }; - - var query = folder.CreateFileQueryWithOptions(queryOptions); - - var files = await query.GetFilesAsync(); - - return files.Select(f => f.Path).ToArray(); - } - public static async Task ReadFileContent(CancellationToken ct, string path) { var file = await StorageFile.GetFileFromPathAsync(path); diff --git a/src/NuGet.Updater/Helpers/SolutionHelper.cs b/src/NuGet.Updater/Helpers/SolutionHelper.cs index 56afbcd..b3f3969 100644 --- a/src/NuGet.Updater/Helpers/SolutionHelper.cs +++ b/src/NuGet.Updater/Helpers/SolutionHelper.cs @@ -6,39 +6,27 @@ using System.Threading.Tasks; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; +using NuGet.Updater.Log; using Uno.Extensions; namespace NuGet.Updater.Helpers { public static class SolutionHelper { - internal static async Task GetTargetFilePaths(CancellationToken ct, string solutionPath, UpdateTarget updateTarget) + internal static async Task GetPackageReferences( + CancellationToken ct, + string solutionPath, + UpdateTarget updateTarget, + Logger log = null + ) { - switch(updateTarget) - { - case UpdateTarget.Csproj: - return await GetProjectFiles(ct, solutionPath); - case UpdateTarget.DirectoryProps: - case UpdateTarget.DirectoryTargets: - return new[] { GetDirectoryFile(solutionPath, updateTarget) }; - case UpdateTarget.Nuspec: - return new string[0]; // find all matching files in the solution folder - case UpdateTarget.ProjectJson: - return new string[0]; //find all csproj, look for project.json next to them - case UpdateTarget.Unspecified: - case UpdateTarget.All: - default: - return new string[0]; - } - } + log?.Write($"Retrieving references from files in {solutionPath}"); - internal static async Task GetPackageReferences(CancellationToken ct, string solutionPath, UpdateTarget updateTarget) - { var packages = new List(); if(updateTarget.Matches(UpdateTarget.Csproj)) { - foreach(var f in await GetProjectFiles(ct, solutionPath)) + foreach(var f in await GetProjectFiles(ct, solutionPath, log)) { packages.AddRange(await GetFileReferences(ct, f, UpdateTarget.Csproj)); } @@ -46,44 +34,49 @@ internal static async Task GetPackageReferences(Cancellation if(updateTarget.Matches(UpdateTarget.DirectoryProps)) { - packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryProps), UpdateTarget.DirectoryProps)); + packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryProps, log), UpdateTarget.DirectoryProps)); } if(updateTarget.Matches(UpdateTarget.DirectoryTargets)) { - packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryTargets), UpdateTarget.DirectoryTargets)); + packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryTargets, log), UpdateTarget.DirectoryTargets)); } return packages .GroupBy(p => p.Id) .Select(g => new PackageReference( g.Key, - g.Select(p => p.Version).OrderBy(v => v).FirstOrDefault(), + g.Select(p => p.Version).OrderByDescending(v => v).FirstOrDefault(), g.SelectMany(p => p.Files).GroupBy(f => f.Key).ToDictionary(f => f.Key, f => f.SelectMany(x => x.Value).Distinct().ToArray()) )) .ToArray(); } - private static async Task GetProjectFiles(CancellationToken ct, string solutionPath) + private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, Logger log = null) { var solutionContent = await FileHelper.ReadFileContent(ct, solutionPath); var solutionFolder = Path.GetDirectoryName(solutionPath); var matches = Regex.Matches(solutionContent, "[^\\s\"]*\\.csproj"); - return matches + var files = matches .Cast() .Select(m => Path.Combine(solutionFolder, m.Value)) .ToArray(); + + log?.Write($"Found {files.Length} csproj files"); + + return files; } - private static string GetDirectoryFile(string solutionPath, UpdateTarget target) + private static string GetDirectoryFile(string solutionPath, UpdateTarget target, Logger log = null) { var solutionFolder = Path.GetDirectoryName(solutionPath); var file = Path.Combine(solutionFolder, target.GetDescription()); if(File.Exists(file)) { + log?.Write($"Found {target.GetDescription()}"); return file; } diff --git a/src/NuGet.Updater/NuGetUpdater.Execution.cs b/src/NuGet.Updater/NuGetUpdater.Execution.cs deleted file mode 100644 index 1a3ec71..0000000 --- a/src/NuGet.Updater/NuGetUpdater.Execution.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; - -namespace NuGet.Updater -{ - /// - /// Static execution methods for the NuGetUpdater. - /// - public partial class NuGetUpdater - { - public static Task UpdateAsync( - CancellationToken ct, - UpdaterParameters parameters, - TextWriter logWriter = null, - string summaryOutputFilePath = null - ) => UpdateAsync(ct, parameters, new Logger(logWriter, summaryOutputFilePath)); - - public static async Task UpdateAsync( - CancellationToken ct, - UpdaterParameters parameters, - Logger log - ) - { - var updater = new NuGetUpdater(parameters, parameters.GetSources(), log); - return await updater.UpdatePackages(ct); - } - } -} diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index aa03b78..e5b7ae2 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -7,9 +7,8 @@ using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; using NuGet.Versioning; -using Uno.Extensions; using NuGet.Updater.Log; -using System; +using System.IO; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; @@ -21,15 +20,22 @@ namespace NuGet.Updater { - /// - /// NuGet Updater implementation. - /// - public partial class NuGetUpdater + public class NuGetUpdater { private readonly UpdaterParameters _parameters; private readonly Logger _log; private readonly IUpdaterSource[] _packageSources; + public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, string summaryOutputFilePath) + : this(parameters, parameters.GetSources(), new Logger(logWriter, summaryOutputFilePath)) + { + } + + public NuGetUpdater(UpdaterParameters parameters, Logger log) + : this(parameters, parameters.GetSources(), log) + { + } + internal NuGetUpdater(UpdaterParameters parameters, IUpdaterSource[] packageSources, Logger log) { //TODO validate parameters @@ -38,7 +44,7 @@ internal NuGetUpdater(UpdaterParameters parameters, IUpdaterSource[] packageSour _packageSources = packageSources; } - internal async Task UpdatePackages(CancellationToken ct) + public async Task UpdatePackages(CancellationToken ct) { _log.Clear(); @@ -55,7 +61,7 @@ internal async Task UpdatePackages(CancellationToken ct) continue; } - _log.Write($"Latest matching version for [{package.PackageId}] is [{latest.Version}] on {package.SourceUri}"); + _log.Write($"Latest matching version for [{package.PackageId}] is [{latest.Version}] on {latest.FeedUri}"); var updates = new UpdateOperation[0]; @@ -90,16 +96,29 @@ internal async Task UpdatePackages(CancellationToken ct) internal async Task GetPackages(CancellationToken ct) { var packages = new List(); - var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget); + var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget, _log); _log.Write($"Found {references.Length} references"); + _log.Write(""); + _log.Write($"Retrieving packages from {_packageSources.Length} sources"); foreach(var reference in references.OrderBy(r => r.Id)) { + var matchingPackages = new List(); foreach(var source in _packageSources) { - packages.Add(await source.GetPackage(ct, reference, _log)); + matchingPackages.Add(await source.GetPackage(ct, reference, _log)); } + + _log.Write(""); + + //If the reference has been found on multiple sources, we merge the packages found together + var package = matchingPackages + .GroupBy(p => p.Reference) + .Select(g => new UpdaterPackage(g.Key, g.SelectMany(p => p.AvailableVersions).ToArray())) + .SingleOrDefault(); + + packages.Add(package); } return packages From 575c79944f08ec2bcb33035c1b58faf833978fd6 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 2 Aug 2019 16:21:00 -0400 Subject: [PATCH 105/201] Simplified the downgrade parameter in the tool --- src/NuGet.Updater.Tool/Program.cs | 12 +----------- .../Properties/launchSettings.json | 3 ++- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 24ef7e7..0cb92d8 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -26,7 +26,7 @@ public static async Task Main(string[] args) { "version=|versions=|v=", "The target versions to use", s => Set(p => p.TargetVersions = GetList(s))}, { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, { "packageAuthor=|a=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => Set(p => p.PackageAuthor = s)}, - { "allowDowngrade=|d=", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = GetBoolean(s))}, + { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, { "ignore=|i=", "A comma-separated list of packages to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, { "outputFile=|of=", "The path to a file where the update summary will be written", s => summaryFile = s }, @@ -77,16 +77,6 @@ private static void AddPrivateFeed(string value) _isParameterSet = true; } - private static bool GetBoolean(string value, bool fallbackValue = false) - { - if(bool.TryParse(value, out var boolean)) - { - return boolean; - } - - return fallbackValue; - } - private static List GetList(string value) { var list = new List(); diff --git a/src/NuGet.Updater.Tool/Properties/launchSettings.json b/src/NuGet.Updater.Tool/Properties/launchSettings.json index bc41851..9e856df 100644 --- a/src/NuGet.Updater.Tool/Properties/launchSettings.json +++ b/src/NuGet.Updater.Tool/Properties/launchSettings.json @@ -1,7 +1,8 @@ { "profiles": { "NuGet.Updater.Tool": { - "commandName": "Project" + "commandName": "Project", + "commandLineArgs": "--solution=\"C:\\Git\\MyMD\\MyMD\\MyMD.sln\" --feed=https://nventive.pkgs.visualstudio.com/_packaging/nventive/nuget/v3/index.json|3cganbkmlloatqlwwkmcbl2j3mlrcsxwc5vgi5uuqttjkcpzxyeq -n --versions=dev,stable --packagesOwner=nventive" } } } \ No newline at end of file From 6a8502f820137f1d014dcccac707f10633713c1f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 2 Aug 2019 16:54:30 -0400 Subject: [PATCH 106/201] Improved tool arguments --- src/NuGet.Updater.Tool/Program.cs | 18 +++++++++--------- .../Properties/launchSettings.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 0cb92d8..ccad274 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -21,16 +21,16 @@ public static async Task Main(string[] args) var options = new OptionSet { { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The path to the solution to update", s => Set(p => p.SolutionRoot = s) }, - { "feed=|f=", "A private feed to use for the update; the format is {url}|{accessToken}; can be specified multiple times", s => AddPrivateFeed(s) }, - { "version=|versions=|v=", "The target versions to use", s => Set(p => p.TargetVersions = GetList(s))}, - { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, - { "packageAuthor=|a=", "The owner of the packages to update; must be specified if useNuGetorg is true", s => Set(p => p.PackageAuthor = s)}, - { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, - { "ignore=|i=", "A comma-separated list of packages to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, - { "update=|u=", "A comma-separated list of packages to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, - { "outputFile=|of=", "The path to a file where the update summary will be written", s => summaryFile = s }, + { "solution=|s=", "The {path} to the solution to update", s => Set(p => p.SolutionRoot = s) }, + { "feed=|f=", "A private feed to use for the update; the format is {url|accessToken}; can be specified multiple times", s => AddPrivateFeed(s) }, + { "version=|versions=|v=", "The target {versions} to use", s => Set(p => p.TargetVersions = GetList(s))}, { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, + { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, + { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, + { "packageAuthor=|a=", "The {author} of the packages to update; used for NuGet.org", s => Set(p => p.PackageAuthor = s)}, + { "ignore=|i=", "A comma-separated list of {packages} to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, + { "update=|u=", "A comma-separated list of {packages} to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, + { "outputFile=|of=", "The {path} to a file where the update summary will be written", s => summaryFile = s }, }; _isParameterSet = false; diff --git a/src/NuGet.Updater.Tool/Properties/launchSettings.json b/src/NuGet.Updater.Tool/Properties/launchSettings.json index 9e856df..640bec2 100644 --- a/src/NuGet.Updater.Tool/Properties/launchSettings.json +++ b/src/NuGet.Updater.Tool/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "NuGet.Updater.Tool": { "commandName": "Project", - "commandLineArgs": "--solution=\"C:\\Git\\MyMD\\MyMD\\MyMD.sln\" --feed=https://nventive.pkgs.visualstudio.com/_packaging/nventive/nuget/v3/index.json|3cganbkmlloatqlwwkmcbl2j3mlrcsxwc5vgi5uuqttjkcpzxyeq -n --versions=dev,stable --packagesOwner=nventive" + "commandLineArgs": "" } } -} \ No newline at end of file +} From c9881aae4198cf941ee7777d49d5db967aa2c405 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 2 Aug 2019 16:55:15 -0400 Subject: [PATCH 107/201] Removed launch settings from repo --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6f62953..344bf05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +launchSettings.json + # Remove everything beginning with a dot .*/ !.github/ From fa3ad82145eee4fb920d963d9bbb9bc8b66532ba Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 5 Aug 2019 12:01:53 -0400 Subject: [PATCH 108/201] Update --- CHANGELOG.md | 4 +- src/NuGet.Updater/Entities/UpdateTarget.cs | 18 ++-- .../Extensions/UpdateTargetExtensions.cs | 6 +- src/NuGet.Updater/Helpers/FileHelper.Net.cs | 19 ++++ src/NuGet.Updater/Helpers/FileHelper.UAP.cs | 47 +++++++++- src/NuGet.Updater/Helpers/SolutionHelper.cs | 91 +++++++++++++++---- src/NuGet.Updater/NuGetUpdater.cs | 65 +++---------- 7 files changed, 164 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 805899f..a1f00b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added aliases for .Net Core application - Added support for multiple target versions +- Added support for solution folders alongside sln files ### Changed - Replaced package owner with package author -- Reworked internal logic of the updater: package list is now fetched from the files before being looked up online. +- Reworked internal logic of the updater: package list is now fetched from the files before being looked up online ### Deprecated ### Removed - Removed superfluous arguments +- Removed code used to update project.json files ### Fixed diff --git a/src/NuGet.Updater/Entities/UpdateTarget.cs b/src/NuGet.Updater/Entities/UpdateTarget.cs index 4a7f759..b0245f8 100644 --- a/src/NuGet.Updater/Entities/UpdateTarget.cs +++ b/src/NuGet.Updater/Entities/UpdateTarget.cs @@ -1,8 +1,11 @@ -namespace NuGet.Updater.Entities +using System; + +namespace NuGet.Updater.Entities { /// /// The type of files to update. /// + [Flags] public enum UpdateTarget { /// @@ -15,29 +18,24 @@ public enum UpdateTarget /// Nuspec = 2, - /// - /// project.json files. - /// - ProjectJson = 4, - /// /// PackageReferences from csproj. /// - Csproj = 8, + Csproj = 4, /// /// Directory.Build.props files. /// - DirectoryProps = 16, + DirectoryProps = 8, /// /// Directory.Build.targets files. /// - DirectoryTargets = 32, + DirectoryTargets = 16, /// /// All the supported file types. /// - All = Nuspec | ProjectJson | Csproj | DirectoryProps | DirectoryTargets, + All = Nuspec | Csproj | DirectoryProps | DirectoryTargets, } } diff --git a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs index fcbe215..01948b6 100644 --- a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs +++ b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs @@ -11,8 +11,6 @@ public static string GetDescription(this UpdateTarget target) { case UpdateTarget.Nuspec: return ".nuspec"; - case UpdateTarget.ProjectJson: - return "project.json"; case UpdateTarget.Csproj: return ".csproj"; case UpdateTarget.DirectoryProps: @@ -24,9 +22,9 @@ public static string GetDescription(this UpdateTarget target) } } - public static bool Matches(this UpdateTarget target, params UpdateTarget[] others) + public static bool HasAnyFlag(this UpdateTarget target, params UpdateTarget[] others) { - return others.Any(t => (target & t) == t); + return others.Any(t => t.HasFlag(target)); } } } diff --git a/src/NuGet.Updater/Helpers/FileHelper.Net.cs b/src/NuGet.Updater/Helpers/FileHelper.Net.cs index 84daae4..fe075ec 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.Net.cs @@ -26,6 +26,20 @@ public static void LogToFile(string outputFilePath, IEnumerable log) } } + public static async Task GetFiles(CancellationToken ct, string path, string extensionFilter = null, string nameFilter = null) + { + var filter = extensionFilter != null + ? "*" + extensionFilter + : null; + + if (nameFilter != null && filter == null) + { + filter = nameFilter; + } + + return Directory.GetFiles(path, filter, SearchOption.AllDirectories); + } + public static async Task ReadFileContent(CancellationToken ct, string path) { return File.ReadAllText(path); @@ -35,6 +49,11 @@ public static async Task SetFileContent(CancellationToken ct, string path, strin { File.WriteAllText(path, content, Encoding.UTF8); } + + public static async Task IsDirectory(CancellationToken ct, string path) + { + return (File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory; + } } } #endif diff --git a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs index d92b16c..cfcaba3 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Updater/Helpers/FileHelper.UAP.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Windows.Storage; @@ -10,12 +9,42 @@ namespace NuGet.Updater.Helpers { + /// + /// UAP-Specific methods. + /// partial class FileHelper { public static void LogToFile(string outputFilePath, IEnumerable log) { } + public static async Task GetFiles( + CancellationToken ct, + string path, + string extensionFilter = null, + string nameFilter = null + ) + { + var folder = await StorageFolder.GetFolderFromPathAsync(path); + + var searchFilter = extensionFilter == null + ? $"filename:\"{nameFilter}\"" + : $"extension:{extensionFilter}"; + + var queryOptions = new QueryOptions + { + UserSearchFilter = searchFilter, + IndexerOption = IndexerOption.UseIndexerWhenAvailable, + FolderDepth = FolderDepth.Deep, + }; + + var query = folder.CreateFileQueryWithOptions(queryOptions); + + var files = await query.GetFilesAsync(); + + return files.Select(f => f.Path).ToArray(); + } + public static async Task ReadFileContent(CancellationToken ct, string path) { var file = await StorageFile.GetFileFromPathAsync(path); @@ -27,9 +56,23 @@ public static async Task ReadFileContent(CancellationToken ct, string pa public static async Task SetFileContent(CancellationToken ct, string path, string content) { - var file = await StorageFile.GetFileFromPathAsync(path); + var file = await StorageFile.GetFileFromPathAsync(path).AsTask(ct); await FileIO.WriteTextAsync(file, content); } + + public static async Task IsDirectory(CancellationToken ct, string path) + { + try + { + var folder = await StorageFolder.GetFolderFromPathAsync(path).AsTask(ct); + + return true; + } + catch(ArgumentException) + { + return false; + } + } } } #endif diff --git a/src/NuGet.Updater/Helpers/SolutionHelper.cs b/src/NuGet.Updater/Helpers/SolutionHelper.cs index b3f3969..10e2119 100644 --- a/src/NuGet.Updater/Helpers/SolutionHelper.cs +++ b/src/NuGet.Updater/Helpers/SolutionHelper.cs @@ -11,7 +11,10 @@ namespace NuGet.Updater.Helpers { - public static class SolutionHelper + /// + /// Shared solution helper methods. + /// + public static partial class SolutionHelper { internal static async Task GetPackageReferences( CancellationToken ct, @@ -24,7 +27,7 @@ internal static async Task GetPackageReferences( var packages = new List(); - if(updateTarget.Matches(UpdateTarget.Csproj)) + if(updateTarget.HasFlag(UpdateTarget.Csproj)) { foreach(var f in await GetProjectFiles(ct, solutionPath, log)) { @@ -32,14 +35,25 @@ internal static async Task GetPackageReferences( } } - if(updateTarget.Matches(UpdateTarget.DirectoryProps)) + if(updateTarget.HasFlag(UpdateTarget.DirectoryProps)) { - packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryProps, log), UpdateTarget.DirectoryProps)); + const UpdateTarget currentTarget = UpdateTarget.DirectoryProps; + + var file = await GetDirectoryFile(ct, solutionPath, currentTarget, log); + packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + } + + if(updateTarget.HasFlag(UpdateTarget.DirectoryTargets)) + { + const UpdateTarget currentTarget = UpdateTarget.DirectoryTargets; + + var file = await GetDirectoryFile(ct, solutionPath, currentTarget, log); + packages.AddRange(await GetFileReferences(ct, file, currentTarget)); } - if(updateTarget.Matches(UpdateTarget.DirectoryTargets)) + if(updateTarget.HasFlag(UpdateTarget.Nuspec)) { - packages.AddRange(await GetFileReferences(ct, GetDirectoryFile(solutionPath, UpdateTarget.DirectoryTargets, log), UpdateTarget.DirectoryTargets)); + var files = await GetNuspecFiles(ct, solutionPath, log); } return packages @@ -54,27 +68,48 @@ internal static async Task GetPackageReferences( private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, Logger log = null) { - var solutionContent = await FileHelper.ReadFileContent(ct, solutionPath); - var solutionFolder = Path.GetDirectoryName(solutionPath); - - var matches = Regex.Matches(solutionContent, "[^\\s\"]*\\.csproj"); + var files = new string[0]; - var files = matches - .Cast() - .Select(m => Path.Combine(solutionFolder, m.Value)) - .ToArray(); + if(await FileHelper.IsDirectory(ct, solutionPath)) + { + files = await FileHelper.GetFiles(ct, solutionPath, extensionFilter: ".csproj"); + } + else + { + var solutionContent = await FileHelper.ReadFileContent(ct, solutionPath); + var solutionFolder = Path.GetDirectoryName(solutionPath); + + files = Regex + .Matches(solutionContent, "[^\\s\"]*\\.csproj") + .Cast() + .Select(m => Path.Combine(solutionFolder, m.Value)) + .ToArray(); + } log?.Write($"Found {files.Length} csproj files"); return files; } - private static string GetDirectoryFile(string solutionPath, UpdateTarget target, Logger log = null) + //To improve: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019#search-scope + //The file should be looked for at all levels + private static async Task GetDirectoryFile(CancellationToken ct, string solutionPath, UpdateTarget target, Logger log = null) { - var solutionFolder = Path.GetDirectoryName(solutionPath); - var file = Path.Combine(solutionFolder, target.GetDescription()); + string file; - if(File.Exists(file)) + if(await FileHelper.IsDirectory(ct, solutionPath)) + { + var matchingFiles = await FileHelper.GetFiles(ct, solutionPath, nameFilter: target.GetDescription()); + + file = matchingFiles.SingleOrDefault(); + } + else + { + var solutionFolder = Path.GetDirectoryName(solutionPath); + file = Path.Combine(solutionFolder, target.GetDescription()); + } + + if(file != null && File.Exists(file)) { log?.Write($"Found {target.GetDescription()}"); return file; @@ -83,6 +118,26 @@ private static string GetDirectoryFile(string solutionPath, UpdateTarget target, return null; } + private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, Logger log = null) + { + string solutionFolder; + + if(await FileHelper.IsDirectory(ct, solutionPath)) + { + solutionFolder = solutionPath; + } + else + { + solutionFolder = Path.GetDirectoryName(solutionPath); + } + + var files = await FileHelper.GetFiles(ct, solutionFolder, extensionFilter: ".nuspec"); + + log?.Write($"Found {files.Length} nuspec files"); + + return files; + } + private static async Task GetFileReferences(CancellationToken ct, string file, UpdateTarget target) { if(file.IsNullOrEmpty()) diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index e5b7ae2..67bbbf1 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; -using NuGet.Versioning; using NuGet.Updater.Log; using System.IO; @@ -26,6 +24,18 @@ public class NuGetUpdater private readonly Logger _log; private readonly IUpdaterSource[] _packageSources; + public static async Task UpdateAsync( + CancellationToken ct, + UpdaterParameters parameters, + TextWriter logWriter = null, + string summaryOutputFilePath = null + ) + { + var updater = new NuGetUpdater(parameters, logWriter, summaryOutputFilePath); + + return await updater.UpdatePackages(ct); + } + public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, string summaryOutputFilePath) : this(parameters, parameters.GetSources(), new Logger(logWriter, summaryOutputFilePath)) { @@ -69,13 +79,10 @@ public async Task UpdatePackages(CancellationToken ct) { switch(files.Key) { - case var t when t.Matches(UpdateTarget.Nuspec): + case var t when t.HasFlag(UpdateTarget.Nuspec): updates = await UpdateNuSpecs(ct, package.PackageId, latest, documents.GetItems(files.Value), _parameters.IsDowngradeAllowed); break; - case var t when t.Matches(UpdateTarget.ProjectJson): - updates = await UpdateProjectJson(ct, package.PackageId, latest, files.Value, _parameters.IsDowngradeAllowed); - break; - case var t when t.Matches(UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets, UpdateTarget.Csproj): + case var t when t.HasAnyFlag(UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets, UpdateTarget.Csproj): updates = await UpdateProjects(ct, package.PackageId, latest, documents.GetItems(files.Value), _parameters.IsDowngradeAllowed); break; default: @@ -198,49 +205,5 @@ bool isDowngradeAllowed return operations.ToArray(); } - - private static async Task UpdateProjectJson( - CancellationToken ct, - string packageName, - UpdaterVersion latestVersion, - string[] jsonFiles, - bool isDowngradeAllowed - ) - { - var operations = new List(); - - var originalMatch = $@"\""{packageName}\"".*?:.?\""(.*)\"""; - var replaced = $@"""{packageName}"": ""{latestVersion.Version.ToString()}"""; - - for(var i = 0; i < jsonFiles.Length; i++) - { - var file = jsonFiles[i]; - var fileContent = await FileHelper.ReadFileContent(ct, file); - - var match = Regex.Match(fileContent, originalMatch, RegexOptions.IgnoreCase); - if(match?.Success ?? false) - { - var currentVersion = new NuGetVersion(match.Groups[1].Value); - - var operation = new UpdateOperation(isDowngradeAllowed, packageName, currentVersion, latestVersion, file); - - if(operation.IsUpdate) - { - var newContent = Regex.Replace( - fileContent, - originalMatch, - replaced, - RegexOptions.IgnoreCase - ); - - await FileHelper.SetFileContent(ct, file, newContent); - } - - operations.Add(operation); - } - } - - return operations.ToArray(); - } } } From b5510ab6c61efe891d2b000dc91ebf8932c849fa Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 5 Aug 2019 16:04:29 -0400 Subject: [PATCH 109/201] Added back support for nuspec Improvements to code readability and performance --- src/NuGet.Updater/Entities/UpdaterPackage.cs | 32 ++- .../Extensions/DictionaryExtensions.cs | 14 ++ .../Extensions/XmlDocumentExtensions.Net.cs | 162 -------------- .../Extensions/XmlDocumentExtensions.UAP.cs | 134 ------------ .../Extensions/XmlDocumentExtensions.cs | 207 +++++++++++++++++- src/NuGet.Updater/Helpers/SolutionHelper.cs | 20 +- src/NuGet.Updater/NuGetUpdater.cs | 136 +++++------- 7 files changed, 314 insertions(+), 391 deletions(-) delete mode 100644 src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs delete mode 100644 src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs diff --git a/src/NuGet.Updater/Entities/UpdaterPackage.cs b/src/NuGet.Updater/Entities/UpdaterPackage.cs index 6139839..450334b 100644 --- a/src/NuGet.Updater/Entities/UpdaterPackage.cs +++ b/src/NuGet.Updater/Entities/UpdaterPackage.cs @@ -1,31 +1,47 @@ using System; +using System.Collections.Generic; using System.Linq; namespace NuGet.Updater.Entities { public class UpdaterPackage { - public UpdaterPackage(PackageReference reference, params UpdaterVersion[] versions) - : this(reference.Id, versions) + /// + /// Creates a new instance of UpdaterPackage from an id, an Url and a list of version. Used for testing. + /// + /// + /// + /// + internal UpdaterPackage(string id, Uri sourceUri, params string[] versions) { - Reference = reference; + PackageId = id; + AvailableVersions = versions?.Select(v => new UpdaterVersion(v, sourceUri)).ToArray(); } - internal UpdaterPackage(string id, Uri sourceUri, params string[] versions) - : this(id, versions?.Select(v => new UpdaterVersion(v, sourceUri)).ToArray()) + public UpdaterPackage(PackageReference reference, IEnumerable availableVersions) + : this(reference) { + AvailableVersions = availableVersions.ToArray(); } - private UpdaterPackage(string id, params UpdaterVersion[] versions) + public UpdaterPackage(PackageReference reference, UpdaterVersion latestVersion) + : this(reference) { - PackageId = id; - AvailableVersions = versions; + LatestVersion = latestVersion; + } + + private UpdaterPackage(PackageReference reference) + { + Reference = reference; + PackageId = reference.Id; } public string PackageId { get; } public PackageReference Reference { get; set; } + public UpdaterVersion LatestVersion { get; } + public UpdaterVersion[] AvailableVersions { get; } } } diff --git a/src/NuGet.Updater/Extensions/DictionaryExtensions.cs b/src/NuGet.Updater/Extensions/DictionaryExtensions.cs index 93a3eaa..5aff40a 100644 --- a/src/NuGet.Updater/Extensions/DictionaryExtensions.cs +++ b/src/NuGet.Updater/Extensions/DictionaryExtensions.cs @@ -12,5 +12,19 @@ params TKey[] keys ) => dictionary .Where(g => keys.Contains(g.Key)) .ToDictionary(g => g.Key, g => g.Value); + +#if !UAP + public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value) + { + if(dictionary.ContainsKey(key)) + { + return false; + } + + dictionary.Add(key, value); + + return true; + } +#endif } } diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs deleted file mode 100644 index da9a322..0000000 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.Net.cs +++ /dev/null @@ -1,162 +0,0 @@ -#if !UAP -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using NuGet.Updater.Entities; -using NuGet.Updater.Log; -using NuGet.Versioning; - -namespace NuGet.Updater.Extensions -{ - /// - /// .Net XmlDocument extension methods. - /// - public partial class XmlDocumentExtensions - { - private const string MsBuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - - public static Dictionary GetPackageReferences(this XmlDocument document) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace("d", MsBuildNamespace); - - var references = document.GetPackageReferences(namespaceManager); - - namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace("d", ""); - - var otherReferences = document.GetPackageReferences(namespaceManager); - - return references - .Concat(otherReferences) - .ToDictionary(g => g.Key, g => g.Value); - } - - private static Dictionary GetPackageReferences(this XmlDocument document, XmlNamespaceManager namespaceManager) - { - var references = new Dictionary(); - - var packageReferences = document.SelectNodes($"//d:PackageReference", namespaceManager).OfType(); - var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference", namespaceManager).OfType(); - - foreach(XmlElement packageReference in packageReferences.Concat(dotnetCliReferences)) - { - var packageId = packageReference.Attributes["Include"].Value; - - if(packageReference.HasAttribute("Version")) - { - references.Add(packageId, packageReference.Attributes["Version"].Value); - } - else - { - var node = packageReference.SelectSingleNode("d:Version", namespaceManager); - if(node != null) - { - references.Add(packageId, node.InnerText); - } - } - } - - return references; - } - - public static UpdateOperation[] UpdateProjectReferenceVersions( - this XmlDocument document, - string packageId, - UpdaterVersion version, - string path, - bool isDowngradeAllowed) - { - var operations = new List(); - - var nsmgr = new XmlNamespaceManager(document.NameTable); - nsmgr.AddNamespace("d", MsBuildNamespace); - operations.AddRange(document.UpdateProjectReferenceVersions(packageId, version, path, nsmgr, isDowngradeAllowed)); - - var nsmgr2 = new XmlNamespaceManager(document.NameTable); - nsmgr2.AddNamespace("d", ""); - operations.AddRange(document.UpdateProjectReferenceVersions(packageId, version, path, nsmgr2, isDowngradeAllowed)); - - return operations.ToArray(); - } - - private static UpdateOperation[] UpdateProjectReferenceVersions( - this XmlDocument document, - string packageId, - UpdaterVersion version, - string path, - XmlNamespaceManager namespaceManager, - bool isDowngradeAllowed - ) - { - var operations = new List(); - - var packageReferences = document.SelectNodes($"//d:PackageReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); - var dotnetCliReferences = document.SelectNodes($"//d:DotNetCliToolReference[@Include='{packageId}' or @Update='{packageId}']", namespaceManager).OfType(); - - foreach(XmlElement packageReference in packageReferences.Concat(dotnetCliReferences)) - { - if(packageReference.HasAttribute("Version")) - { - var currentVersion = new NuGetVersion(packageReference.Attributes["Version"].Value); - - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - - if(operation.IsUpdate) - { - packageReference.SetAttribute("Version", version.Version.ToString()); - } - - operations.Add(operation); - } - else - { - var node = packageReference.SelectSingleNode("d:Version", namespaceManager); - - if(node != null) - { - var currentVersion = new NuGetVersion(node.InnerText); - - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - - if(operation.IsUpdate) - { - node.InnerText = version.Version.ToString(); - } - - operations.Add(operation); - } - } - } - - return operations.ToArray(); - } - - private static IEnumerable GetElements(this XmlDocument document, string xpath) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace("x", document.DocumentElement.NamespaceURI); - - return document - .SelectNodes(xpath, namespaceManager) - .OfType(); - } - - public static async Task GetDocument(this string path, CancellationToken ct) - { - var document = new XmlDocument() - { - PreserveWhitespace = true, - }; - - document.Load(path); - - return document; - } - - public static async Task Save(this XmlDocument document, CancellationToken ct, string path) => document.Save(path); - } -} -#endif diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs deleted file mode 100644 index e3626ae..0000000 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.UAP.cs +++ /dev/null @@ -1,134 +0,0 @@ -#if UAP -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using NuGet.Updater.Entities; -using NuGet.Updater.Log; -using NuGet.Versioning; -using Uno.Extensions; -using Windows.Data.Xml.Dom; -using Windows.Storage; -using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; -using XmlElement = Windows.Data.Xml.Dom.XmlElement; - -namespace NuGet.Updater.Extensions -{ - /// - /// UAP-specific XmlDocument extension methods. - /// - partial class XmlDocumentExtensions - { - public static Dictionary GetPackageReferences(this XmlDocument document) - { - var references = new Dictionary(); - - foreach(var packageReference in document.GetElementsByTagName("PackageReference").Cast()) - { - var packageId = packageReference.GetAttribute("Include"); - - var version = packageReference.GetAttribute("Version"); - if(version.HasValue()) - { - references.Add(packageId, version); - } - else - { - var node = packageReference.GetElementsByTagName("Version").SingleOrDefault(); - - if(node != null) - { - references.Add(packageId, node.InnerText); - } - } - } - - return references; - } - - public static UpdateOperation[] UpdateProjectReferenceVersions( - this XmlDocument document, - string packageId, - UpdaterVersion version, - string path, - bool isDowngradeAllowed - ) - { - var operations = new List(); - - var packageReferences = document.GetElementsByTagName("PackageReference") - .Cast() - .Where(p => p.Attributes.GetNamedItem("Include")?.NodeValue?.ToString() == packageId - || p.Attributes.GetNamedItem("Update")?.NodeValue?.ToString() == packageId - ); - - foreach (var packageReference in packageReferences) - { - string versionAttribute = packageReference.GetAttribute("Version"); - - if (versionAttribute != null && versionAttribute != "") - { - var currentVersion = new NuGetVersion(versionAttribute); - - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - - if (operation.IsUpdate) - { - packageReference.SetAttribute("Version", version.Version.ToString()); - } - - operations.Add(operation); - } - else - { - var node = packageReference.GetElementsByTagName("Version").SingleOrDefault(); - - if (node != null) - { - var currentVersion = new NuGetVersion(node.InnerText); - - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - - if (operation.IsUpdate) - { - node.InnerText = version.Version.ToString(); - } - - operations.Add(operation); - } - } - } - - return operations.ToArray(); - } - - private static IEnumerable GetElements(this XmlDocument document, string xpath) => document - .SelectNodes(xpath) - .OfType(); - - public static async Task GetDocument(this string path, CancellationToken ct) - { - var document = await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(path), new XmlLoadSettings { ElementContentWhiteSpace = true }); - - return document; - } - - public static async Task Save(this XmlDocument document, CancellationToken ct, string path) - { - var xml = document.GetXml(); - xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding") - ? x.Result("$1${declaration} encoding=\"utf-8\"$2") // restore encoding declaration that is stripped by `GetXml` - : x.Value - ); - xml = Regex.Replace(xml, "(\\?>)(<)", "$1\n$2"); // xml declaration should follow by a new line - xml = Regex.Replace(xml, "([^ ])(/>)", "$1 $2"); // self-enclosing tag should end with a space - - await FileIO.WriteTextAsync(await StorageFile.GetFileFromPathAsync(path), xml); - } - } -} -#endif diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index bbc896e..da07cb3 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,22 +1,148 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; using NuGet.Updater.Entities; using NuGet.Updater.Log; using NuGet.Versioning; +using Uno.Extensions; #if UAP +using Windows.Data.Xml.Dom; +using Windows.Storage; using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; #else using XmlDocument = System.Xml.XmlDocument; +using XmlElement = System.Xml.XmlElement; #endif namespace NuGet.Updater.Extensions { - /// - /// Shared XmlDocument extension methods. - /// - public static partial class XmlDocumentExtensions + public static class XmlDocumentExtensions { - public static UpdateOperation[] UpdateNuSpecVersions( + #region PackageReferences + + /// + /// Retrieves the PackageReferences from the given XmlDocument. If a package is present multiple time, only the first version will be returned. + /// + /// + /// A Dictionary where the key is the id of a package and the value its version. + public static Dictionary GetPackageReferences(this XmlDocument document) + { + var references = new Dictionary(); + + var packageReferences = document.SelectElements("//PackageReference"); + var dotnetCliReferences = document.SelectElements("//DotNetCliToolReference"); + + foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) + { + var packageId = packageReference.GetAttribute("Include"); + var packageVersion = packageReference.GetAttribute("Version"); + + if(packageVersion.HasValue()) + { + references.TryAdd(packageId, packageReference.GetAttribute("Version")); + } + else + { + var node = packageReference.SelectSingleNode("Version"); + if(node != null) + { + references.TryAdd(packageId, node.InnerText); + } + } + } + + return references; + } + + /// + /// Updates the PackageReferences with the given id in the given XmlDocument. + /// + /// + /// + /// + /// + /// + /// + public static UpdateOperation[] UpdatePackageReferences( + this XmlDocument document, + string packageId, + UpdaterVersion version, + string path, + bool isDowngradeAllowed) + { + var operations = new List(); + + var packageReferences = document.SelectElements($"//PackageReference[@Include='{packageId}' or @Update='{packageId}']"); + var dotnetCliReferences = document.SelectElements($"//DotNetCliToolReference[@Include='{packageId}' or @Update='{packageId}']"); + + foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) + { + var packageVersion = packageReference.GetAttribute("Version"); + + if(packageVersion.HasValue()) + { + var currentVersion = new NuGetVersion(packageVersion); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if(operation.IsUpdate) + { + packageReference.SetAttribute("Version", version.Version.ToString()); + } + + operations.Add(operation); + } + else + { + var node = packageReference.SelectSingleNode("Version"); + + if(node != null) + { + var currentVersion = new NuGetVersion(node.InnerText); + + var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + + if(operation.IsUpdate) + { + node.InnerText = version.Version.ToString(); + } + + operations.Add(operation); + } + } + } + + return operations.ToArray(); + } + #endregion + + #region Nuspec dependencies + + /// + /// Retrieves the dependency elements from the given XmlDocument. If a package is present multiple time, only the first version will be returned. + /// + /// + /// A Dictionary where the key is the id of a package and the value its version. + public static Dictionary GetDependencies(this XmlDocument document) + => document + .SelectElements("//dependency") + .ToDictionary(dependency => dependency.GetAttribute("id"), dependency => dependency.GetAttribute("version")); + + /// + /// Updates the dependencies with the given id in the given XmlDocument. + /// + /// + /// + /// + /// + /// + /// + public static UpdateOperation[] UpdateDependencies( this XmlDocument document, string packageId, UpdaterVersion version, @@ -26,7 +152,7 @@ bool isDowngradeAllowed { var operations = new List(); - foreach (var node in document.GetElements($"//x:dependency[@id='{packageId}']")) + foreach (var node in document.SelectElements($"//dependency[@id='{packageId}']")) { var versionNodeValue = node.GetAttribute("version"); @@ -48,5 +174,72 @@ bool isDowngradeAllowed return operations.ToArray(); } + #endregion + + #region Utilities + + /// + /// Loads an XmlDocument from the given path. + /// + /// + /// + /// + public static async Task LoadDocument(this string path, CancellationToken ct) + { +#if UAP + var file = await StorageFile + .GetFileFromPathAsync(path) + .AsTask(ct); + + var document = await XmlDocument + .LoadFromFileAsync(file, new XmlLoadSettings { ElementContentWhiteSpace = true }) + .AsTask(ct); +#else + var document = new XmlDocument() + { + PreserveWhitespace = true, + }; + + document.Load(path); +#endif + + return document; + } + + /// + /// Save the given XmlDocument at the given path. + /// + /// + /// + /// + /// + public static async Task Save(this XmlDocument document, CancellationToken ct, string path) + { +#if UAP + var xml = document.GetXml(); + + xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding") + ? x.Result("$1${declaration} encoding=\"utf-8\"$2") // restore encoding declaration that is stripped by `GetXml` + : x.Value + ); + xml = Regex.Replace(xml, "(\\?>)(<)", "$1\n$2"); // xml declaration should follow by a new line + xml = Regex.Replace(xml, "([^ ])(/>)", "$1 $2"); // self-enclosing tag should end with a space + + await FileIO.WriteTextAsync(await StorageFile.GetFileFromPathAsync(path).AsTask(ct), xml); +#else + document.Save(path); +#endif + } + + /// + /// Select the XmlElements matching the given xpath in the XmlDocument. + /// + /// + /// + /// + private static IEnumerable SelectElements(this XmlDocument document, string xpath) => document + .SelectNodes(xpath) + .OfType(); + #endregion } } diff --git a/src/NuGet.Updater/Helpers/SolutionHelper.cs b/src/NuGet.Updater/Helpers/SolutionHelper.cs index 10e2119..d8f7c90 100644 --- a/src/NuGet.Updater/Helpers/SolutionHelper.cs +++ b/src/NuGet.Updater/Helpers/SolutionHelper.cs @@ -53,7 +53,10 @@ internal static async Task GetPackageReferences( if(updateTarget.HasFlag(UpdateTarget.Nuspec)) { - var files = await GetNuspecFiles(ct, solutionPath, log); + foreach(var f in await GetNuspecFiles(ct, solutionPath, log)) + { + packages.AddRange(await GetFileReferences(ct, f, UpdateTarget.Nuspec)); + } } return packages @@ -133,6 +136,9 @@ private static async Task GetNuspecFiles(CancellationToken ct, string var files = await FileHelper.GetFiles(ct, solutionFolder, extensionFilter: ".nuspec"); + //Nuspec files are generated in obj when using the new csproj format + files = files.Where(f => !f.Contains("\\obj\\")).ToArray(); + log?.Write($"Found {files.Length} nuspec files"); return files; @@ -145,9 +151,17 @@ private static async Task GetFileReferences(CancellationToke return new PackageReference[0]; } - var document = await file.GetDocument(ct); + var document = await file.LoadDocument(ct); + var references = new Dictionary(); - var references = document.GetPackageReferences(); + if(target.HasAnyFlag(UpdateTarget.Csproj, UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets)) + { + references = document.GetPackageReferences(); + } + else if(target.HasFlag(UpdateTarget.Nuspec)) + { + references = document.GetDependencies(); + } return references .Select(g => new PackageReference(g.Key, g.Value, file, target)) diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 67bbbf1..d0d8f9d 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -6,7 +7,6 @@ using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; using NuGet.Updater.Log; -using System.IO; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; @@ -41,11 +41,6 @@ public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, string s { } - public NuGetUpdater(UpdaterParameters parameters, Logger log) - : this(parameters, parameters.GetSources(), log) - { - } - internal NuGetUpdater(UpdaterParameters parameters, IUpdaterSource[] packageSources, Logger log) { //TODO validate parameters @@ -59,40 +54,25 @@ public async Task UpdatePackages(CancellationToken ct) _log.Clear(); var packages = await GetPackages(ct); + //Open all the files at once so we don't have to do it all the time var documents = await OpenFiles(ct, packages); foreach(var package in packages.Where(p => _parameters.ShouldUpdatePackage(p))) { - var latest = package.GetLatestVersion(_parameters); + var latestVersion = package.LatestVersion; - if(latest == null) + if(latestVersion == null) { _log.Write($"Skipping [{package.PackageId}]: no {string.Join(" or ", _parameters.TargetVersions)} version found."); - continue; } - - _log.Write($"Latest matching version for [{package.PackageId}] is [{latest.Version}] on {latest.FeedUri}"); - - var updates = new UpdateOperation[0]; - - foreach(var files in package.Reference.Files) + else { - switch(files.Key) - { - case var t when t.HasFlag(UpdateTarget.Nuspec): - updates = await UpdateNuSpecs(ct, package.PackageId, latest, documents.GetItems(files.Value), _parameters.IsDowngradeAllowed); - break; - case var t when t.HasAnyFlag(UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets, UpdateTarget.Csproj): - updates = await UpdateProjects(ct, package.PackageId, latest, documents.GetItems(files.Value), _parameters.IsDowngradeAllowed); - break; - default: - break; - } - } + _log.Write($"Latest matching version for [{package.PackageId}] is [{latestVersion.Version}] on {latestVersion.FeedUri}"); - _log.Write(updates); + _log.Write(await UpdateFiles(ct, package.PackageId, latestVersion, package.Reference.Files, documents)); - _log.Write(""); + _log.Write(""); + } } _log.WriteSummary(_parameters); @@ -100,6 +80,11 @@ public async Task UpdatePackages(CancellationToken ct) return true; } + /// + /// Retrieves the NuGet packages according to the set parameters. + /// + /// + /// internal async Task GetPackages(CancellationToken ct) { var packages = new List(); @@ -120,19 +105,24 @@ internal async Task GetPackages(CancellationToken ct) _log.Write(""); //If the reference has been found on multiple sources, we merge the packages found together - var package = matchingPackages + var mergedPackage = matchingPackages .GroupBy(p => p.Reference) - .Select(g => new UpdaterPackage(g.Key, g.SelectMany(p => p.AvailableVersions).ToArray())) + .Select(g => new UpdaterPackage(g.Key, g.SelectMany(p => p.AvailableVersions))) .SingleOrDefault(); - packages.Add(package); + //Retrieve the latest version here so it is only done once per package + packages.Add(new UpdaterPackage(reference, mergedPackage.GetLatestVersion(_parameters))); } - return packages - .Where(p => p.AvailableVersions?.Any() ?? false) - .ToArray(); + return packages.ToArray(); } + /// + /// Opens the XML files where packages were found. + /// + /// + /// + /// private async Task> OpenFiles(CancellationToken ct, UpdaterPackage[] packages) { var files = packages @@ -144,63 +134,55 @@ private async Task> OpenFiles(CancellationToken foreach(var file in files) { - documents.Add(file, await file.GetDocument(ct)); + documents.Add(file, await file.LoadDocument(ct)); } return documents; } - private static async Task UpdateNuSpecs( - CancellationToken ct, - string packageId, - UpdaterVersion version, - Dictionary nuspecDocuments, - bool isDowngradeAllowed - ) + /// + /// Updates a package to the given value in the given files. + /// + /// + /// + /// + /// + /// + private async Task UpdateFiles( + CancellationToken ct, + string packageId, + UpdaterVersion version, + Dictionary targetFiles, + Dictionary documents + ) { var operations = new List(); - foreach(var document in nuspecDocuments) + foreach(var files in targetFiles) { - var path = document.Key; - var xmlDocument = document.Value; - - var updates = xmlDocument.UpdateNuSpecVersions(packageId, version, path, isDowngradeAllowed); + var updateTarget = files.Key; - if(updates.Any(u => u.IsUpdate)) + foreach(var path in files.Value) { - await xmlDocument.Save(ct, path); - } - - operations.AddRange(updates); - } - - return operations.ToArray(); - } - - private static async Task UpdateProjects( - CancellationToken ct, - string packageId, - UpdaterVersion version, - Dictionary projectDocuments, - bool isDowngradeAllowed - ) - { - var operations = new List(); + var document = documents[path]; + var updates = new UpdateOperation[0]; - foreach(var document in projectDocuments) - { - var path = document.Key; - var xmlDocument = document.Value; + if(updateTarget.HasFlag(UpdateTarget.Nuspec)) + { + updates = document.UpdateDependencies(packageId, version, path, _parameters.IsDowngradeAllowed); + } + else if(updateTarget.HasAnyFlag(UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets, UpdateTarget.Csproj)) + { + updates = document.UpdatePackageReferences(packageId, version, path, _parameters.IsDowngradeAllowed); + } - var updates = xmlDocument.UpdateProjectReferenceVersions(packageId, version, path, isDowngradeAllowed); + if(updates.Any(u => u.IsUpdate)) + { + await document.Save(ct, path); + } - if(updates.Any(u => u.IsUpdate)) - { - await xmlDocument.Save(ct, path); + operations.AddRange(updates); } - - operations.AddRange(updates); } return operations.ToArray(); From 3f9ce878d755146c6e186758814df768ab74cc84 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 5 Aug 2019 17:41:22 -0400 Subject: [PATCH 110/201] Fixed xml node selection --- .../Extensions/XmlDocumentExtensions.cs | 50 ++++++++++++------- src/NuGet.Updater/NuGetUpdater.cs | 4 +- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index da07cb3..fa2eca9 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using NuGet.Updater.Entities; @@ -10,13 +9,16 @@ using Uno.Extensions; #if UAP +using System.Text.RegularExpressions; using Windows.Data.Xml.Dom; using Windows.Storage; using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; using XmlElement = Windows.Data.Xml.Dom.XmlElement; +using XmlNode = Windows.Data.Xml.Dom.IXmlNode; #else using XmlDocument = System.Xml.XmlDocument; using XmlElement = System.Xml.XmlElement; +using XmlNode = System.Xml.XmlNode; #endif namespace NuGet.Updater.Extensions @@ -34,8 +36,8 @@ public static Dictionary GetPackageReferences(this XmlDocument d { var references = new Dictionary(); - var packageReferences = document.SelectElements("//PackageReference"); - var dotnetCliReferences = document.SelectElements("//DotNetCliToolReference"); + var packageReferences = document.SelectElements("PackageReference"); + var dotnetCliReferences = document.SelectElements("DotNetCliToolReference"); foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) { @@ -48,7 +50,7 @@ public static Dictionary GetPackageReferences(this XmlDocument d } else { - var node = packageReference.SelectSingleNode("Version"); + var node = packageReference.SelectNode("Version"); if(node != null) { references.TryAdd(packageId, node.InnerText); @@ -77,8 +79,8 @@ public static UpdateOperation[] UpdatePackageReferences( { var operations = new List(); - var packageReferences = document.SelectElements($"//PackageReference[@Include='{packageId}' or @Update='{packageId}']"); - var dotnetCliReferences = document.SelectElements($"//DotNetCliToolReference[@Include='{packageId}' or @Update='{packageId}']"); + var packageReferences = document.SelectElements("PackageReference", $"[@Include='{packageId}' or @Update='{packageId}']"); + var dotnetCliReferences = document.SelectElements("DotNetCliToolReference", $"[@Include='{packageId}' or @Update='{packageId}']"); foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) { @@ -99,7 +101,7 @@ public static UpdateOperation[] UpdatePackageReferences( } else { - var node = packageReference.SelectSingleNode("Version"); + var node = packageReference.SelectNode("Version"); if(node != null) { @@ -119,9 +121,9 @@ public static UpdateOperation[] UpdatePackageReferences( return operations.ToArray(); } - #endregion +#endregion - #region Nuspec dependencies +#region Nuspec dependencies /// /// Retrieves the dependency elements from the given XmlDocument. If a package is present multiple time, only the first version will be returned. @@ -130,7 +132,7 @@ public static UpdateOperation[] UpdatePackageReferences( /// A Dictionary where the key is the id of a package and the value its version. public static Dictionary GetDependencies(this XmlDocument document) => document - .SelectElements("//dependency") + .SelectElements("dependency") .ToDictionary(dependency => dependency.GetAttribute("id"), dependency => dependency.GetAttribute("version")); /// @@ -152,7 +154,7 @@ bool isDowngradeAllowed { var operations = new List(); - foreach (var node in document.SelectElements($"//dependency[@id='{packageId}']")) + foreach (var node in document.SelectElements("dependency", $"[@id='{packageId}']")) { var versionNodeValue = node.GetAttribute("version"); @@ -174,9 +176,9 @@ bool isDowngradeAllowed return operations.ToArray(); } - #endregion +#endregion - #region Utilities +#region Utilities /// /// Loads an XmlDocument from the given path. @@ -232,14 +234,26 @@ public static async Task Save(this XmlDocument document, CancellationToken ct, s } /// - /// Select the XmlElements matching the given xpath in the XmlDocument. + /// Select the XmlElements matching the given element name in the XmlDocument. /// /// - /// + /// Name of the XML tags to look for. + /// Addtional xpath filter to apply. /// - private static IEnumerable SelectElements(this XmlDocument document, string xpath) => document - .SelectNodes(xpath) + private static IEnumerable SelectElements(this XmlDocument document, string elementName, string filter = null) => document + .SelectNodes($"//*[local-name() = '{elementName}']{filter}") //Using local-name to avoid having to deal with namespaces .OfType(); - #endregion + + /// + /// Select the first child node with the given of an element. + /// + /// + /// + /// + private static XmlNode SelectNode(this XmlElement element, string name) => element + .ChildNodes + .OfType() + .FirstOrDefault(e => e.LocalName == name); +#endregion } } diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index d0d8f9d..ae42204 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -70,9 +70,9 @@ public async Task UpdatePackages(CancellationToken ct) _log.Write($"Latest matching version for [{package.PackageId}] is [{latestVersion.Version}] on {latestVersion.FeedUri}"); _log.Write(await UpdateFiles(ct, package.PackageId, latestVersion, package.Reference.Files, documents)); - - _log.Write(""); } + + _log.Write(""); } _log.WriteSummary(_parameters); From f4f25d178d7d0b6c6a35a483e97ce092bc271058 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 5 Aug 2019 18:32:12 -0400 Subject: [PATCH 111/201] Fixed retrieval of package version in UWP --- src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index fa2eca9..522471a 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -253,7 +253,7 @@ private static IEnumerable SelectElements(this XmlDocument document, private static XmlNode SelectNode(this XmlElement element, string name) => element .ChildNodes .OfType() - .FirstOrDefault(e => e.LocalName == name); + .FirstOrDefault(e => e.LocalName.ToString().Equals(name, StringComparison.OrdinalIgnoreCase)); #endregion } } From 8f83a72c7bbe5d492a0fb3bc4546a730f3f35565 Mon Sep 17 00:00:00 2001 From: Papus Koulibaly Date: Wed, 21 Aug 2019 15:16:39 -0400 Subject: [PATCH 112/201] Put back ignorePackages and updatePackages options for retrocompatibility --- src/NuGet.Updater.Tool/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index ccad274..a50d591 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -28,8 +28,8 @@ public static async Task Main(string[] args) { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, { "packageAuthor=|a=", "The {author} of the packages to update; used for NuGet.org", s => Set(p => p.PackageAuthor = s)}, - { "ignore=|i=", "A comma-separated list of {packages} to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, - { "update=|u=", "A comma-separated list of {packages} to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, + { "ignorePackages=|ignore=|i=", "A comma-separated list of {packages} to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, + { "updatePackages=|update=|u=", "A comma-separated list of {packages} to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, { "outputFile=|of=", "The {path} to a file where the update summary will be written", s => summaryFile = s }, }; From a740c39e35ee7fc878122d014e3a2df5d1eb1f9f Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Wed, 21 Aug 2019 16:28:34 -0400 Subject: [PATCH 113/201] fixed empty option being parsed as list of one empty string --- src/NuGet.Updater.Tool/Program.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index a50d591..0db8790 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Mono.Options; @@ -77,20 +78,6 @@ private static void AddPrivateFeed(string value) _isParameterSet = true; } - private static List GetList(string value) - { - var list = new List(); - - if(value.Contains(",")) - { - list.AddRange(value.Split(",")); - } - else - { - list.Add(value); - } - - return list; - } + private static string[] GetList(string value) => value.Split(",;".ToArray(), StringSplitOptions.RemoveEmptyEntries); } } From 241ae7d81a80468f81b949091a28d4d0754fd69e Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Wed, 21 Aug 2019 18:27:37 -0400 Subject: [PATCH 114/201] fixed no package is being updated --- src/NuGet.Updater.Tool/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 0db8790..b72c3fa 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -78,6 +78,6 @@ private static void AddPrivateFeed(string value) _isParameterSet = true; } - private static string[] GetList(string value) => value.Split(",;".ToArray(), StringSplitOptions.RemoveEmptyEntries); + private static string[] GetList(string value) => !string.IsNullOrEmpty(value) ? value.Split(",;".ToArray(), StringSplitOptions.RemoveEmptyEntries) : null; } } From a60cb0a418fdc6a1eee91842620899dbb0c71d05 Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Thu, 22 Aug 2019 10:32:28 -0400 Subject: [PATCH 115/201] fixed nre when -v options is unspecified or empty --- src/NuGet.Updater.Tool/Program.cs | 2 +- src/NuGet.Updater/Entities/UpdaterParameters.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index b72c3fa..3595c1e 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -24,7 +24,7 @@ public static async Task Main(string[] args) { "help|h", "Displays this help screen", s => isHelp = true }, { "solution=|s=", "The {path} to the solution to update", s => Set(p => p.SolutionRoot = s) }, { "feed=|f=", "A private feed to use for the update; the format is {url|accessToken}; can be specified multiple times", s => AddPrivateFeed(s) }, - { "version=|versions=|v=", "The target {versions} to use", s => Set(p => p.TargetVersions = GetList(s))}, + { "version=|versions=|v=", "The target {versions} to use", s => Set(p => p.TargetVersions = GetList(s) ?? p.TargetVersions)}, { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 2013c63..2c59ad3 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -17,7 +17,7 @@ public class UpdaterParameters /// /// Gets or sets the versions to update to (stable, dev, beta, etc.), in order of priority. /// - public IEnumerable TargetVersions { get; set; } + public IEnumerable TargetVersions { get; set; } = new[] { "stable" }; /// /// Gets or sets a value indicating whether the version should exactly match the target version. From f6dbde77d2dcc062e9e2eef2911f73835dc88ca1 Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Tue, 10 Sep 2019 11:20:38 -0400 Subject: [PATCH 116/201] fixed failure to parse Directory.Build.targets with update --- src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index 522471a..53ebfe5 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -41,7 +41,9 @@ public static Dictionary GetPackageReferences(this XmlDocument d foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) { - var packageId = packageReference.GetAttribute("Include"); + var packageId = new[] { "Include", "Update", "Remove" } + .Select(packageReference.GetAttribute) + .FirstOrDefault(x => !string.IsNullOrEmpty(x)); var packageVersion = packageReference.GetAttribute("Version"); if(packageVersion.HasValue()) From 7fa4c29120d1674db6e4c49370d507b42c51ac92 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Sep 2019 17:14:45 -0400 Subject: [PATCH 117/201] Updated sources handling --- CHANGELOG.md | 2 + README.md | 3 +- .../Entities/TestPackageSource.cs | 10 +-- src/NuGet.Updater.Tests/NuGetUpdaterTests.cs | 5 +- src/NuGet.Updater.Tool/Program.cs | 72 +++++++++++-------- .../Properties/launchSettings.json | 2 +- src/NuGet.Updater/Entities/IUpdaterSource.cs | 7 +- .../Entities/UpdaterParameters.cs | 10 +-- src/NuGet.Updater/Entities/UpdaterSource.cs | 20 +++--- .../Extensions/UpdaterParametersExtension.cs | 44 +++++------- src/NuGet.Updater/NuGetUpdater.cs | 15 ++-- 11 files changed, 100 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f00b1..fa7782b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added aliases for .Net Core application - Added support for multiple target versions - Added support for solution folders alongside sln files +- Added better error handling ### Changed - Replaced package owner with package author - Reworked internal logic of the updater: package list is now fetched from the files before being looked up online +- Updated sources handling to add support for any number of public or private package sources (it was previously limited to NuGet.org and any number of private package sources) ### Deprecated diff --git a/README.md b/README.md index d54a7a8..7d7a6dd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ NuGet Updater allows batch updates of NuGet packages in a solution. ## Getting Started -The NuGet Updater can be installed as a standalone .Net Core tool using the following command: `dotnet tool install -g nuget.updater.tool` +The NuGet Updater can be installed as a standalone .Net Core tool using the following command: `dotnet tool install -g nventive.NuGet.Updater.Tool` +Help can be found with : `nugetupdater --help` The NuGet updater library can also be installed as a NuGet package in a UWP or WPF application. diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs index 7eb12a6..8264adc 100644 --- a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs +++ b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs @@ -12,21 +12,21 @@ public class TestPackageSource : IUpdaterSource { private readonly Dictionary _packages; - public TestPackageSource(Uri uri, Dictionary packages) + public TestPackageSource(Uri url, Dictionary packages) { - Uri = uri; + Url = url; _packages = packages; } - public Uri Uri { get; } + public Uri Url { get; } - public async Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null) + public async Task GetPackage(CancellationToken ct, PackageReference reference, string author, Logger log = null) { var packageId = reference.Id; var versions = _packages .GetValueOrDefault(reference.Id) - ?.Select(v => new UpdaterVersion(v, Uri)) + ?.Select(v => new UpdaterVersion(v, Url)) .ToArray() ?? new UpdaterVersion[0]; return new UpdaterPackage(reference, versions); diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs index 9f3c8c8..056deb5 100644 --- a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs +++ b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; using NuGet.Updater.Log; using NuGet.Updater.Tests.Entities; using Uno.Extensions; @@ -32,11 +30,12 @@ public async Task GivenUnspecifiedTarget_NoUpdateIsMade() SolutionRoot = "MySolution.sln", UpdateTarget = UpdateTarget.Unspecified, TargetVersions = new[] { "stable" }, + Sources = new List { TestSource }, }; var logger = new Logger(Console.Out); - var updater = new NuGetUpdater(parameters, new[] { TestSource }, logger); + var updater = new NuGetUpdater(parameters, logger); await updater.UpdatePackages(CancellationToken.None); diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 3595c1e..3ef32e1 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -15,44 +15,54 @@ public class Program public static async Task Main(string[] args) { - var isHelp = false; - var isSilent = false; - string summaryFile = default; + try + { + var isHelp = false; + var isSilent = false; + string summaryFile = default; - var options = new OptionSet + var options = new OptionSet { { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The {path} to the solution to update", s => Set(p => p.SolutionRoot = s) }, - { "feed=|f=", "A private feed to use for the update; the format is {url|accessToken}; can be specified multiple times", s => AddPrivateFeed(s) }, - { "version=|versions=|v=", "The target {versions} to use", s => Set(p => p.TargetVersions = GetList(s) ?? p.TargetVersions)}, - { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, + { "solution=|s=", "The {path} to the solution or folder to update", s => Set(p => p.SolutionRoot = s) }, + { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => AddSource(s) }, + { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, + { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, + { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.PackagesToUpdate.Add(s)) }, + { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.PackageAuthor = s)}, + { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => summaryFile = s }, { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, - { "useNuGetorg|n", "Whether to pull packages from NuGet.org", _ => Set(p => p.IncludeNuGetOrg = true )}, - { "packageAuthor=|a=", "The {author} of the packages to update; used for NuGet.org", s => Set(p => p.PackageAuthor = s)}, - { "ignorePackages=|ignore=|i=", "A comma-separated list of {packages} to ignore", s => Set(p => p.PackagesToIgnore = GetList(s)) }, - { "updatePackages=|update=|u=", "A comma-separated list of {packages} to update; not specifying this will update all packages found", s => Set(p => p.PackagesToUpdate = GetList(s)) }, - { "outputFile=|of=", "The {path} to a file where the update summary will be written", s => summaryFile = s }, + { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Sources.Add(UpdaterSource.NuGetOrg)) }, + { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, }; - _isParameterSet = false; - _parameters = new UpdaterParameters - { - UpdateTarget = UpdateTarget.All, - PrivateFeeds = new Dictionary(), - }; + _isParameterSet = false; + _parameters = new UpdaterParameters + { + UpdateTarget = UpdateTarget.All, + Sources = new List(), + PackagesToIgnore = new List(), + PackagesToUpdate = new List(), + }; - options.Parse(args); + options.Parse(args); - if(isHelp || !_isParameterSet) - { - Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); - options.WriteOptionDescriptions(Console.Out); + if(isHelp || !_isParameterSet) + { + Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); + Console.WriteLine(); + options.WriteOptionDescriptions(Console.Out); + } + else + { + var updater = new NuGetUpdater(_parameters, isSilent ? null : Console.Out, summaryFile); + + await updater.UpdatePackages(CancellationToken.None); + } } - else + catch(Exception ex) { - var updater = new NuGetUpdater(_parameters, isSilent ? null : Console.Out, summaryFile); - - await updater.UpdatePackages(CancellationToken.None); + Console.WriteLine($"Error: {ex.Message}"); } } @@ -62,17 +72,17 @@ private static void Set(Action set) _isParameterSet = true; } - private static void AddPrivateFeed(string value) + private static void AddSource(string value) { const char separator = '|'; if(value.Contains(separator)) { var parts = value.Split(separator); - _parameters.PrivateFeeds.Add(parts[0], parts[1]); + _parameters.Sources.Add(new UpdaterSource(parts[0], parts[1])); } else { - _parameters.PrivateFeeds.Add(value, null); + _parameters.Sources.Add(new UpdaterSource(value)); } _isParameterSet = true; diff --git a/src/NuGet.Updater.Tool/Properties/launchSettings.json b/src/NuGet.Updater.Tool/Properties/launchSettings.json index 640bec2..c96edea 100644 --- a/src/NuGet.Updater.Tool/Properties/launchSettings.json +++ b/src/NuGet.Updater.Tool/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "NuGet.Updater.Tool": { "commandName": "Project", - "commandLineArgs": "" + "commandLineArgs": "-v=a -v=b -v=c -u=toto -u=tata -i=zaza -i=zozo -f=nuget.org -f=dev.azure.com" } } } diff --git a/src/NuGet.Updater/Entities/IUpdaterSource.cs b/src/NuGet.Updater/Entities/IUpdaterSource.cs index 670744b..9e1ade0 100644 --- a/src/NuGet.Updater/Entities/IUpdaterSource.cs +++ b/src/NuGet.Updater/Entities/IUpdaterSource.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using NuGet.Updater.Log; @@ -6,6 +7,8 @@ namespace NuGet.Updater.Entities { public interface IUpdaterSource { - Task GetPackage(CancellationToken ct, PackageReference reference, Logger log = null); + Uri Url { get; } + + Task GetPackage(CancellationToken ct, PackageReference reference, string author, Logger log = null); } } diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 2c59ad3..f9b8fe1 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -10,14 +10,14 @@ public class UpdaterParameters public string SolutionRoot { get; set; } /// - /// Gets or sets a list of private feed URLs and access tokens to get packages from. + /// Gets or sets a list of source to get packages from. /// - public Dictionary PrivateFeeds { get; set; } + public ICollection Sources { get; set; } /// /// Gets or sets the versions to update to (stable, dev, beta, etc.), in order of priority. /// - public IEnumerable TargetVersions { get; set; } = new[] { "stable" }; + public ICollection TargetVersions { get; set; } = new List { "stable" }; /// /// Gets or sets a value indicating whether the version should exactly match the target version. @@ -42,12 +42,12 @@ public class UpdaterParameters /// /// Gets or sets a list of packages to ignore. /// - public IEnumerable PackagesToIgnore { get; set; } + public ICollection PackagesToIgnore { get; set; } /// /// Gets or sets a list of packages to update; all packages found will be updated if nothing is specified. /// - public IEnumerable PackagesToUpdate { get; set; } + public ICollection PackagesToUpdate { get; set; } /// /// Gets or sets the name of the author of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. diff --git a/src/NuGet.Updater/Entities/UpdaterSource.cs b/src/NuGet.Updater/Entities/UpdaterSource.cs index b926855..46e6e58 100644 --- a/src/NuGet.Updater/Entities/UpdaterSource.cs +++ b/src/NuGet.Updater/Entities/UpdaterSource.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using NuGet.Configuration; using NuGet.Updater.Extensions; @@ -8,27 +9,30 @@ namespace NuGet.Updater.Entities { public class UpdaterSource : IUpdaterSource { + public static readonly UpdaterSource NuGetOrg = new UpdaterSource("https://api.nuget.org/v3/index.json"); + private readonly PackageSource _packageSource; - private readonly string _packageAuthor; + private readonly bool _isPrivate = false; - public UpdaterSource(string url, string packageOwner) + public UpdaterSource(string url) { - _packageAuthor = packageOwner; _packageSource = new PackageSource(url); } - public UpdaterSource(string url, string accessToken, string packageAuthor) + public UpdaterSource(string url, string accessToken) { - //Not filtering packages from private feeds - _packageAuthor = null; _packageSource = GetPackageSource(url, accessToken); + _isPrivate = true; } + public Uri Url => _packageSource.SourceUri; + public Task GetPackage( CancellationToken ct, PackageReference reference, + string author, Logger log = null - ) => _packageSource.GetPackage(ct, reference, _packageAuthor, log); + ) => _packageSource.GetPackage(ct, reference, author: _isPrivate ? null : author, log); //Not filtering packages from private sources private static PackageSource GetPackageSource(string url, string accessToken) { diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index c96852d..12507d5 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -9,23 +9,6 @@ namespace NuGet.Updater.Extensions { internal static class UpdaterParametersExtension { - internal static IUpdaterSource[] GetSources(this UpdaterParameters parameters) - { - var packageSources = new List(); - - if(parameters.PrivateFeeds?.Any() ?? false) - { - packageSources.AddRange(parameters.PrivateFeeds.Select(g => new UpdaterSource(g.Key, g.Value, parameters.PackageAuthor))); - } - - if(parameters.IncludeNuGetOrg) - { - packageSources.Add(new UpdaterSource("https://api.nuget.org/v3/index.json", parameters.PackageAuthor)); - } - - return packageSources.ToArray(); - } - internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, UpdaterPackage package) { var isPackageToIgnore = parameters.PackagesToIgnore?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? false; @@ -49,20 +32,16 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Update targeting {files} files under {parameters.SolutionRoot}"; - var packageSources = new List(); - - if(parameters.IncludeNuGetOrg) + if(parameters.Sources?.Any() ?? false) { - packageSources.Add(parameters.PackageAuthor.SelectOrDefault(o => $"NuGet.org (limited to packages by {o})", "NuGet.org")); + yield return $"- Using NuGet packages from {string.Join(", ", parameters.Sources.Select(s => s.Url))}"; } - if(parameters.PrivateFeeds?.Any() ?? false) + if(parameters.PackageAuthor.HasValue()) { - packageSources.AddRange(parameters.PrivateFeeds.Keys); + yield return $"- Using only public packages authored by {parameters.PackageAuthor}"; } - yield return $"- Using NuGet packages from {string.Join(", ", packageSources)}"; - yield return $"- Using {(parameters.Strict ? "exact " : "")}target version {string.Join(", then ", parameters.TargetVersions)}"; if (parameters.IsDowngradeAllowed) @@ -80,5 +59,20 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Updating only {string.Join(",", parameters.PackagesToUpdate)}"; } } + + public static UpdaterParameters Validate(this UpdaterParameters parameters) + { + if(parameters.SolutionRoot.IsNullOrEmpty()) + { + throw new InvalidOperationException("The solution root must be specified"); + } + + if(parameters.Sources.None()) + { + throw new InvalidOperationException("At least one NuGet source should be specified"); + } + + return parameters; + } } } diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index ae42204..8d8356b 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -22,7 +22,6 @@ public class NuGetUpdater { private readonly UpdaterParameters _parameters; private readonly Logger _log; - private readonly IUpdaterSource[] _packageSources; public static async Task UpdateAsync( CancellationToken ct, @@ -37,16 +36,14 @@ public static async Task UpdateAsync( } public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, string summaryOutputFilePath) - : this(parameters, parameters.GetSources(), new Logger(logWriter, summaryOutputFilePath)) + : this(parameters, new Logger(logWriter, summaryOutputFilePath)) { } - internal NuGetUpdater(UpdaterParameters parameters, IUpdaterSource[] packageSources, Logger log) + internal NuGetUpdater(UpdaterParameters parameters, Logger log) { - //TODO validate parameters - _parameters = parameters; + _parameters = parameters.Validate(); _log = log; - _packageSources = packageSources; } public async Task UpdatePackages(CancellationToken ct) @@ -92,14 +89,14 @@ internal async Task GetPackages(CancellationToken ct) _log.Write($"Found {references.Length} references"); _log.Write(""); - _log.Write($"Retrieving packages from {_packageSources.Length} sources"); + _log.Write($"Retrieving packages from {_parameters.Sources.Count} sources"); foreach(var reference in references.OrderBy(r => r.Id)) { var matchingPackages = new List(); - foreach(var source in _packageSources) + foreach(var source in _parameters.Sources) { - matchingPackages.Add(await source.GetPackage(ct, reference, _log)); + matchingPackages.Add(await source.GetPackage(ct, reference, _parameters.PackageAuthor, _log)); } _log.Write(""); From 1645cbe58144d03131d027c6fa7cc46c6a2a4ba7 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 13 Sep 2019 10:16:01 -0400 Subject: [PATCH 118/201] Added sample commands in the documentation --- CHANGELOG.md | 3 +- README.md | 50 +++++++++++++++++++++++++++---- src/NuGet.Updater.Tool/Program.cs | 3 +- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa7782b..6249c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Replaced package owner with package author - Reworked internal logic of the updater: package list is now fetched from the files before being looked up online -- Updated sources handling to add support for any number of public or private package sources (it was previously limited to NuGet.org and any number of private package sources) +- Updated sources handling to add support for any number of public or private package sources (it was previously limited to NuGet.org and any number of private package sources +- Set the current directory as the solution root if none is specified in the .Net Core application ### Deprecated diff --git a/README.md b/README.md index 7d7a6dd..f54c310 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,54 @@ NuGet Updater allows batch updates of NuGet packages in a solution. ## Getting Started -The NuGet Updater can be installed as a standalone .Net Core tool using the following command: `dotnet tool install -g nventive.NuGet.Updater.Tool` -Help can be found with : `nugetupdater --help` +The NuGet Updater can be installed as a standalone .Net Core tool using the following command: +`dotnet tool install -g nventive.NuGet.Updater.Tool` -The NuGet updater library can also be installed as a NuGet package in a UWP or WPF application. +Help can be found with : +`nugetupdater --help` -## Features +The NuGet updater library can also be installed as a NuGet package in a UWP or WPF application. -Update NuGet packages to the latest version with a specific tag (i.e. dev, alpha, etc.). +## Sample commands + +- Update all packages in the current folder (and its subfolders) to the latest stable version found on NuGet.org +``` +nugetupdater --useNuGetorg +``` +This can also be achieved using the following command +``` +nugetupdater --feed=https://api.nuget.org/v3/index.json +``` + +- Update all packages in `MySolution.sln` to the latest stable version available on NuGet.org +``` +nugetupdater --solution=MySolution.sln -n +``` + +- Update packages to either beta, stable or alpha (whichever's the highest) +``` +nugetupdater -s=MySolution.sln -n --version=beta -v=alpha +``` + +- Update packages to the latest beta version available on a private feed +``` +nugetupdater -s=MySolution.sln --feed=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --version=beta +``` + +- Update packages from `nventive` from NuGet.org, except for `PackageA` and `PackageB` +``` +nugetupdater -s=MySolution.sln -n --packageAuthor=nventive --ignore=PackageA -i=PackageB +``` + +- Update only `PackageA` and `PackageB` from NuGet.org and a private feed +``` +nugetupdater -s=MySolution.sln -n -f=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --update=PackageA -u=PackageB +``` + +- Update packages to latest stable, even if a higher version is already found in the solution +``` +nugetupdater -s=MySolution.sln -n --allowDowngrade +``` ## Changelog diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 3ef32e1..26c0724 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -24,7 +24,7 @@ public static async Task Main(string[] args) var options = new OptionSet { { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The {path} to the solution or folder to update", s => Set(p => p.SolutionRoot = s) }, + { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.SolutionRoot = s) }, { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => AddSource(s) }, { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, @@ -39,6 +39,7 @@ public static async Task Main(string[] args) _isParameterSet = false; _parameters = new UpdaterParameters { + SolutionRoot = Environment.CurrentDirectory, UpdateTarget = UpdateTarget.All, Sources = new List(), PackagesToIgnore = new List(), From 1106365a149a2a890e1864793b81549ff0df06e3 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Sep 2019 16:53:44 -0400 Subject: [PATCH 119/201] Improved package retrieval --- .../Extensions/PackageSourceExtensions.cs | 19 ++++++++++++------- .../Extensions/UpdaterParametersExtension.cs | 9 --------- src/NuGet.Updater/NuGetUpdater.cs | 14 ++++++++++---- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs index 30cd488..0f5587a 100644 --- a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using NuGet.Common; @@ -21,6 +22,8 @@ public static async Task GetPackage( Logger log = null ) { + var logMessage = new StringBuilder(); + var repositoryProvider = new SourceRepositoryProvider( Settings.LoadDefaultSettings(null), Repository.Provider.GetCoreV3() @@ -30,27 +33,29 @@ public static async Task GetPackage( var packageId = reference.Id; - log?.Write($"Retrieving package {packageId} from {source.SourceUri}"); + logMessage.AppendLine($"Retrieving package {packageId} from {source.SourceUri}"); - var metadata = (await repository + var packageMetadata = (await repository .GetResource() .GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, new NullLogger(), ct)) .ToArray(); - log?.Write(metadata.Length > 0 ? $"Found {metadata.Length} versions" : "No versions found"); + logMessage.AppendLine(packageMetadata.Length > 0 ? $"Found {packageMetadata.Length} versions" : "No versions found"); - if(author.HasValue() && metadata.Any()) + if(author.HasValue() && packageMetadata.Any()) { - metadata = metadata.Where(m => m.HasAuthor(author)).ToArray(); + packageMetadata = packageMetadata.Where(m => m.HasAuthor(author)).ToArray(); - log?.Write(metadata.Length > 0 ? $"Found {metadata.Length} version from {author}" : $"No versions from {author} found"); + logMessage.AppendLine(packageMetadata.Length > 0 ? $"Found {packageMetadata.Length} versions from {author}" : $"No versions from {author} found"); } - var versions = metadata + var versions = packageMetadata .Cast() .Select(m => new UpdaterVersion(m.Version, source.SourceUri)) .ToArray(); + log?.Write(logMessage.ToString()); + return new UpdaterPackage(reference, versions); } } diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 12507d5..32ac0f4 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using NuGet.Configuration; using NuGet.Updater.Entities; using Uno.Extensions; @@ -9,14 +8,6 @@ namespace NuGet.Updater.Extensions { internal static class UpdaterParametersExtension { - internal static bool ShouldUpdatePackage(this UpdaterParameters parameters, UpdaterPackage package) - { - var isPackageToIgnore = parameters.PackagesToIgnore?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? false; - var isPackageToUpdate = parameters.PackagesToUpdate?.Contains(package.PackageId, StringComparer.OrdinalIgnoreCase) ?? true; - - return isPackageToUpdate && !isPackageToIgnore; - } - internal static IEnumerable GetSummary(this UpdaterParameters parameters) { yield return $"## Configuration"; diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 8d8356b..0c37cdc 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -54,7 +54,7 @@ public async Task UpdatePackages(CancellationToken ct) //Open all the files at once so we don't have to do it all the time var documents = await OpenFiles(ct, packages); - foreach(var package in packages.Where(p => _parameters.ShouldUpdatePackage(p))) + foreach(var package in packages) { var latestVersion = package.LatestVersion; @@ -93,12 +93,18 @@ internal async Task GetPackages(CancellationToken ct) foreach(var reference in references.OrderBy(r => r.Id)) { - var matchingPackages = new List(); - foreach(var source in _parameters.Sources) + if(_parameters.PackagesToIgnore.Contains(reference.Id) || + (_parameters.PackagesToUpdate.Any() && !_parameters.PackagesToUpdate.Contains(reference.Id)) + ) { - matchingPackages.Add(await source.GetPackage(ct, reference, _parameters.PackageAuthor, _log)); + continue; } + var matchingPackages = await Task.WhenAll(_parameters + .Sources + .Select(source => source.GetPackage(ct, reference, _parameters.PackageAuthor, _log)) + ); + _log.Write(""); //If the reference has been found on multiple sources, we merge the packages found together From ab13a5f1cb51942245ad4cb650ffba10a781715b Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 24 Sep 2019 10:29:34 -0400 Subject: [PATCH 120/201] Mapped the Strict parameter to the tool --- CHANGELOG.md | 1 + src/NuGet.Updater.Tool/Program.cs | 1 + src/NuGet.Updater/Entities/UpdaterParameters.cs | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6249c10..0164d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added support for multiple target versions - Added support for solution folders alongside sln files - Added better error handling +- Added support for the "strict" parameter in the .Net Core app ### Changed - Replaced package owner with package author diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 26c0724..9bfcac1 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -34,6 +34,7 @@ public static async Task Main(string[] args) { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Sources.Add(UpdaterSource.NuGetOrg)) }, { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, + { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, }; _isParameterSet = false; diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index f9b8fe1..70acab7 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -17,7 +17,7 @@ public class UpdaterParameters /// /// Gets or sets the versions to update to (stable, dev, beta, etc.), in order of priority. /// - public ICollection TargetVersions { get; set; } = new List { "stable" }; + public ICollection TargetVersions { get; set; } = new[] { "stable" }; /// /// Gets or sets a value indicating whether the version should exactly match the target version. @@ -42,12 +42,12 @@ public class UpdaterParameters /// /// Gets or sets a list of packages to ignore. /// - public ICollection PackagesToIgnore { get; set; } + public ICollection PackagesToIgnore { get; set; } = new string[0]; /// /// Gets or sets a list of packages to update; all packages found will be updated if nothing is specified. /// - public ICollection PackagesToUpdate { get; set; } + public ICollection PackagesToUpdate { get; set; } = new string[0]; /// /// Gets or sets the name of the author of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. From fb4be741278219065251ced294124d0d9850e6bf Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 28 Oct 2019 17:09:30 -0400 Subject: [PATCH 121/201] Updated MSBuild.Extras --- src/global.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/global.json b/src/global.json index 69c1890..ed04889 100644 --- a/src/global.json +++ b/src/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "1.6.68" + "MSBuild.Sdk.Extras": "2.0.54" } -} \ No newline at end of file +} From 7ac14d2e6e40ecf4e67a99461697d7bf1c1246c1 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 28 Oct 2019 16:39:12 -0400 Subject: [PATCH 122/201] Updated changelog for version 1.0 --- CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0164d5b..974dd3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## Version 1.0 + ### Added - Added aliases for .Net Core application - Added support for multiple target versions @@ -19,12 +21,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Updated sources handling to add support for any number of public or private package sources (it was previously limited to NuGet.org and any number of private package sources - Set the current directory as the solution root if none is specified in the .Net Core application -### Deprecated - ### Removed - Removed superfluous arguments - Removed code used to update project.json files - -### Fixed - -### Security From cecde34800cf1c00bee6b19f32c97b9dec36e7ea Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 25 Oct 2019 15:04:18 -0400 Subject: [PATCH 123/201] +semver: breaking - Re-organized code structure --- src/NuGet.Shared/Entities/FeedVersion.cs | 25 +++ .../Entities/FileType.cs} | 6 +- src/NuGet.Shared/Entities/IPackageFeed.cs | 16 ++ src/NuGet.Shared/Entities/PackageFeed.cs | 97 +++++++++++ src/NuGet.Shared/Entities/PackageReference.cs | 37 +++++ .../Extensions/DictionaryExtensions.cs | 2 +- .../Extensions/FeedVersionExtensions.cs} | 8 +- .../Extensions/FileTypeExtensions.cs | 30 ++++ .../Extensions/NuGetVersionExtensions.cs | 2 +- .../PackageSearchMetadataExtensions.cs | 15 ++ .../Extensions/PackageSourceExtensions.cs | 33 ++++ .../Extensions/XmlDocumentExtensions.cs | 149 +++++++++++++++++ .../Helpers/FileHelper.Net.cs | 4 +- .../Helpers/FileHelper.UAP.cs | 4 +- .../Helpers/PackageHelper.cs | 4 +- .../Helpers/SolutionHelper.cs | 61 +++---- src/NuGet.Shared/Log/ConsoleLogger.cs | 37 +++++ .../Log/SimpleTextWriter.cs | 2 +- src/NuGet.Shared/NuGet.Shared.projitems | 31 ++++ src/NuGet.Shared/NuGet.Shared.shproj | 13 ++ .../Entities/TestPackageFeed.cs | 36 +++++ .../Entities/TestPackageSource.cs | 35 ---- src/NuGet.Updater.Tests/NuGetUpdaterTests.cs | 9 +- .../PackageReferenceTests.cs | 63 ++++++++ .../SolutionHelperTests.cs | 6 +- .../UpdaterPackagePackageTests.cs | 46 ------ src/NuGet.Updater.Tool/Program.cs | 49 ++---- .../Properties/launchSettings.json | 8 - src/NuGet.Updater.sln | 13 ++ src/NuGet.Updater/Entities/IUpdaterSource.cs | 14 -- .../Entities/PackageReference.cs | 27 ---- src/NuGet.Updater/Entities/UpdaterPackage.cs | 39 +---- .../Entities/UpdaterParameters.cs | 13 +- src/NuGet.Updater/Entities/UpdaterSource.cs | 51 ------ src/NuGet.Updater/Entities/UpdaterVersion.cs | 25 --- .../Extensions/PackageReferenceExtensions.cs | 71 +++++++++ .../PackageSearchMetadataExtensions.cs | 24 --- .../Extensions/PackageSourceExtensions.cs | 62 -------- .../Extensions/UpdateTargetExtensions.cs | 30 ---- .../Extensions/UpdaterPackageExtensions.cs | 23 --- .../Extensions/UpdaterParametersExtension.cs | 16 +- .../Extensions/XmlDocumentExtensions.cs | 150 +----------------- src/NuGet.Updater/Log/UpdateOperation.cs | 5 +- .../Log/{Logger.cs => UpdaterLogger.cs} | 33 +++- src/NuGet.Updater/NuGet.Updater.csproj | 2 + src/NuGet.Updater/NuGetUpdater.cs | 71 +++------ 46 files changed, 823 insertions(+), 674 deletions(-) create mode 100644 src/NuGet.Shared/Entities/FeedVersion.cs rename src/{NuGet.Updater/Entities/UpdateTarget.cs => NuGet.Shared/Entities/FileType.cs} (86%) create mode 100644 src/NuGet.Shared/Entities/IPackageFeed.cs create mode 100644 src/NuGet.Shared/Entities/PackageFeed.cs create mode 100644 src/NuGet.Shared/Entities/PackageReference.cs rename src/{NuGet.Updater => NuGet.Shared}/Extensions/DictionaryExtensions.cs (94%) rename src/{NuGet.Updater/Extensions/UpdaterVersionExtensions.cs => NuGet.Shared/Extensions/FeedVersionExtensions.cs} (85%) create mode 100644 src/NuGet.Shared/Extensions/FileTypeExtensions.cs rename src/{NuGet.Updater => NuGet.Shared}/Extensions/NuGetVersionExtensions.cs (99%) create mode 100644 src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs create mode 100644 src/NuGet.Shared/Extensions/PackageSourceExtensions.cs create mode 100644 src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs rename src/{NuGet.Updater => NuGet.Shared}/Helpers/FileHelper.Net.cs (95%) rename src/{NuGet.Updater => NuGet.Shared}/Helpers/FileHelper.UAP.cs (96%) rename src/{NuGet.Updater => NuGet.Shared}/Helpers/PackageHelper.cs (89%) rename src/{NuGet.Updater => NuGet.Shared}/Helpers/SolutionHelper.cs (67%) create mode 100644 src/NuGet.Shared/Log/ConsoleLogger.cs rename src/{NuGet.Updater => NuGet.Shared}/Log/SimpleTextWriter.cs (94%) create mode 100644 src/NuGet.Shared/NuGet.Shared.projitems create mode 100644 src/NuGet.Shared/NuGet.Shared.shproj create mode 100644 src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs delete mode 100644 src/NuGet.Updater.Tests/Entities/TestPackageSource.cs create mode 100644 src/NuGet.Updater.Tests/PackageReferenceTests.cs delete mode 100644 src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs delete mode 100644 src/NuGet.Updater.Tool/Properties/launchSettings.json delete mode 100644 src/NuGet.Updater/Entities/IUpdaterSource.cs delete mode 100644 src/NuGet.Updater/Entities/PackageReference.cs delete mode 100644 src/NuGet.Updater/Entities/UpdaterSource.cs delete mode 100644 src/NuGet.Updater/Entities/UpdaterVersion.cs create mode 100644 src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/PackageSourceExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs rename src/NuGet.Updater/Log/{Logger.cs => UpdaterLogger.cs} (72%) diff --git a/src/NuGet.Shared/Entities/FeedVersion.cs b/src/NuGet.Shared/Entities/FeedVersion.cs new file mode 100644 index 0000000..148b699 --- /dev/null +++ b/src/NuGet.Shared/Entities/FeedVersion.cs @@ -0,0 +1,25 @@ +using System; +using NuGet.Versioning; + +namespace NuGet.Shared.Entities +{ + public class FeedVersion : IComparable + { + internal FeedVersion(string version, Uri feedUri) + : this(new NuGetVersion(version), feedUri) + { + } + + public FeedVersion(NuGetVersion version, Uri feedUri) + { + Version = version; + FeedUri = feedUri; + } + + public NuGetVersion Version { get; } + + public Uri FeedUri { get; } + + public int CompareTo(FeedVersion other) => Version.CompareTo(other.Version); + } +} diff --git a/src/NuGet.Updater/Entities/UpdateTarget.cs b/src/NuGet.Shared/Entities/FileType.cs similarity index 86% rename from src/NuGet.Updater/Entities/UpdateTarget.cs rename to src/NuGet.Shared/Entities/FileType.cs index b0245f8..1cc1263 100644 --- a/src/NuGet.Updater/Entities/UpdateTarget.cs +++ b/src/NuGet.Shared/Entities/FileType.cs @@ -1,12 +1,12 @@ using System; -namespace NuGet.Updater.Entities +namespace NuGet.Shared.Entities { /// - /// The type of files to update. + /// The type of file containing a reference. /// [Flags] - public enum UpdateTarget + public enum FileType { /// /// No files will be updated. diff --git a/src/NuGet.Shared/Entities/IPackageFeed.cs b/src/NuGet.Shared/Entities/IPackageFeed.cs new file mode 100644 index 0000000..1eb7c9f --- /dev/null +++ b/src/NuGet.Shared/Entities/IPackageFeed.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; + +namespace NuGet.Shared.Entities +{ + public interface IPackageFeed + { + Uri Url { get; } + + bool IsPrivate { get; } + + Task GetPackageVersions(CancellationToken ct, PackageReference reference, string author = null, ILogger log = null); + } +} diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs new file mode 100644 index 0000000..bceef3b --- /dev/null +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -0,0 +1,97 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Shared.Extensions; +using Uno.Extensions; + +namespace NuGet.Shared.Entities +{ + public class PackageFeed : IPackageFeed + { + #region Static + private const char PackageFeedInputSeparator = '|'; + public static readonly PackageFeed NuGetOrg = new PackageFeed("https://api.nuget.org/v3/index.json"); + + public static PackageFeed FromString(string input) + { + var parts = input.Split(PackageFeedInputSeparator); + + return parts.Length == 1 + ? new PackageFeed(parts[0]) + : new PackageFeed(parts[0], parts[1]); + } + #endregion + + private readonly PackageSource _packageSource; + private readonly bool _isPrivate = false; + + private PackageFeed(string url) + : this(new PackageSource(url)) + { + } + + private PackageFeed(string url, string accessToken) + : this(GetPackageSource(url, accessToken), isPrivate: true) + { + } + + private PackageFeed(PackageSource source, bool isPrivate = false) + { + _packageSource = source; + IsPrivate = isPrivate; + } + + public Uri Url => _packageSource.SourceUri; + + public bool IsPrivate { get; } + + public async Task GetPackageVersions( + CancellationToken ct, + PackageReference reference, + string author = null, + ILogger log = null + ) + { + var logMessage = new StringBuilder(); + + logMessage.AppendLine($"Retrieving package {reference.Identity.Id} from {Url}"); + + var versions = await _packageSource.GetPackageVersions(ct, reference.Identity.Id); + + logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions" : "No versions found"); + + if(author.HasValue() && versions.Any()) + { + versions = versions.Where(m => m.HasAuthor(author)).ToArray(); + + logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions from {author}" : $"No versions from {author} found"); + } + + log?.LogInformation(logMessage.ToString()); + + return versions + .Cast() + .Select(m => new FeedVersion(m.Version, Url)) + .ToArray(); + } + + private static PackageSource GetPackageSource(string url, string accessToken) + { + var name = url.GetHashCode().ToStringInvariant(); + + return new PackageSource(url, name) + { +#if UAP + Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false), +#else + Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false, null), +#endif + }; + } + } +} diff --git a/src/NuGet.Shared/Entities/PackageReference.cs b/src/NuGet.Shared/Entities/PackageReference.cs new file mode 100644 index 0000000..668202e --- /dev/null +++ b/src/NuGet.Shared/Entities/PackageReference.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using NuGet.Packaging.Core; +using NuGet.Versioning; + +namespace NuGet.Shared.Entities +{ + public class PackageReference + { + internal PackageReference(string id, string version) + : this(new PackageIdentity(id, new NuGetVersion(version)), new Dictionary()) + { + } + + public PackageReference(string id, NuGetVersion version, string file, FileType type) + : this(new PackageIdentity(id, version), new Dictionary() { { type, new[] { file } } }) + { + } + + public PackageReference(PackageIdentity identity, Dictionary files) + { + Identity = identity; + Files = files; + } + + /// + /// Gets the identity of the package. + /// + public PackageIdentity Identity { get; } + + /// + /// Gets the locations where the refernce is present. + /// + public Dictionary Files { get; } + + public override string ToString() => Identity.ToString(); + } +} diff --git a/src/NuGet.Updater/Extensions/DictionaryExtensions.cs b/src/NuGet.Shared/Extensions/DictionaryExtensions.cs similarity index 94% rename from src/NuGet.Updater/Extensions/DictionaryExtensions.cs rename to src/NuGet.Shared/Extensions/DictionaryExtensions.cs index 5aff40a..1017c22 100644 --- a/src/NuGet.Updater/Extensions/DictionaryExtensions.cs +++ b/src/NuGet.Shared/Extensions/DictionaryExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace NuGet.Updater.Extensions +namespace NuGet.Shared.Extensions { public static class DictionaryExtensions { diff --git a/src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs b/src/NuGet.Shared/Extensions/FeedVersionExtensions.cs similarity index 85% rename from src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs rename to src/NuGet.Shared/Extensions/FeedVersionExtensions.cs index 3ee4692..a56d2ef 100644 --- a/src/NuGet.Updater/Extensions/UpdaterVersionExtensions.cs +++ b/src/NuGet.Shared/Extensions/FeedVersionExtensions.cs @@ -1,15 +1,15 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using NuGet.Updater.Entities; +using NuGet.Shared.Entities; using Uno.Extensions; -namespace NuGet.Updater.Extensions +namespace NuGet.Shared.Extensions { - public static class UpdaterVersionExtensions + public static class FeedVersionExtensions { public static bool IsMatchingVersion( - this UpdaterVersion version, + this FeedVersion version, string tag, bool isStrict ) diff --git a/src/NuGet.Shared/Extensions/FileTypeExtensions.cs b/src/NuGet.Shared/Extensions/FileTypeExtensions.cs new file mode 100644 index 0000000..aad5182 --- /dev/null +++ b/src/NuGet.Shared/Extensions/FileTypeExtensions.cs @@ -0,0 +1,30 @@ +using System.Linq; +using NuGet.Shared.Entities; + +namespace NuGet.Shared.Extensions +{ + public static class FileTypeExtensions + { + public static string GetDescription(this FileType target) + { + switch(target) + { + case FileType.Nuspec: + return ".nuspec"; + case FileType.Csproj: + return ".csproj"; + case FileType.DirectoryProps: + return "Directory.Build.targets"; + case FileType.DirectoryTargets: + return "Directory.Build.props"; + default: + return default; + } + } + + public static bool HasAnyFlag(this FileType target, params FileType[] others) + { + return others.Any(t => t.HasFlag(target)); + } + } +} diff --git a/src/NuGet.Updater/Extensions/NuGetVersionExtensions.cs b/src/NuGet.Shared/Extensions/NuGetVersionExtensions.cs similarity index 99% rename from src/NuGet.Updater/Extensions/NuGetVersionExtensions.cs rename to src/NuGet.Shared/Extensions/NuGetVersionExtensions.cs index f8a82ce..290a1a8 100644 --- a/src/NuGet.Updater/Extensions/NuGetVersionExtensions.cs +++ b/src/NuGet.Shared/Extensions/NuGetVersionExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using NuGet.Versioning; -namespace NuGet.Updater +namespace NuGet.Shared.Extensions { public static class NuGetVersionExtensions { diff --git a/src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs b/src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs new file mode 100644 index 0000000..05c01d7 --- /dev/null +++ b/src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Linq; +using NuGet.Protocol.Core.Types; + +namespace NuGet.Shared.Extensions +{ + public static class PackageSearchMetadataExtensions + { + public static bool HasAuthor(this IPackageSearchMetadata metadata, string author) + => metadata + ?.Authors + .Split(',') + .Any(a => a.Equals(author, StringComparison.OrdinalIgnoreCase)) ?? false; + } +} diff --git a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs new file mode 100644 index 0000000..b34c12d --- /dev/null +++ b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; + +namespace NuGet.Shared.Extensions +{ + public static class PackageSourceExtensions + { + public static async Task GetPackageVersions( + this PackageSource source, + CancellationToken ct, + string packageId + ) + { + var repositoryProvider = new SourceRepositoryProvider( + Settings.LoadDefaultSettings(null), + Repository.Provider.GetCoreV3() + ); + + var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); + + var packageMetadataResource = await repository.GetResourceAsync(ct); + + var versions = await packageMetadataResource.GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, NullLogger.Instance, ct); + + return versions.ToArray(); + } + } +} diff --git a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs new file mode 100644 index 0000000..57f8c91 --- /dev/null +++ b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Uno.Extensions; + +#if UAP +using System.Text.RegularExpressions; +using Windows.Data.Xml.Dom; +using Windows.Storage; +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; +using XmlNode = Windows.Data.Xml.Dom.IXmlNode; +#else +using XmlDocument = System.Xml.XmlDocument; +using XmlElement = System.Xml.XmlElement; +using XmlNode = System.Xml.XmlNode; +#endif + +namespace NuGet.Shared.Extensions +{ + public static class XmlDocumentExtensions + { + /// + /// Retrieves the PackageReferences from the given XmlDocument. If a package is present multiple time, only the first version will be returned. + /// + /// + /// A Dictionary where the key is the id of a package and the value its version. + public static Dictionary GetPackageReferences(this XmlDocument document) + { + var references = new Dictionary(); + + var packageReferences = document.SelectElements("PackageReference"); + var dotnetCliReferences = document.SelectElements("DotNetCliToolReference"); + + foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) + { + var packageId = new[] { "Include", "Update", "Remove" } + .Select(packageReference.GetAttribute) + .FirstOrDefault(x => !string.IsNullOrEmpty(x)); + var packageVersion = packageReference.GetAttribute("Version"); + + if(packageVersion.HasValue()) + { + references.TryAdd(packageId, packageReference.GetAttribute("Version")); + } + else + { + var node = packageReference.SelectNode("Version"); + if(node != null) + { + references.TryAdd(packageId, node.InnerText); + } + } + } + + return references; + } + + /// + /// Retrieves the dependency elements from the given XmlDocument. If a package is present multiple time, only the first version will be returned. + /// + /// + /// A Dictionary where the key is the id of a package and the value its version. + public static Dictionary GetDependencies(this XmlDocument document) + => document + .SelectElements("dependency") + .ToDictionary(dependency => dependency.GetAttribute("id"), dependency => dependency.GetAttribute("version")); + + #region Utilities + + /// + /// Loads an XmlDocument from the given path. + /// + /// + /// + /// + public static async Task LoadDocument(this string path, CancellationToken ct) + { +#if UAP + var file = await StorageFile + .GetFileFromPathAsync(path) + .AsTask(ct); + + var document = await XmlDocument + .LoadFromFileAsync(file, new XmlLoadSettings { ElementContentWhiteSpace = true }) + .AsTask(ct); +#else + var document = new XmlDocument() + { + PreserveWhitespace = true, + }; + + document.Load(path); +#endif + + return document; + } + + /// + /// Save the given XmlDocument at the given path. + /// + /// + /// + /// + /// + public static async Task Save(this XmlDocument document, CancellationToken ct, string path) + { +#if UAP + var xml = document.GetXml(); + + xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding") + ? x.Result("$1${declaration} encoding=\"utf-8\"$2") // restore encoding declaration that is stripped by `GetXml` + : x.Value + ); + xml = Regex.Replace(xml, "(\\?>)(<)", "$1\n$2"); // xml declaration should follow by a new line + xml = Regex.Replace(xml, "([^ ])(/>)", "$1 $2"); // self-enclosing tag should end with a space + + await FileIO.WriteTextAsync(await StorageFile.GetFileFromPathAsync(path).AsTask(ct), xml); +#else + document.Save(path); +#endif + } + + /// + /// Select the XmlElements matching the given element name in the XmlDocument. + /// + /// + /// Name of the XML tags to look for. + /// Addtional xpath filter to apply. + /// + public static IEnumerable SelectElements(this XmlDocument document, string elementName, string filter = null) => document + .SelectNodes($"//*[local-name() = '{elementName}']{filter}") //Using local-name to avoid having to deal with namespaces + .OfType(); + + /// + /// Select the first child node with the given of an element. + /// + /// + /// + /// + public static XmlNode SelectNode(this XmlElement element, string name) => element + .ChildNodes + .OfType() + .FirstOrDefault(e => e.LocalName.ToString().Equals(name, StringComparison.OrdinalIgnoreCase)); + #endregion + } +} diff --git a/src/NuGet.Updater/Helpers/FileHelper.Net.cs b/src/NuGet.Shared/Helpers/FileHelper.Net.cs similarity index 95% rename from src/NuGet.Updater/Helpers/FileHelper.Net.cs rename to src/NuGet.Shared/Helpers/FileHelper.Net.cs index fe075ec..3e45bb2 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Shared/Helpers/FileHelper.Net.cs @@ -5,9 +5,9 @@ using System.Threading; using System.Threading.Tasks; -namespace NuGet.Updater.Helpers +namespace NuGet.Shared.Helpers { - partial class FileHelper + internal class FileHelper { public static void LogToFile(string outputFilePath, IEnumerable log) { diff --git a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs b/src/NuGet.Shared/Helpers/FileHelper.UAP.cs similarity index 96% rename from src/NuGet.Updater/Helpers/FileHelper.UAP.cs rename to src/NuGet.Shared/Helpers/FileHelper.UAP.cs index cfcaba3..d43d277 100644 --- a/src/NuGet.Updater/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Shared/Helpers/FileHelper.UAP.cs @@ -7,12 +7,12 @@ using Windows.Storage; using Windows.Storage.Search; -namespace NuGet.Updater.Helpers +namespace NuGet.Shared.Helpers { /// /// UAP-Specific methods. /// - partial class FileHelper + internal class FileHelper { public static void LogToFile(string outputFilePath, IEnumerable log) { diff --git a/src/NuGet.Updater/Helpers/PackageHelper.cs b/src/NuGet.Shared/Helpers/PackageHelper.cs similarity index 89% rename from src/NuGet.Updater/Helpers/PackageHelper.cs rename to src/NuGet.Shared/Helpers/PackageHelper.cs index 05612cd..470e5db 100644 --- a/src/NuGet.Updater/Helpers/PackageHelper.cs +++ b/src/NuGet.Shared/Helpers/PackageHelper.cs @@ -11,14 +11,14 @@ internal static class PackageHelper public static string GetUrl(string packageId, NuGetVersion version, Uri feedUri) { - if(feedUri.AbsoluteUri.StartsWith("https://api.nuget.org")) + if(feedUri.AbsoluteUri.StartsWith("https://api.nuget.org", StringComparison.OrdinalIgnoreCase)) { return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; } var pattern = LegacyAzureArtifactsFeedUrlPattern; - if(feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com")) + if(feedUri.AbsoluteUri.StartsWith("https://pkgs.dev.azure.com", StringComparison.OrdinalIgnoreCase)) { pattern = AzureArtifactsFeedUrlPattern; } diff --git a/src/NuGet.Updater/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs similarity index 67% rename from src/NuGet.Updater/Helpers/SolutionHelper.cs rename to src/NuGet.Shared/Helpers/SolutionHelper.cs index d8f7c90..2add91a 100644 --- a/src/NuGet.Updater/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -1,75 +1,76 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; +using NuGet.Common; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; +using NuGet.Versioning; using Uno.Extensions; -namespace NuGet.Updater.Helpers +namespace NuGet.Shared.Helpers { /// /// Shared solution helper methods. /// - public static partial class SolutionHelper + internal static partial class SolutionHelper { internal static async Task GetPackageReferences( CancellationToken ct, string solutionPath, - UpdateTarget updateTarget, - Logger log = null + FileType fileType, + ILogger log = null ) { - log?.Write($"Retrieving references from files in {solutionPath}"); + log?.LogInformation($"Retrieving references from files in {solutionPath}"); var packages = new List(); - if(updateTarget.HasFlag(UpdateTarget.Csproj)) + if(fileType.HasFlag(FileType.Csproj)) { foreach(var f in await GetProjectFiles(ct, solutionPath, log)) { - packages.AddRange(await GetFileReferences(ct, f, UpdateTarget.Csproj)); + packages.AddRange(await GetFileReferences(ct, f, FileType.Csproj)); } } - if(updateTarget.HasFlag(UpdateTarget.DirectoryProps)) + if(fileType.HasFlag(FileType.DirectoryProps)) { - const UpdateTarget currentTarget = UpdateTarget.DirectoryProps; + const FileType currentTarget = FileType.DirectoryProps; var file = await GetDirectoryFile(ct, solutionPath, currentTarget, log); packages.AddRange(await GetFileReferences(ct, file, currentTarget)); } - if(updateTarget.HasFlag(UpdateTarget.DirectoryTargets)) + if(fileType.HasFlag(FileType.DirectoryTargets)) { - const UpdateTarget currentTarget = UpdateTarget.DirectoryTargets; + const FileType currentTarget = FileType.DirectoryTargets; var file = await GetDirectoryFile(ct, solutionPath, currentTarget, log); packages.AddRange(await GetFileReferences(ct, file, currentTarget)); } - if(updateTarget.HasFlag(UpdateTarget.Nuspec)) + if(fileType.HasFlag(FileType.Nuspec)) { foreach(var f in await GetNuspecFiles(ct, solutionPath, log)) { - packages.AddRange(await GetFileReferences(ct, f, UpdateTarget.Nuspec)); + packages.AddRange(await GetFileReferences(ct, f, FileType.Nuspec)); } } return packages - .GroupBy(p => p.Id) + .GroupBy(p => p.Identity) .Select(g => new PackageReference( g.Key, - g.Select(p => p.Version).OrderByDescending(v => v).FirstOrDefault(), g.SelectMany(p => p.Files).GroupBy(f => f.Key).ToDictionary(f => f.Key, f => f.SelectMany(x => x.Value).Distinct().ToArray()) )) .ToArray(); } - private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, Logger log = null) + private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, ILogger log = null) { var files = new string[0]; @@ -89,14 +90,14 @@ private static async Task GetProjectFiles(CancellationToken ct, string .ToArray(); } - log?.Write($"Found {files.Length} csproj files"); + log?.LogInformation($"Found {files.Length} csproj files"); return files; } //To improve: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019#search-scope //The file should be looked for at all levels - private static async Task GetDirectoryFile(CancellationToken ct, string solutionPath, UpdateTarget target, Logger log = null) + private static async Task GetDirectoryFile(CancellationToken ct, string solutionPath, FileType target, ILogger log = null) { string file; @@ -114,14 +115,14 @@ private static async Task GetDirectoryFile(CancellationToken ct, string if(file != null && File.Exists(file)) { - log?.Write($"Found {target.GetDescription()}"); + log?.LogInformation($"Found {target.GetDescription()}"); return file; } return null; } - private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, Logger log = null) + private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, ILogger log = null) { string solutionFolder; @@ -139,32 +140,32 @@ private static async Task GetNuspecFiles(CancellationToken ct, string //Nuspec files are generated in obj when using the new csproj format files = files.Where(f => !f.Contains("\\obj\\")).ToArray(); - log?.Write($"Found {files.Length} nuspec files"); + log?.LogInformation($"Found {files.Length} nuspec files"); return files; } - private static async Task GetFileReferences(CancellationToken ct, string file, UpdateTarget target) + private static async Task GetFileReferences(CancellationToken ct, string file, FileType target) { if(file.IsNullOrEmpty()) { - return new PackageReference[0]; + return Array.Empty(); } var document = await file.LoadDocument(ct); var references = new Dictionary(); - if(target.HasAnyFlag(UpdateTarget.Csproj, UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets)) + if(target.HasAnyFlag(FileType.Csproj, FileType.DirectoryProps, FileType.DirectoryTargets)) { references = document.GetPackageReferences(); } - else if(target.HasFlag(UpdateTarget.Nuspec)) + else if(target.HasFlag(FileType.Nuspec)) { references = document.GetDependencies(); } return references - .Select(g => new PackageReference(g.Key, g.Value, file, target)) + .Select(g => new PackageReference(g.Key, new NuGetVersion(g.Value), file, target)) .ToArray(); } } diff --git a/src/NuGet.Shared/Log/ConsoleLogger.cs b/src/NuGet.Shared/Log/ConsoleLogger.cs new file mode 100644 index 0000000..b0ed3e9 --- /dev/null +++ b/src/NuGet.Shared/Log/ConsoleLogger.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using NuGet.Common; + +namespace NuGet.Shared.Entities +{ + public class ConsoleLogger : ILogger + { + public static readonly ConsoleLogger Default = new ConsoleLogger(); + + private ConsoleLogger() + { + } + + public void Log(LogLevel level, string data) => Log(new LogMessage(level, data)); + + public void Log(ILogMessage message) => Console.WriteLine(message.Message); + + public async Task LogAsync(LogLevel level, string data) => Log(level, data); + + public async Task LogAsync(ILogMessage message) => Log(message); + + public void LogDebug(string data) => Log(LogLevel.Debug, data); + + public void LogError(string data) => Log(LogLevel.Error, data); + + public void LogInformation(string data) => Log(LogLevel.Information, data); + + public void LogInformationSummary(string data) => Log(LogLevel.Information, data); + + public void LogMinimal(string data) => Log(LogLevel.Minimal, data); + + public void LogVerbose(string data) => Log(LogLevel.Verbose, data); + + public void LogWarning(string data) => Log(LogLevel.Warning, data); + } +} diff --git a/src/NuGet.Updater/Log/SimpleTextWriter.cs b/src/NuGet.Shared/Log/SimpleTextWriter.cs similarity index 94% rename from src/NuGet.Updater/Log/SimpleTextWriter.cs rename to src/NuGet.Shared/Log/SimpleTextWriter.cs index 7b18731..e1cbf6c 100644 --- a/src/NuGet.Updater/Log/SimpleTextWriter.cs +++ b/src/NuGet.Shared/Log/SimpleTextWriter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text; -namespace NuGet.Updater.Log +namespace NuGet.Shared.Log { public class SimpleTextWriter : TextWriter { diff --git a/src/NuGet.Shared/NuGet.Shared.projitems b/src/NuGet.Shared/NuGet.Shared.projitems new file mode 100644 index 0000000..75ff87a --- /dev/null +++ b/src/NuGet.Shared/NuGet.Shared.projitems @@ -0,0 +1,31 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 3890cca3-b7cf-450b-a6cd-4a4106b0008c + + + NuGet.Shared + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NuGet.Shared/NuGet.Shared.shproj b/src/NuGet.Shared/NuGet.Shared.shproj new file mode 100644 index 0000000..7290aa3 --- /dev/null +++ b/src/NuGet.Shared/NuGet.Shared.shproj @@ -0,0 +1,13 @@ + + + + 3890cca3-b7cf-450b-a6cd-4a4106b0008c + 14.0 + + + + + + + + diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs b/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs new file mode 100644 index 0000000..162698e --- /dev/null +++ b/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Shared.Entities; + +namespace NuGet.Updater.Tests.Entities +{ + public class TestPackageFeed : IPackageFeed + { + private readonly Dictionary _packages; + + public TestPackageFeed(Uri url, Dictionary packages) + { + Url = url; + IsPrivate = true; + _packages = packages; + } + + public Uri Url { get; } + + public bool IsPrivate { get; } + + public async Task GetPackageVersions( + CancellationToken ct, + PackageReference reference, + string author = null, + ILogger log = null + ) => _packages + .GetValueOrDefault(reference.Identity.Id) + ?.Select(v => new FeedVersion(v, Url)) + .ToArray() ?? new FeedVersion[0]; + } +} diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs b/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs deleted file mode 100644 index 8264adc..0000000 --- a/src/NuGet.Updater.Tests/Entities/TestPackageSource.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Updater.Entities; -using NuGet.Updater.Log; - -namespace NuGet.Updater.Tests.Entities -{ - public class TestPackageSource : IUpdaterSource - { - private readonly Dictionary _packages; - - public TestPackageSource(Uri url, Dictionary packages) - { - Url = url; - _packages = packages; - } - - public Uri Url { get; } - - public async Task GetPackage(CancellationToken ct, PackageReference reference, string author, Logger log = null) - { - var packageId = reference.Id; - - var versions = _packages - .GetValueOrDefault(reference.Id) - ?.Select(v => new UpdaterVersion(v, Url)) - .ToArray() ?? new UpdaterVersion[0]; - - return new UpdaterPackage(reference, versions); - } - } -} diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs index 056deb5..fae104e 100644 --- a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs +++ b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Shared.Entities; using NuGet.Updater.Entities; using NuGet.Updater.Log; using NuGet.Updater.Tests.Entities; @@ -20,7 +21,7 @@ public class NuGetUpdaterTests {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, }; - private static readonly TestPackageSource TestSource = new TestPackageSource(new Uri("http://localhost"), TestPackages); + private static readonly TestPackageFeed TestFeed = new TestPackageFeed(new Uri("http://localhost"), TestPackages); [TestMethod] public async Task GivenUnspecifiedTarget_NoUpdateIsMade() @@ -28,12 +29,12 @@ public async Task GivenUnspecifiedTarget_NoUpdateIsMade() var parameters = new UpdaterParameters { SolutionRoot = "MySolution.sln", - UpdateTarget = UpdateTarget.Unspecified, + UpdateTarget = FileType.Unspecified, TargetVersions = new[] { "stable" }, - Sources = new List { TestSource }, + Feeds = new List { TestFeed }, }; - var logger = new Logger(Console.Out); + var logger = new UpdaterLogger(Console.Out); var updater = new NuGetUpdater(parameters, logger); diff --git a/src/NuGet.Updater.Tests/PackageReferenceTests.cs b/src/NuGet.Updater.Tests/PackageReferenceTests.cs new file mode 100644 index 0000000..4ba2916 --- /dev/null +++ b/src/NuGet.Updater.Tests/PackageReferenceTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Shared.Entities; +using NuGet.Updater.Entities; +using NuGet.Updater.Extensions; +using NuGet.Updater.Tests.Entities; + +namespace NuGet.Updater.Tests +{ + [TestClass] + public class PackageReferenceTests + { + private static readonly Uri TestFeedUri = new Uri("http://localhost"); + + private static readonly Dictionary TestPackages = new Dictionary + { + {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, + }; + + private static readonly TestPackageFeed TestFeed = new TestPackageFeed(TestFeedUri, TestPackages); + + [TestMethod] + public async Task GivenPackageWithMatchingVersion_VersionIsFound() + { + var parameters = new UpdaterParameters + { + TargetVersions = new[] { "beta" }, + Feeds = new[] { TestFeed }, + }; + + var packageVersion = "1.0-beta.1"; + var packageId = "nventive.NuGet.Updater"; + + var reference = new PackageReference(packageId, packageVersion); + + var version = await reference.GetLatestVersion(CancellationToken.None, parameters); + + Assert.IsNotNull(version); + Assert.AreEqual(version.Version.OriginalVersion, packageVersion); + } + + [TestMethod] + public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() + { + var parameters = new UpdaterParameters + { + TargetVersions = new[] { "stable" }, + }; + + var packageVersion = "1.0-beta.1"; + var packageId = "nventive.NuGet.Updater"; + + var reference = new PackageReference(packageId, packageVersion); + + var version = await reference.GetLatestVersion(CancellationToken.None, parameters); + + Assert.IsNull(version); + } + } +} diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs index f732a2d..9669f57 100644 --- a/src/NuGet.Updater.Tests/SolutionHelperTests.cs +++ b/src/NuGet.Updater.Tests/SolutionHelperTests.cs @@ -2,8 +2,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Updater.Entities; -using NuGet.Updater.Helpers; +using NuGet.Shared.Entities; +using NuGet.Shared.Helpers; namespace NuGet.Updater.Tests { @@ -15,7 +15,7 @@ public async Task GivenSolution_PackageReferencesAreFound() { var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; - var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution, UpdateTarget.Csproj); + var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution, FileType.Csproj); Assert.IsTrue(references.Any()); } diff --git a/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs b/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs deleted file mode 100644 index 24a3d21..0000000 --- a/src/NuGet.Updater.Tests/UpdaterPackagePackageTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; - -namespace NuGet.Updater.Tests -{ - [TestClass] - public class UpdaterPackagePackageTests - { - private static readonly Uri TestFeedUri = new Uri("http://localhost"); - - [TestMethod] - public async Task GivenPackageWithMatchingVersion_VersionIsFound() - { - var parameters = new UpdaterParameters - { - TargetVersions = new[] { "beta" }, - }; - - var packageVersion = "1.0-beta.1"; - var package = new UpdaterPackage("nventive.NuGet.Updater", TestFeedUri, packageVersion); - - var version = package.GetLatestVersion(parameters); - - Assert.IsNotNull(version); - Assert.AreEqual(version.Version.OriginalVersion, packageVersion); - } - - [TestMethod] - public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() - { - var parameters = new UpdaterParameters - { - TargetVersions = new[] { "stable" }, - }; - - var package = new UpdaterPackage("nventive.NuGet.Updater", TestFeedUri, "1.0-beta.1"); - - var version = package.GetLatestVersion(parameters); - - Assert.IsNull(version); - } - } -} diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 9bfcac1..434f026 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Mono.Options; +using NuGet.Shared.Entities; using NuGet.Updater.Entities; namespace NuGet.Updater.Tool @@ -22,27 +23,27 @@ public static async Task Main(string[] args) string summaryFile = default; var options = new OptionSet - { - { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.SolutionRoot = s) }, - { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => AddSource(s) }, - { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, - { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, - { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.PackagesToUpdate.Add(s)) }, - { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.PackageAuthor = s)}, - { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => summaryFile = s }, - { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, - { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Sources.Add(UpdaterSource.NuGetOrg)) }, - { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, - { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, - }; + { + { "help|h", "Displays this help screen", s => isHelp = true }, + { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.SolutionRoot = s) }, + { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => Set(p => p.Feeds.Add(PackageFeed.FromString(s)))}, + { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, + { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, + { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.PackagesToUpdate.Add(s)) }, + { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.PackageAuthor = s)}, + { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => summaryFile = s }, + { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, + { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Feeds.Add(PackageFeed.NuGetOrg)) }, + { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, + { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, + }; _isParameterSet = false; _parameters = new UpdaterParameters { SolutionRoot = Environment.CurrentDirectory, - UpdateTarget = UpdateTarget.All, - Sources = new List(), + UpdateTarget = FileType.All, + Feeds = new List(), PackagesToIgnore = new List(), PackagesToUpdate = new List(), }; @@ -74,22 +75,6 @@ private static void Set(Action set) _isParameterSet = true; } - private static void AddSource(string value) - { - const char separator = '|'; - if(value.Contains(separator)) - { - var parts = value.Split(separator); - _parameters.Sources.Add(new UpdaterSource(parts[0], parts[1])); - } - else - { - _parameters.Sources.Add(new UpdaterSource(value)); - } - - _isParameterSet = true; - } - private static string[] GetList(string value) => !string.IsNullOrEmpty(value) ? value.Split(",;".ToArray(), StringSplitOptions.RemoveEmptyEntries) : null; } } diff --git a/src/NuGet.Updater.Tool/Properties/launchSettings.json b/src/NuGet.Updater.Tool/Properties/launchSettings.json deleted file mode 100644 index c96edea..0000000 --- a/src/NuGet.Updater.Tool/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "NuGet.Updater.Tool": { - "commandName": "Project", - "commandLineArgs": "-v=a -v=b -v=c -u=toto -u=tata -i=zaza -i=zozo -f=nuget.org -f=dev.azure.com" - } - } -} diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index cd85ee5..64178e3 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -15,14 +15,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\CHANGELOG.md = ..\CHANGELOG.md ..\CODE_OF_CONDUCT.md = ..\CODE_OF_CONDUCT.md ..\CONTRIBUTING.md = ..\CONTRIBUTING.md + Directory.Build.props = Directory.Build.props ..\gitversion.yml = ..\gitversion.yml + global.json = global.json ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tests", "NuGet.Updater.Tests\NuGet.Updater.Tests.csproj", "{D240ABFD-6A48-4E4B-A946-9EBF9840FADA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader", "NuGet.Downloader\NuGet.Downloader.csproj", "{912018BD-D988-4C90-B4EC-BA1C171207C3}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NuGet.Shared", "NuGet.Shared\NuGet.Shared.shproj", "{3890CCA3-B7CF-450B-A6CD-4A4106B0008C}" +EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + NuGet.Shared\NuGet.Shared.projitems*{3890cca3-b7cf-450b-a6cd-4a4106b0008c}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -40,6 +49,10 @@ Global {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Debug|Any CPU.Build.0 = Debug|Any CPU {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Release|Any CPU.ActiveCfg = Release|Any CPU {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Release|Any CPU.Build.0 = Release|Any CPU + {912018BD-D988-4C90-B4EC-BA1C171207C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {912018BD-D988-4C90-B4EC-BA1C171207C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {912018BD-D988-4C90-B4EC-BA1C171207C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {912018BD-D988-4C90-B4EC-BA1C171207C3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/NuGet.Updater/Entities/IUpdaterSource.cs b/src/NuGet.Updater/Entities/IUpdaterSource.cs deleted file mode 100644 index 9e1ade0..0000000 --- a/src/NuGet.Updater/Entities/IUpdaterSource.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Updater.Log; - -namespace NuGet.Updater.Entities -{ - public interface IUpdaterSource - { - Uri Url { get; } - - Task GetPackage(CancellationToken ct, PackageReference reference, string author, Logger log = null); - } -} diff --git a/src/NuGet.Updater/Entities/PackageReference.cs b/src/NuGet.Updater/Entities/PackageReference.cs deleted file mode 100644 index c96ad45..0000000 --- a/src/NuGet.Updater/Entities/PackageReference.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace NuGet.Updater.Entities -{ - public class PackageReference - { - public PackageReference(string id, string version, string file, UpdateTarget target) - : this(id, version, new Dictionary() { { target, new[] { file } } }) - { - } - - public PackageReference(string id, string version, Dictionary files) - { - Id = id; - Version = version; - Files = files; - } - - public string Id { get; } - - public string Version { get; } - - public Dictionary Files { get; } - - public override string ToString() => $"{Id} {Version}"; - } -} diff --git a/src/NuGet.Updater/Entities/UpdaterPackage.cs b/src/NuGet.Updater/Entities/UpdaterPackage.cs index 450334b..7ab9931 100644 --- a/src/NuGet.Updater/Entities/UpdaterPackage.cs +++ b/src/NuGet.Updater/Entities/UpdaterPackage.cs @@ -1,47 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using NuGet.Shared.Entities; namespace NuGet.Updater.Entities { public class UpdaterPackage { - /// - /// Creates a new instance of UpdaterPackage from an id, an Url and a list of version. Used for testing. - /// - /// - /// - /// - internal UpdaterPackage(string id, Uri sourceUri, params string[] versions) - { - PackageId = id; - AvailableVersions = versions?.Select(v => new UpdaterVersion(v, sourceUri)).ToArray(); - } - - public UpdaterPackage(PackageReference reference, IEnumerable availableVersions) - : this(reference) - { - AvailableVersions = availableVersions.ToArray(); - } - - public UpdaterPackage(PackageReference reference, UpdaterVersion latestVersion) - : this(reference) - { - LatestVersion = latestVersion; - } - - private UpdaterPackage(PackageReference reference) + public UpdaterPackage(PackageReference reference, FeedVersion version) { Reference = reference; - PackageId = reference.Id; + PackageId = reference.Identity.Id; + Version = version; } public string PackageId { get; } - public PackageReference Reference { get; set; } - - public UpdaterVersion LatestVersion { get; } + public PackageReference Reference { get; } - public UpdaterVersion[] AvailableVersions { get; } + public FeedVersion Version { get; } } } diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 70acab7..284a28e 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NuGet.Shared.Entities; namespace NuGet.Updater.Entities { @@ -10,14 +11,14 @@ public class UpdaterParameters public string SolutionRoot { get; set; } /// - /// Gets or sets a list of source to get packages from. + /// Gets or sets a list of feeds to get packages from. /// - public ICollection Sources { get; set; } + public ICollection Feeds { get; set; } /// /// Gets or sets the versions to update to (stable, dev, beta, etc.), in order of priority. /// - public ICollection TargetVersions { get; set; } = new[] { "stable" }; + public ICollection TargetVersions { get; set; } = new List { "stable" }; /// /// Gets or sets a value indicating whether the version should exactly match the target version. @@ -37,17 +38,17 @@ public class UpdaterParameters /// /// Gets or sets the type of files to update. /// - public UpdateTarget UpdateTarget { get; set; } + public FileType UpdateTarget { get; set; } /// /// Gets or sets a list of packages to ignore. /// - public ICollection PackagesToIgnore { get; set; } = new string[0]; + public ICollection PackagesToIgnore { get; set; } = new List(); /// /// Gets or sets a list of packages to update; all packages found will be updated if nothing is specified. /// - public ICollection PackagesToUpdate { get; set; } = new string[0]; + public ICollection PackagesToUpdate { get; set; } = new List(); /// /// Gets or sets the name of the author of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. diff --git a/src/NuGet.Updater/Entities/UpdaterSource.cs b/src/NuGet.Updater/Entities/UpdaterSource.cs deleted file mode 100644 index 46e6e58..0000000 --- a/src/NuGet.Updater/Entities/UpdaterSource.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Configuration; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; - -namespace NuGet.Updater.Entities -{ - public class UpdaterSource : IUpdaterSource - { - public static readonly UpdaterSource NuGetOrg = new UpdaterSource("https://api.nuget.org/v3/index.json"); - - private readonly PackageSource _packageSource; - private readonly bool _isPrivate = false; - - public UpdaterSource(string url) - { - _packageSource = new PackageSource(url); - } - - public UpdaterSource(string url, string accessToken) - { - _packageSource = GetPackageSource(url, accessToken); - _isPrivate = true; - } - - public Uri Url => _packageSource.SourceUri; - - public Task GetPackage( - CancellationToken ct, - PackageReference reference, - string author, - Logger log = null - ) => _packageSource.GetPackage(ct, reference, author: _isPrivate ? null : author, log); //Not filtering packages from private sources - - private static PackageSource GetPackageSource(string url, string accessToken) - { - var name = url.GetHashCode().ToString(); - - return new PackageSource(url, name) - { -#if UAP - Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false), -#else - Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false, null), -#endif - }; - } - } -} diff --git a/src/NuGet.Updater/Entities/UpdaterVersion.cs b/src/NuGet.Updater/Entities/UpdaterVersion.cs deleted file mode 100644 index a4e4b08..0000000 --- a/src/NuGet.Updater/Entities/UpdaterVersion.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using NuGet.Versioning; - -namespace NuGet.Updater.Entities -{ - public class UpdaterVersion : IComparable - { - internal UpdaterVersion(string version, Uri feedUri) - : this(new NuGetVersion(version), feedUri) - { - } - - public UpdaterVersion(NuGetVersion version, Uri feedUri) - { - Version = version; - FeedUri = feedUri; - } - - public NuGetVersion Version { get; } - - public Uri FeedUri { get; } - - public int CompareTo(UpdaterVersion other) => Version.CompareTo(other.Version); - } -} diff --git a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs new file mode 100644 index 0000000..c0161bd --- /dev/null +++ b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; +using NuGet.Updater.Entities; +using Uno.Extensions; + +#if UAP +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +#else +using XmlDocument = System.Xml.XmlDocument; +#endif + +namespace NuGet.Updater.Extensions +{ + public static class PackageReferenceExtensions + { + /// + /// Opens the XML files where package references were found. + /// + /// + /// + /// + public static async Task> OpenFiles( + this IEnumerable references, + CancellationToken ct + ) + { + var files = references + .SelectMany(r => r.Files) + .SelectMany(g => g.Value) + .Distinct(); + + var documents = new Dictionary(); + + foreach(var file in files) + { + documents.Add(file, await file.LoadDocument(ct)); + } + + return documents; + } + + public static async Task GetLatestVersion( + this PackageReference reference, + CancellationToken ct, + UpdaterParameters parameters, + ILogger log = null + ) + { + var availableVersions = await Task.WhenAll(parameters + .Feeds + .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor, log)) + ); + + var versionsPerTarget = availableVersions + .SelectMany(x => x) + .OrderByDescending(v => v) + .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) + .Where(g => g.Key.HasValue()); + + return versionsPerTarget + .Select(g => g.FirstOrDefault()) + .OrderByDescending(v => v.Version) + .FirstOrDefault(); + } + } +} diff --git a/src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs b/src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs deleted file mode 100644 index 544b7d7..0000000 --- a/src/NuGet.Updater/Extensions/PackageSearchMetadataExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Linq; -using NuGet.Protocol.Core.Types; -using Uno.Extensions; - -namespace NuGet.Updater.Extensions -{ - public static class PackageSearchMetadataExtensions - { - public static bool HasAuthor(this IPackageSearchMetadata metadata, string author) - { - var authors = metadata?.Authors; - - if(authors.IsNullOrEmpty()) - { - return false; - } - - return authors.Contains(",") - ? authors.Split(',').Any(a => a.Equals(author, StringComparison.OrdinalIgnoreCase)) - : authors.Equals(author, StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs b/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs deleted file mode 100644 index 0f5587a..0000000 --- a/src/NuGet.Updater/Extensions/PackageSourceExtensions.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Common; -using NuGet.Configuration; -using NuGet.Protocol; -using NuGet.Protocol.Core.Types; -using NuGet.Updater.Entities; -using NuGet.Updater.Log; -using Uno.Extensions; - -namespace NuGet.Updater.Extensions -{ - public static class PackageSourceExtensions - { - public static async Task GetPackage( - this PackageSource source, - CancellationToken ct, - PackageReference reference, - string author = null, - Logger log = null - ) - { - var logMessage = new StringBuilder(); - - var repositoryProvider = new SourceRepositoryProvider( - Settings.LoadDefaultSettings(null), - Repository.Provider.GetCoreV3() - ); - - var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); - - var packageId = reference.Id; - - logMessage.AppendLine($"Retrieving package {packageId} from {source.SourceUri}"); - - var packageMetadata = (await repository - .GetResource() - .GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, new NullLogger(), ct)) - .ToArray(); - - logMessage.AppendLine(packageMetadata.Length > 0 ? $"Found {packageMetadata.Length} versions" : "No versions found"); - - if(author.HasValue() && packageMetadata.Any()) - { - packageMetadata = packageMetadata.Where(m => m.HasAuthor(author)).ToArray(); - - logMessage.AppendLine(packageMetadata.Length > 0 ? $"Found {packageMetadata.Length} versions from {author}" : $"No versions from {author} found"); - } - - var versions = packageMetadata - .Cast() - .Select(m => new UpdaterVersion(m.Version, source.SourceUri)) - .ToArray(); - - log?.Write(logMessage.ToString()); - - return new UpdaterPackage(reference, versions); - } - } -} diff --git a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs b/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs deleted file mode 100644 index 01948b6..0000000 --- a/src/NuGet.Updater/Extensions/UpdateTargetExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using NuGet.Updater.Entities; - -namespace NuGet.Updater.Extensions -{ - public static class UpdateTargetExtensions - { - public static string GetDescription(this UpdateTarget target) - { - switch(target) - { - case UpdateTarget.Nuspec: - return ".nuspec"; - case UpdateTarget.Csproj: - return ".csproj"; - case UpdateTarget.DirectoryProps: - return "Directory.Build.targets"; - case UpdateTarget.DirectoryTargets: - return "Directory.Build.props"; - default: - return default; - } - } - - public static bool HasAnyFlag(this UpdateTarget target, params UpdateTarget[] others) - { - return others.Any(t => t.HasFlag(target)); - } - } -} diff --git a/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs b/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs deleted file mode 100644 index 4caf2af..0000000 --- a/src/NuGet.Updater/Extensions/UpdaterPackageExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; -using NuGet.Updater.Entities; -using Uno.Extensions; - -namespace NuGet.Updater.Extensions -{ - public static class UpdaterPackageExtensions - { - public static UpdaterVersion GetLatestVersion(this UpdaterPackage package, UpdaterParameters parameters) - { - var versionsPerTarget = package - .AvailableVersions - .OrderByDescending(v => v) - .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) - .Where(g => g.Key.HasValue()); - - return versionsPerTarget - .Select(g => g.FirstOrDefault()) - .OrderByDescending(v => v.Version) - .FirstOrDefault(); - } - } -} diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 32ac0f4..4dbb4de 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; using NuGet.Updater.Entities; using Uno.Extensions; @@ -12,10 +14,10 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters { yield return $"## Configuration"; - var files = parameters.UpdateTarget == UpdateTarget.All + var files = parameters.UpdateTarget == FileType.All ? string.Join(", ", Enum - .GetValues(typeof(UpdateTarget)) - .Cast() + .GetValues(typeof(FileType)) + .Cast() .Select(t => t.GetDescription()) .Trim() ) @@ -23,9 +25,9 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Update targeting {files} files under {parameters.SolutionRoot}"; - if(parameters.Sources?.Any() ?? false) + if(parameters.Feeds?.Any() ?? false) { - yield return $"- Using NuGet packages from {string.Join(", ", parameters.Sources.Select(s => s.Url))}"; + yield return $"- Using NuGet packages from {string.Join(", ", parameters.Feeds.Select(s => s.Url))}"; } if(parameters.PackageAuthor.HasValue()) @@ -58,9 +60,9 @@ public static UpdaterParameters Validate(this UpdaterParameters parameters) throw new InvalidOperationException("The solution root must be specified"); } - if(parameters.Sources.None()) + if(parameters.Feeds.None()) { - throw new InvalidOperationException("At least one NuGet source should be specified"); + throw new InvalidOperationException("At least one NuGet feed should be specified"); } return parameters; diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index 53ebfe5..c6605b1 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Updater.Entities; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; using NuGet.Updater.Log; using NuGet.Versioning; using Uno.Extensions; @@ -17,52 +15,12 @@ using XmlNode = Windows.Data.Xml.Dom.IXmlNode; #else using XmlDocument = System.Xml.XmlDocument; -using XmlElement = System.Xml.XmlElement; -using XmlNode = System.Xml.XmlNode; #endif namespace NuGet.Updater.Extensions { public static class XmlDocumentExtensions { - #region PackageReferences - - /// - /// Retrieves the PackageReferences from the given XmlDocument. If a package is present multiple time, only the first version will be returned. - /// - /// - /// A Dictionary where the key is the id of a package and the value its version. - public static Dictionary GetPackageReferences(this XmlDocument document) - { - var references = new Dictionary(); - - var packageReferences = document.SelectElements("PackageReference"); - var dotnetCliReferences = document.SelectElements("DotNetCliToolReference"); - - foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) - { - var packageId = new[] { "Include", "Update", "Remove" } - .Select(packageReference.GetAttribute) - .FirstOrDefault(x => !string.IsNullOrEmpty(x)); - var packageVersion = packageReference.GetAttribute("Version"); - - if(packageVersion.HasValue()) - { - references.TryAdd(packageId, packageReference.GetAttribute("Version")); - } - else - { - var node = packageReference.SelectNode("Version"); - if(node != null) - { - references.TryAdd(packageId, node.InnerText); - } - } - } - - return references; - } - /// /// Updates the PackageReferences with the given id in the given XmlDocument. /// @@ -75,7 +33,7 @@ public static Dictionary GetPackageReferences(this XmlDocument d public static UpdateOperation[] UpdatePackageReferences( this XmlDocument document, string packageId, - UpdaterVersion version, + FeedVersion version, string path, bool isDowngradeAllowed) { @@ -123,19 +81,6 @@ public static UpdateOperation[] UpdatePackageReferences( return operations.ToArray(); } -#endregion - -#region Nuspec dependencies - - /// - /// Retrieves the dependency elements from the given XmlDocument. If a package is present multiple time, only the first version will be returned. - /// - /// - /// A Dictionary where the key is the id of a package and the value its version. - public static Dictionary GetDependencies(this XmlDocument document) - => document - .SelectElements("dependency") - .ToDictionary(dependency => dependency.GetAttribute("id"), dependency => dependency.GetAttribute("version")); /// /// Updates the dependencies with the given id in the given XmlDocument. @@ -149,25 +94,25 @@ public static Dictionary GetDependencies(this XmlDocument docume public static UpdateOperation[] UpdateDependencies( this XmlDocument document, string packageId, - UpdaterVersion version, + FeedVersion version, string path, bool isDowngradeAllowed ) { var operations = new List(); - foreach (var node in document.SelectElements("dependency", $"[@id='{packageId}']")) + foreach(var node in document.SelectElements("dependency", $"[@id='{packageId}']")) { var versionNodeValue = node.GetAttribute("version"); // only nodes with explicit version, skip expansion. - if (!versionNodeValue.Contains("{")) + if(!versionNodeValue.Contains("{")) { var currentVersion = new NuGetVersion(versionNodeValue); var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - if (operation.IsUpdate) + if(operation.IsUpdate) { node.SetAttribute("version", version.Version.ToString()); } @@ -178,84 +123,5 @@ bool isDowngradeAllowed return operations.ToArray(); } -#endregion - -#region Utilities - - /// - /// Loads an XmlDocument from the given path. - /// - /// - /// - /// - public static async Task LoadDocument(this string path, CancellationToken ct) - { -#if UAP - var file = await StorageFile - .GetFileFromPathAsync(path) - .AsTask(ct); - - var document = await XmlDocument - .LoadFromFileAsync(file, new XmlLoadSettings { ElementContentWhiteSpace = true }) - .AsTask(ct); -#else - var document = new XmlDocument() - { - PreserveWhitespace = true, - }; - - document.Load(path); -#endif - - return document; - } - - /// - /// Save the given XmlDocument at the given path. - /// - /// - /// - /// - /// - public static async Task Save(this XmlDocument document, CancellationToken ct, string path) - { -#if UAP - var xml = document.GetXml(); - - xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding") - ? x.Result("$1${declaration} encoding=\"utf-8\"$2") // restore encoding declaration that is stripped by `GetXml` - : x.Value - ); - xml = Regex.Replace(xml, "(\\?>)(<)", "$1\n$2"); // xml declaration should follow by a new line - xml = Regex.Replace(xml, "([^ ])(/>)", "$1 $2"); // self-enclosing tag should end with a space - - await FileIO.WriteTextAsync(await StorageFile.GetFileFromPathAsync(path).AsTask(ct), xml); -#else - document.Save(path); -#endif - } - - /// - /// Select the XmlElements matching the given element name in the XmlDocument. - /// - /// - /// Name of the XML tags to look for. - /// Addtional xpath filter to apply. - /// - private static IEnumerable SelectElements(this XmlDocument document, string elementName, string filter = null) => document - .SelectNodes($"//*[local-name() = '{elementName}']{filter}") //Using local-name to avoid having to deal with namespaces - .OfType(); - - /// - /// Select the first child node with the given of an element. - /// - /// - /// - /// - private static XmlNode SelectNode(this XmlElement element, string name) => element - .ChildNodes - .OfType() - .FirstOrDefault(e => e.LocalName.ToString().Equals(name, StringComparison.OrdinalIgnoreCase)); -#endregion } } diff --git a/src/NuGet.Updater/Log/UpdateOperation.cs b/src/NuGet.Updater/Log/UpdateOperation.cs index 6af6568..a97d7ad 100644 --- a/src/NuGet.Updater/Log/UpdateOperation.cs +++ b/src/NuGet.Updater/Log/UpdateOperation.cs @@ -1,12 +1,13 @@ using System; -using NuGet.Updater.Entities; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; using NuGet.Versioning; namespace NuGet.Updater.Log { public class UpdateOperation { - public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, UpdaterVersion updatedVersion, string filePath) + public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, FeedVersion updatedVersion, string filePath) { Date = DateTimeOffset.Now; diff --git a/src/NuGet.Updater/Log/Logger.cs b/src/NuGet.Updater/Log/UpdaterLogger.cs similarity index 72% rename from src/NuGet.Updater/Log/Logger.cs rename to src/NuGet.Updater/Log/UpdaterLogger.cs index fae7ca4..5ef5aa8 100644 --- a/src/NuGet.Updater/Log/Logger.cs +++ b/src/NuGet.Updater/Log/UpdaterLogger.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Shared.Helpers; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; @@ -9,13 +12,13 @@ namespace NuGet.Updater.Log { - public class Logger + public class UpdaterLogger : ILogger { private readonly List _updateOperations = new List(); private readonly TextWriter _writer; private readonly string _summaryFilePath; - public Logger(TextWriter writer, string summaryFilePath = null) + public UpdaterLogger(TextWriter writer, string summaryFilePath = null) { _writer = writer #if DEBUG @@ -42,7 +45,7 @@ public void Write(IEnumerable operations) public void Write(UpdateOperation operation) { - Write(operation.GetLogMessage()); + LogInformation(operation.GetLogMessage()); _updateOperations.Add(operation); } @@ -118,5 +121,29 @@ private IEnumerable LogPackageOperations(IEnumerable op yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; } } + + #region ILogger + public void LogDebug(string data) => Log(LogLevel.Debug, data); + + public void LogVerbose(string data) => Log(LogLevel.Verbose, data); + + public void LogInformation(string data) => Log(LogLevel.Information, data); + + public void LogMinimal(string data) => Log(LogLevel.Minimal, data); + + public void LogWarning(string data) => Log(LogLevel.Warning, data); + + public void LogError(string data) => Log(LogLevel.Error, data); + + public void LogInformationSummary(string data) => Log(LogLevel.Information, data); + + public void Log(LogLevel level, string data) => Log(new LogMessage(level, data)); + + public async Task LogAsync(LogLevel level, string data) => Log(level, data); + + public void Log(ILogMessage message) => Write(message.Message); + + public async Task LogAsync(ILogMessage message) => Log(message); + #endregion } } diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj index 0badd97..8673a98 100644 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -52,4 +52,6 @@ + + \ No newline at end of file diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 0c37cdc..a1de204 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -3,9 +3,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; +using NuGet.Shared.Helpers; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; -using NuGet.Updater.Helpers; using NuGet.Updater.Log; #if UAP @@ -21,7 +23,7 @@ namespace NuGet.Updater public class NuGetUpdater { private readonly UpdaterParameters _parameters; - private readonly Logger _log; + private readonly UpdaterLogger _log; public static async Task UpdateAsync( CancellationToken ct, @@ -36,11 +38,11 @@ public static async Task UpdateAsync( } public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, string summaryOutputFilePath) - : this(parameters, new Logger(logWriter, summaryOutputFilePath)) + : this(parameters, new UpdaterLogger(logWriter, summaryOutputFilePath)) { } - internal NuGetUpdater(UpdaterParameters parameters, Logger log) + internal NuGetUpdater(UpdaterParameters parameters, UpdaterLogger log) { _parameters = parameters.Validate(); _log = log; @@ -52,11 +54,13 @@ public async Task UpdatePackages(CancellationToken ct) var packages = await GetPackages(ct); //Open all the files at once so we don't have to do it all the time - var documents = await OpenFiles(ct, packages); + var documents = await packages + .Select(p => p.Reference) + .OpenFiles(ct); foreach(var package in packages) { - var latestVersion = package.LatestVersion; + var latestVersion = package.Version; if(latestVersion == null) { @@ -89,60 +93,25 @@ internal async Task GetPackages(CancellationToken ct) _log.Write($"Found {references.Length} references"); _log.Write(""); - _log.Write($"Retrieving packages from {_parameters.Sources.Count} sources"); + _log.Write($"Retrieving packages from {_parameters.Feeds.Count} feeds"); - foreach(var reference in references.OrderBy(r => r.Id)) + foreach(var reference in references.OrderBy(r => r.Identity)) { - if(_parameters.PackagesToIgnore.Contains(reference.Id) || - (_parameters.PackagesToUpdate.Any() && !_parameters.PackagesToUpdate.Contains(reference.Id)) + if(_parameters.PackagesToIgnore.Contains(reference.Identity.Id) || + (_parameters.PackagesToUpdate.Any() && !_parameters.PackagesToUpdate.Contains(reference.Identity.Id)) ) { continue; } - var matchingPackages = await Task.WhenAll(_parameters - .Sources - .Select(source => source.GetPackage(ct, reference, _parameters.PackageAuthor, _log)) - ); + packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, _parameters, _log))); _log.Write(""); - - //If the reference has been found on multiple sources, we merge the packages found together - var mergedPackage = matchingPackages - .GroupBy(p => p.Reference) - .Select(g => new UpdaterPackage(g.Key, g.SelectMany(p => p.AvailableVersions))) - .SingleOrDefault(); - - //Retrieve the latest version here so it is only done once per package - packages.Add(new UpdaterPackage(reference, mergedPackage.GetLatestVersion(_parameters))); } return packages.ToArray(); } - /// - /// Opens the XML files where packages were found. - /// - /// - /// - /// - private async Task> OpenFiles(CancellationToken ct, UpdaterPackage[] packages) - { - var files = packages - .SelectMany(p => p.Reference.Files) - .SelectMany(g => g.Value) - .Distinct(); - - var documents = new Dictionary(); - - foreach(var file in files) - { - documents.Add(file, await file.LoadDocument(ct)); - } - - return documents; - } - /// /// Updates a package to the given value in the given files. /// @@ -154,8 +123,8 @@ private async Task> OpenFiles(CancellationToken private async Task UpdateFiles( CancellationToken ct, string packageId, - UpdaterVersion version, - Dictionary targetFiles, + FeedVersion version, + Dictionary targetFiles, Dictionary documents ) { @@ -163,18 +132,18 @@ Dictionary documents foreach(var files in targetFiles) { - var updateTarget = files.Key; + var fileType = files.Key; foreach(var path in files.Value) { var document = documents[path]; var updates = new UpdateOperation[0]; - if(updateTarget.HasFlag(UpdateTarget.Nuspec)) + if(fileType.HasFlag(FileType.Nuspec)) { updates = document.UpdateDependencies(packageId, version, path, _parameters.IsDowngradeAllowed); } - else if(updateTarget.HasAnyFlag(UpdateTarget.DirectoryProps, UpdateTarget.DirectoryTargets, UpdateTarget.Csproj)) + else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj)) { updates = document.UpdatePackageReferences(packageId, version, path, _parameters.IsDowngradeAllowed); } From 7bce29577494e564e8ceb458d0377465ce7582ee Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 28 Oct 2019 14:44:10 -0400 Subject: [PATCH 124/201] Added NuGet.Downloader --- .../NuGet.Downloader.Tests.csproj | 16 ++ .../NuGet.Downloader.Tool.csproj | 16 ++ src/NuGet.Downloader.Tool/Program.cs | 55 +++++++ .../Entities/DownloaderParameters.cs | 28 ++++ src/NuGet.Downloader/NuGet.Downloader.csproj | 28 ++++ src/NuGet.Downloader/NuGetDownloader.cs | 139 ++++++++++++++++++ src/NuGet.Shared/Entities/IPackageFeed.cs | 41 +++++- src/NuGet.Shared/Entities/LocalPackage.cs | 17 +++ src/NuGet.Shared/Entities/PackageFeed.cs | 96 +++++++----- .../Entities/PackageNotFoundException.cs | 26 ++++ .../Extensions/PackageSourceExtensions.cs | 96 +++++++++++- src/NuGet.Shared/Helpers/SolutionHelper.cs | 16 +- src/NuGet.Shared/Log/ConsoleLogger.cs | 2 +- src/NuGet.Shared/NuGet.Shared.projitems | 2 + .../Entities/TestPackageFeed.cs | 20 ++- .../SolutionHelperTests.cs | 2 +- src/NuGet.Updater.sln | 24 +++ .../Extensions/PackageReferenceExtensions.cs | 5 +- src/NuGet.Updater/NuGetUpdater.cs | 4 +- 19 files changed, 570 insertions(+), 63 deletions(-) create mode 100644 src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj create mode 100644 src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj create mode 100644 src/NuGet.Downloader.Tool/Program.cs create mode 100644 src/NuGet.Downloader/Entities/DownloaderParameters.cs create mode 100644 src/NuGet.Downloader/NuGet.Downloader.csproj create mode 100644 src/NuGet.Downloader/NuGetDownloader.cs create mode 100644 src/NuGet.Shared/Entities/LocalPackage.cs create mode 100644 src/NuGet.Shared/Entities/PackageNotFoundException.cs diff --git a/src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj b/src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj new file mode 100644 index 0000000..8ae4c5b --- /dev/null +++ b/src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.0 + + false + + + + + + + + + + diff --git a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj new file mode 100644 index 0000000..8ff589f --- /dev/null +++ b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + + + + diff --git a/src/NuGet.Downloader.Tool/Program.cs b/src/NuGet.Downloader.Tool/Program.cs new file mode 100644 index 0000000..d8bae1e --- /dev/null +++ b/src/NuGet.Downloader.Tool/Program.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Mono.Options; +using NuGet.Downloader.Entities; +using NuGet.Shared.Entities; +using NuGet.Shared.Extensions; +using Uno.Extensions; + +namespace NuGet.Downloader.Tool +{ + public class Program + { + public static async Task Main(string[] args) + { + try + { + var isHelp = false; + var parameters = new DownloaderParameters + { + PackageOutputPath = GetTemporaryOutputPath(), + }; + + var options = new OptionSet + { + { "help|h", "Displays this help screen", s => isHelp = true }, + { "solution|s=", "Path to the {solution}", s => parameters.SolutionPath = s }, + { "output|o=", "Path where to extract the packages; optional, defaults to a temporary folder", s => parameters.PackageOutputPath = s }, + { "sourceFeed=|sf=", "The NuGet feed from where to download the packages; a private feed can be specified with the format {url|accessToken}", s => parameters.Source = PackageFeed.FromString(s) }, + { "targetFeed=|tf=", "The NuGet feed where to push the packages; a private feed can be specified with the format {url|accessToken}; optional", s => parameters.Target = PackageFeed.FromString(s) }, + }; + + options.Parse(args); + + if(isHelp) + { + Console.WriteLine("NuGet Downloader is a tool allowing the download of the NuGet packages found in a solution"); + Console.WriteLine(); + options.WriteOptionDescriptions(Console.Out); + } + + var packages = await NuGetDownloader.DownloadAsync(CancellationToken.None, parameters, ConsoleLogger.Instance); + + Console.WriteLine($"{packages.Length} packages have been downloaded under {parameters.PackageOutputPath}"); + } + catch(Exception ex) + { + Console.Error.WriteLine($"Failed to download nuget packages: {ex.Message}"); + } + } + + private static string GetTemporaryOutputPath() => Path.Combine(Path.GetTempPath(), $"NuGet.Downloader.{Guid.NewGuid().ToStringInvariant()}"); + } +} diff --git a/src/NuGet.Downloader/Entities/DownloaderParameters.cs b/src/NuGet.Downloader/Entities/DownloaderParameters.cs new file mode 100644 index 0000000..3edf0d0 --- /dev/null +++ b/src/NuGet.Downloader/Entities/DownloaderParameters.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using NuGet.Shared.Entities; + +namespace NuGet.Downloader.Entities +{ + public class DownloaderParameters + { + /// + /// Gets or sets the solution to get packages from. + /// + public string SolutionPath { get; set; } + + /// + /// Gets or sets the location where the generate the package cache. + /// + public string PackageOutputPath { get; set; } + + /// + /// Gets or sets the feed to use to retrieve the packages. + /// + public IPackageFeed Source { get; set; } + + /// + /// Gets or sets the feed where to push the packages. + /// + public IPackageFeed Target { get; set; } + } +} diff --git a/src/NuGet.Downloader/NuGet.Downloader.csproj b/src/NuGet.Downloader/NuGet.Downloader.csproj new file mode 100644 index 0000000..6fe46f3 --- /dev/null +++ b/src/NuGet.Downloader/NuGet.Downloader.csproj @@ -0,0 +1,28 @@ + + + + netstandard20;net472 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NuGet.Downloader/NuGetDownloader.cs b/src/NuGet.Downloader/NuGetDownloader.cs new file mode 100644 index 0000000..97c2363 --- /dev/null +++ b/src/NuGet.Downloader/NuGetDownloader.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Downloader.Entities; +using NuGet.Packaging.Core; +using NuGet.Shared.Entities; +using NuGet.Shared.Helpers; +using Uno.Extensions; + +namespace NuGet.Downloader +{ + public class NuGetDownloader + { + public static async Task DownloadAsync(CancellationToken ct, DownloaderParameters parameters, ILogger log) + { + var downloader = new NuGetDownloader(log); + + return await downloader.DownloadPackages(ct, parameters); + } + + private readonly ILogger _log; + + private NuGetDownloader(ILogger log) + { + _log = log; + } + + public async Task DownloadPackages(CancellationToken ct, DownloaderParameters parameters) + { + var stopwatch = Stopwatch.StartNew(); + + var localPackages = new List(); + + Directory.CreateDirectory(parameters.PackageOutputPath); + + var packages = await GetPackagesToDownload(ct, parameters.SolutionPath, parameters.Source); + + _log.LogInformation($"Found {packages.Count()} packages to download."); + + foreach(var package in packages) + { + var localPackage = await parameters.Source.DownloadPackage(ct, package, parameters.PackageOutputPath); + + if(localPackage == null) + { + throw new PackageNotFoundException(package, parameters.Source.Url); //Shouldn't happen + } + + localPackages.Add(localPackage); + } + + if(parameters.Target != null) + { + _log.LogInformation($"Pushing packages to {parameters.Target.Url}."); + + foreach(var package in localPackages) + { + await parameters.Target.PushPackage(ct, package); + } + } + + stopwatch.Stop(); + + _log.LogInformation($"Operation completed in {stopwatch.Elapsed}."); + + return localPackages.ToArray(); + } + + private async Task> GetPackagesToDownload(CancellationToken ct, string solutionPath, IPackageFeed source) + { + var references = await SolutionHelper.GetPackageReferences(ct, solutionPath, FileType.All, _log); + + var identities = new HashSet(references.Select(r => r.Identity)); + + return await GetPackagesWithDependencies(ct, identities, source); + } + + private async Task> GetPackagesWithDependencies( + CancellationToken ct, + ISet packages, + IPackageFeed source, + ISet knownPackages = null, + ISet missingPackages = null + ) + { + knownPackages = knownPackages ?? new HashSet(); + missingPackages = missingPackages ?? new HashSet(); + + //Assume we will have to download the packages passed + var packagesToDonwload = new HashSet(packages); + + foreach(var package in packages) + { + try + { + var packageDependencies = await source.GetDependencies(ct, package); + + _log.LogInformation($"Found {packageDependencies.Length} dependencies for {package} in {source.Url}"); + + //TODO: make the version used parametrable (Use MaxVersion or MinVersion) + packagesToDonwload.AddRange(packageDependencies.Select(d => new PackageIdentity(d.Id, d.VersionRange.MinVersion))); + + knownPackages.Add(package); + } + catch(PackageNotFoundException ex) + { + _log.LogInformation(ex.Message); + + missingPackages.Add(package); + } + } + + //Take all the dependencies found + var unknownPackages = new HashSet(packagesToDonwload); + //Remove the packages that we already know are missing + unknownPackages.ExceptWith(missingPackages); + //Remove the packages we already have dependencies for + unknownPackages.ExceptWith(knownPackages); + + if(unknownPackages.Any()) + { + //Get the dependencies for the rest + var subDependencies = await GetPackagesWithDependencies(ct, unknownPackages, source, knownPackages, missingPackages); + //Add those to the packages to download + packagesToDonwload.UnionWith(subDependencies); + } + + //Remove the packages that we know are missing + packagesToDonwload.ExceptWith(missingPackages); + + return packagesToDonwload; + } + } +} diff --git a/src/NuGet.Shared/Entities/IPackageFeed.cs b/src/NuGet.Shared/Entities/IPackageFeed.cs index 1eb7c9f..d5c72d1 100644 --- a/src/NuGet.Shared/Entities/IPackageFeed.cs +++ b/src/NuGet.Shared/Entities/IPackageFeed.cs @@ -2,15 +2,54 @@ using System.Threading; using System.Threading.Tasks; using NuGet.Common; +using NuGet.Packaging.Core; namespace NuGet.Shared.Entities { public interface IPackageFeed { + /// + /// Gets the URL of the feed. + /// Uri Url { get; } + /// + /// Gets a value indicating whether the feed is private. + /// bool IsPrivate { get; } - Task GetPackageVersions(CancellationToken ct, PackageReference reference, string author = null, ILogger log = null); + /// + /// Get available versions for the given package reference. + /// + /// + /// + /// + /// + Task GetPackageVersions(CancellationToken ct, PackageReference reference, string author = null); + + /// + /// Get the dependencies of the given package identity. + /// + /// + /// + /// + Task GetDependencies(CancellationToken ct, PackageIdentity packageIdentity); + + /// + /// Downloads the package with the given identity. + /// + /// + /// + /// + /// + Task DownloadPackage(CancellationToken ct, PackageIdentity packageIdentity, string location); + + /// + /// Pushes the given local package. + /// + /// + /// + /// + Task PushPackage(CancellationToken ct, LocalPackage package); } } diff --git a/src/NuGet.Shared/Entities/LocalPackage.cs b/src/NuGet.Shared/Entities/LocalPackage.cs new file mode 100644 index 0000000..832053e --- /dev/null +++ b/src/NuGet.Shared/Entities/LocalPackage.cs @@ -0,0 +1,17 @@ +using NuGet.Packaging.Core; + +namespace NuGet.Shared.Entities +{ + public class LocalPackage + { + internal LocalPackage(PackageIdentity identity, string path) + { + Identity = identity; + Path = path; + } + + public PackageIdentity Identity { get; } + + public string Path { get; } + } +} diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs index bceef3b..a0243c3 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -1,10 +1,12 @@ using System; +using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using NuGet.Common; using NuGet.Configuration; +using NuGet.Packaging.Core; using NuGet.Protocol; using NuGet.Shared.Extensions; using Uno.Extensions; @@ -13,48 +15,27 @@ namespace NuGet.Shared.Entities { public class PackageFeed : IPackageFeed { - #region Static - private const char PackageFeedInputSeparator = '|'; - public static readonly PackageFeed NuGetOrg = new PackageFeed("https://api.nuget.org/v3/index.json"); + public static readonly PackageFeed NuGetOrg = new PackageFeed(new PackageSource("https://api.nuget.org/v3/index.json")); - public static PackageFeed FromString(string input) - { - var parts = input.Split(PackageFeedInputSeparator); + public static ILogger Logger { get; set; } = ConsoleLogger.Instance; - return parts.Length == 1 - ? new PackageFeed(parts[0]) - : new PackageFeed(parts[0], parts[1]); - } - #endregion + public static PackageFeed FromString(string input) => new PackageFeed(input.ToPackageSource()); private readonly PackageSource _packageSource; - private readonly bool _isPrivate = false; - - private PackageFeed(string url) - : this(new PackageSource(url)) - { - } - - private PackageFeed(string url, string accessToken) - : this(GetPackageSource(url, accessToken), isPrivate: true) - { - } - private PackageFeed(PackageSource source, bool isPrivate = false) + private PackageFeed(PackageSource source) { _packageSource = source; - IsPrivate = isPrivate; } public Uri Url => _packageSource.SourceUri; - public bool IsPrivate { get; } + public bool IsPrivate => _packageSource.Credentials == null; public async Task GetPackageVersions( CancellationToken ct, PackageReference reference, - string author = null, - ILogger log = null + string author = null ) { var logMessage = new StringBuilder(); @@ -72,26 +53,65 @@ public async Task GetPackageVersions( logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions from {author}" : $"No versions from {author} found"); } - log?.LogInformation(logMessage.ToString()); + Logger.LogInformation(logMessage.ToString()); return versions - .Cast() .Select(m => new FeedVersion(m.Version, Url)) .ToArray(); } - private static PackageSource GetPackageSource(string url, string accessToken) + public async Task GetDependencies( + CancellationToken ct, + PackageIdentity packageIdentity + ) + { + var version = await _packageSource.GetPackageVersion(ct, packageIdentity); + + if(version == null) + { + throw new PackageNotFoundException(packageIdentity, Url); + } + + return version + .DependencySets + .SelectMany(g => g.Packages) + .Distinct() + .ToArray(); + } + + public async Task DownloadPackage( + CancellationToken ct, + PackageIdentity packageIdentity, + string downloadLocation + ) { - var name = url.GetHashCode().ToStringInvariant(); + var version = await _packageSource.GetPackageVersion(ct, packageIdentity); - return new PackageSource(url, name) + if (version == null) //Package with this version doesn't exist in the source, skipping. { -#if UAP - Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false), -#else - Credentials = PackageSourceCredential.FromUserInput(name, "user", accessToken, false, null), -#endif - }; + return null; + } + + var downloadResult = await _packageSource.DownloadPackage(ct, packageIdentity, downloadLocation, Logger); + + var localPackagePath = Path.Combine(downloadLocation, $"{packageIdentity}.nupkg"); + + File.WriteAllBytes(localPackagePath, downloadResult.PackageStream.ReadBytes()); + + return new LocalPackage(packageIdentity, localPackagePath); + } + + public async Task PushPackage(CancellationToken ct, LocalPackage package) + { + var version = await _packageSource.GetPackageVersion(ct, package.Identity); + + if(version != null) + { + Logger.LogInformation($"{package.Identity} already exists in source, skipping."); + return; + } + + await _packageSource.PushPackage(ct, package, Logger); } } } diff --git a/src/NuGet.Shared/Entities/PackageNotFoundException.cs b/src/NuGet.Shared/Entities/PackageNotFoundException.cs new file mode 100644 index 0000000..e9370b5 --- /dev/null +++ b/src/NuGet.Shared/Entities/PackageNotFoundException.cs @@ -0,0 +1,26 @@ +using System; +using NuGet.Packaging.Core; + +namespace NuGet.Shared.Entities +{ + [Serializable] + public class PackageNotFoundException : Exception + { + public PackageNotFoundException() + { + } + + public PackageNotFoundException(PackageIdentity package, Uri sourceUrl) + : this($"{package} not found in {sourceUrl.AbsoluteUri}.") + { + } + + private PackageNotFoundException(string message) : base(message) + { + } + + public PackageNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs index b34c12d..ce624c0 100644 --- a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs @@ -1,20 +1,106 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NuGet.Common; using NuGet.Configuration; +using NuGet.Packaging.Core; using NuGet.Protocol; using NuGet.Protocol.Core.Types; +using NuGet.Shared.Entities; +using Uno.Extensions; namespace NuGet.Shared.Extensions { public static class PackageSourceExtensions { - public static async Task GetPackageVersions( + private const char PackageFeedInputSeparator = '|'; + + /// + /// Transforms a input string into a package feed + /// If the string matches the {url}|{token} format, a private source will be created. + /// Otherwise, the input will be used as the URL of a public source. + /// + /// + /// + public static PackageSource ToPackageSource(this string input) + { + var parts = input.Split(PackageFeedInputSeparator); + + var url = parts.ElementAtOrDefault(0); + var accessToken = parts.ElementAtOrDefault(1); + + if(accessToken == null) + { + return new PackageSource(url); + } + + var sourceName = Guid.NewGuid().ToStringInvariant(); + + return new PackageSource(url) + { +#if UAP + Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false), +#else + Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false, null), +#endif + }; + } + + public static async Task GetPackageVersion( + this PackageSource source, + CancellationToken ct, + PackageIdentity identity + ) + { + var versions = await source.GetPackageVersions(ct, identity.Id); + + return versions + .Cast() + .FirstOrDefault(m => m.Version.Equals(identity.Version)); + } + + public static async Task GetPackageVersions( this PackageSource source, CancellationToken ct, string packageId ) + { + var versions = await source + .GetResource() + .GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, NullLogger.Instance, ct); + + return versions + .Cast() + .ToArray(); + } + + public static async Task DownloadPackage( + this PackageSource source, + CancellationToken ct, + PackageIdentity identity, + string downloadDirectory, + ILogger log + ) + { + var downloadContext = new PackageDownloadContext(new SourceCacheContext { NoCache = true }, downloadDirectory, directDownload: true); + + return await source + .GetResource() + .GetDownloadResourceResultAsync(identity, downloadContext, downloadDirectory, log, ct); + } + + public static Task PushPackage( + this PackageSource source, + CancellationToken ct, + LocalPackage package, + ILogger log + ) => source + .GetResource() + .Push(package.Path, null, 100, true, s => s, s => s, true, log); + + private static TResource GetResource(this PackageSource source) + where TResource : class, INuGetResource { var repositoryProvider = new SourceRepositoryProvider( Settings.LoadDefaultSettings(null), @@ -23,11 +109,7 @@ string packageId var repository = repositoryProvider.CreateRepository(source, FeedType.HttpV3); - var packageMetadataResource = await repository.GetResourceAsync(ct); - - var versions = await packageMetadataResource.GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, NullLogger.Instance, ct); - - return versions.ToArray(); + return repository.GetResource(); } } } diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs index 2add91a..f58796e 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -22,10 +22,10 @@ internal static async Task GetPackageReferences( CancellationToken ct, string solutionPath, FileType fileType, - ILogger log = null + ILogger log ) { - log?.LogInformation($"Retrieving references from files in {solutionPath}"); + log.LogInformation($"Retrieving references from files in {solutionPath}"); var packages = new List(); @@ -70,7 +70,7 @@ internal static async Task GetPackageReferences( .ToArray(); } - private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, ILogger log = null) + private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, ILogger log) { var files = new string[0]; @@ -90,14 +90,14 @@ private static async Task GetProjectFiles(CancellationToken ct, string .ToArray(); } - log?.LogInformation($"Found {files.Length} csproj files"); + log.LogInformation($"Found {files.Length} csproj files"); return files; } //To improve: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019#search-scope //The file should be looked for at all levels - private static async Task GetDirectoryFile(CancellationToken ct, string solutionPath, FileType target, ILogger log = null) + private static async Task GetDirectoryFile(CancellationToken ct, string solutionPath, FileType target, ILogger log) { string file; @@ -115,14 +115,14 @@ private static async Task GetDirectoryFile(CancellationToken ct, string if(file != null && File.Exists(file)) { - log?.LogInformation($"Found {target.GetDescription()}"); + log.LogInformation($"Found {target.GetDescription()}"); return file; } return null; } - private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, ILogger log = null) + private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, ILogger log) { string solutionFolder; @@ -140,7 +140,7 @@ private static async Task GetNuspecFiles(CancellationToken ct, string //Nuspec files are generated in obj when using the new csproj format files = files.Where(f => !f.Contains("\\obj\\")).ToArray(); - log?.LogInformation($"Found {files.Length} nuspec files"); + log.LogInformation($"Found {files.Length} nuspec files"); return files; } diff --git a/src/NuGet.Shared/Log/ConsoleLogger.cs b/src/NuGet.Shared/Log/ConsoleLogger.cs index b0ed3e9..41bd037 100644 --- a/src/NuGet.Shared/Log/ConsoleLogger.cs +++ b/src/NuGet.Shared/Log/ConsoleLogger.cs @@ -6,7 +6,7 @@ namespace NuGet.Shared.Entities { public class ConsoleLogger : ILogger { - public static readonly ConsoleLogger Default = new ConsoleLogger(); + public static readonly ConsoleLogger Instance = new ConsoleLogger(); private ConsoleLogger() { diff --git a/src/NuGet.Shared/NuGet.Shared.projitems b/src/NuGet.Shared/NuGet.Shared.projitems index 75ff87a..6da0fd6 100644 --- a/src/NuGet.Shared/NuGet.Shared.projitems +++ b/src/NuGet.Shared/NuGet.Shared.projitems @@ -9,7 +9,9 @@ NuGet.Shared + + diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs b/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs index 162698e..218865e 100644 --- a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs +++ b/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using NuGet.Common; +using NuGet.Packaging.Core; using NuGet.Shared.Entities; namespace NuGet.Updater.Tests.Entities @@ -23,14 +24,27 @@ public TestPackageFeed(Uri url, Dictionary packages) public bool IsPrivate { get; } + public async Task DownloadPackage( + CancellationToken ct, + PackageIdentity packageIdentity, + string location + ) => _packages + .GetValueOrDefault(packageIdentity.Id) + .Where(v => v.Equals(packageIdentity.Version.ToFullString(), StringComparison.OrdinalIgnoreCase)) + .Select(v => new LocalPackage(packageIdentity, Path.Combine(location, $"{packageIdentity.Id}.nupkg"))) + .SingleOrDefault(); + + public async Task GetDependencies(CancellationToken ct, PackageIdentity packageIdentity) => Array.Empty(); + public async Task GetPackageVersions( CancellationToken ct, PackageReference reference, - string author = null, - ILogger log = null + string author = null ) => _packages .GetValueOrDefault(reference.Identity.Id) ?.Select(v => new FeedVersion(v, Url)) .ToArray() ?? new FeedVersion[0]; + + public Task PushPackage(CancellationToken ct, LocalPackage package) => throw new NotSupportedException(); } } diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs index 9669f57..25ee5ee 100644 --- a/src/NuGet.Updater.Tests/SolutionHelperTests.cs +++ b/src/NuGet.Updater.Tests/SolutionHelperTests.cs @@ -15,7 +15,7 @@ public async Task GivenSolution_PackageReferencesAreFound() { var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; - var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution, FileType.Csproj); + var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution, FileType.Csproj, ConsoleLogger.Instance); Assert.IsTrue(references.Any()); } diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index 64178e3..f388558 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -28,6 +28,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader", "NuGet.D EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NuGet.Shared", "NuGet.Shared\NuGet.Shared.shproj", "{3890CCA3-B7CF-450B-A6CD-4A4106B0008C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet.Updater", "NuGet.Updater", "{54F00313-D565-4D02-9A4B-D2FFF0190934}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet.Downloader", "NuGet.Downloader", "{58AE6856-BDF1-4676-8FF3-39B95B7762C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tool", "NuGet.Downloader.Tool\NuGet.Downloader.Tool.csproj", "{17979C76-865A-4983-977B-E242BCE1B038}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tests", "NuGet.Downloader.Tests\NuGet.Downloader.Tests.csproj", "{4CCA4711-1333-4D4F-91A4-B6E4263D7244}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution NuGet.Shared\NuGet.Shared.projitems*{3890cca3-b7cf-450b-a6cd-4a4106b0008c}*SharedItemsImports = 13 @@ -53,10 +61,26 @@ Global {912018BD-D988-4C90-B4EC-BA1C171207C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {912018BD-D988-4C90-B4EC-BA1C171207C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {912018BD-D988-4C90-B4EC-BA1C171207C3}.Release|Any CPU.Build.0 = Release|Any CPU + {17979C76-865A-4983-977B-E242BCE1B038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17979C76-865A-4983-977B-E242BCE1B038}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17979C76-865A-4983-977B-E242BCE1B038}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17979C76-865A-4983-977B-E242BCE1B038}.Release|Any CPU.Build.0 = Release|Any CPU + {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {29277270-8EFE-41A3-8CD8-D54EBF514C41} = {54F00313-D565-4D02-9A4B-D2FFF0190934} + {3DAA81DB-00A6-4A84-8180-19A588398F52} = {54F00313-D565-4D02-9A4B-D2FFF0190934} + {D240ABFD-6A48-4E4B-A946-9EBF9840FADA} = {54F00313-D565-4D02-9A4B-D2FFF0190934} + {912018BD-D988-4C90-B4EC-BA1C171207C3} = {58AE6856-BDF1-4676-8FF3-39B95B7762C1} + {17979C76-865A-4983-977B-E242BCE1B038} = {58AE6856-BDF1-4676-8FF3-39B95B7762C1} + {4CCA4711-1333-4D4F-91A4-B6E4263D7244} = {58AE6856-BDF1-4676-8FF3-39B95B7762C1} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1C528FB3-4BD1-4D56-ABEE-A20F6367040F} EndGlobalSection diff --git a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs index c0161bd..1d40d17 100644 --- a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs @@ -47,13 +47,12 @@ CancellationToken ct public static async Task GetLatestVersion( this PackageReference reference, CancellationToken ct, - UpdaterParameters parameters, - ILogger log = null + UpdaterParameters parameters ) { var availableVersions = await Task.WhenAll(parameters .Feeds - .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor, log)) + .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor)) ); var versionsPerTarget = availableVersions diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index a1de204..60256c1 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -46,6 +46,8 @@ internal NuGetUpdater(UpdaterParameters parameters, UpdaterLogger log) { _parameters = parameters.Validate(); _log = log; + + PackageFeed.Logger = _log; } public async Task UpdatePackages(CancellationToken ct) @@ -104,7 +106,7 @@ internal async Task GetPackages(CancellationToken ct) continue; } - packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, _parameters, _log))); + packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, _parameters))); _log.Write(""); } From 85fd0f3966a62a72f3dabf4a73ea93d1a5f5e1c1 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 29 Oct 2019 10:41:32 -0400 Subject: [PATCH 125/201] Improved documentation --- CHANGELOG.md | 6 ++++ README.md | 55 +++-------------------------- src/NuGet.Downloader.Tool/README.md | 16 +++++++++ src/NuGet.Updater.Tool/Readme.md | 54 ++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 50 deletions(-) create mode 100644 src/NuGet.Downloader.Tool/README.md create mode 100644 src/NuGet.Updater.Tool/Readme.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 974dd3a..c54360b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- Added NuGet.Downloader + +### Changed +- Re-organized code to improve reusability + ## Version 1.0 ### Added diff --git a/README.md b/README.md index f54c310..3220dec 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,16 @@ # NuGet Updater -NuGet updates made easy. - -NuGet Updater allows batch updates of NuGet packages in a solution. +NuGet packages manipulations made easy. [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) -## Getting Started - -The NuGet Updater can be installed as a standalone .Net Core tool using the following command: -`dotnet tool install -g nventive.NuGet.Updater.Tool` - -Help can be found with : -`nugetupdater --help` - -The NuGet updater library can also be installed as a NuGet package in a UWP or WPF application. - -## Sample commands - -- Update all packages in the current folder (and its subfolders) to the latest stable version found on NuGet.org -``` -nugetupdater --useNuGetorg -``` -This can also be achieved using the following command -``` -nugetupdater --feed=https://api.nuget.org/v3/index.json -``` - -- Update all packages in `MySolution.sln` to the latest stable version available on NuGet.org -``` -nugetupdater --solution=MySolution.sln -n -``` - -- Update packages to either beta, stable or alpha (whichever's the highest) -``` -nugetupdater -s=MySolution.sln -n --version=beta -v=alpha -``` - -- Update packages to the latest beta version available on a private feed -``` -nugetupdater -s=MySolution.sln --feed=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --version=beta -``` +## NuGet.Updater -- Update packages from `nventive` from NuGet.org, except for `PackageA` and `PackageB` -``` -nugetupdater -s=MySolution.sln -n --packageAuthor=nventive --ignore=PackageA -i=PackageB -``` +Documentation about the NuGet.Updater can be found [here](src/NuGet.Updater.Tool/README.md) -- Update only `PackageA` and `PackageB` from NuGet.org and a private feed -``` -nugetupdater -s=MySolution.sln -n -f=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --update=PackageA -u=PackageB -``` +## NuGet.Downloader -- Update packages to latest stable, even if a higher version is already found in the solution -``` -nugetupdater -s=MySolution.sln -n --allowDowngrade -``` +Documentation about the NuGet.Downloader can be found [here](src/NuGet.Downloader.Tool/README.md) ## Changelog diff --git a/src/NuGet.Downloader.Tool/README.md b/src/NuGet.Downloader.Tool/README.md new file mode 100644 index 0000000..a525a66 --- /dev/null +++ b/src/NuGet.Downloader.Tool/README.md @@ -0,0 +1,16 @@ +## About + +NuGet.Downloader allows to download all NuGet packages found in a solution, and also to push them to a specific NuGet feed. + +## Getting Started + +The NuGet Downloader can be installed as a standalone .Net Core tool using the following command: +`dotnet tool install -g nventive.NuGet.Downloader.Tool` + +Help can be found with : +`nugetdownloader --help` + +The NuGet downloader library can also be installed as a NuGet package in a UWP or .Net Standard application. + +## Sample commands + diff --git a/src/NuGet.Updater.Tool/Readme.md b/src/NuGet.Updater.Tool/Readme.md new file mode 100644 index 0000000..ab508f4 --- /dev/null +++ b/src/NuGet.Updater.Tool/Readme.md @@ -0,0 +1,54 @@ +## About + +NuGet.Updater allows batch updates of NuGet packages found in solutions. + +## Getting Started + +The NuGet Updater can be installed as a standalone .Net Core tool using the following command: +`dotnet tool install -g nventive.NuGet.Updater.Tool` + +Help can be found with : +`nugetupdater --help` + +The NuGet updater library can also be installed as a NuGet package in a UWP or .Net Standard application. + +## Sample commands + +- Update all packages in the current folder (and its subfolders) to the latest stable version found on NuGet.org +``` +nugetupdater --useNuGetorg +``` +This can also be achieved using the following command +``` +nugetupdater --feed=https://api.nuget.org/v3/index.json +``` + +- Update all packages in `MySolution.sln` to the latest stable version available on NuGet.org +``` +nugetupdater --solution=MySolution.sln -n +``` + +- Update packages to either beta, stable or alpha (whichever's the highest) +``` +nugetupdater -s=MySolution.sln -n --version=beta -v=alpha +``` + +- Update packages to the latest beta version available on a private feed +``` +nugetupdater -s=MySolution.sln --feed=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --version=beta +``` + +- Update packages from `nventive` from NuGet.org, except for `PackageA` and `PackageB` +``` +nugetupdater -s=MySolution.sln -n --packageAuthor=nventive --ignore=PackageA -i=PackageB +``` + +- Update only `PackageA` and `PackageB` from NuGet.org and a private feed +``` +nugetupdater -s=MySolution.sln -n -f=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --update=PackageA -u=PackageB +``` + +- Update packages to latest stable, even if a higher version is already found in the solution +``` +nugetupdater -s=MySolution.sln -n --allowDowngrade +``` From c23e41db720632da503cf5161b95eba352b46389 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 12 Nov 2019 14:19:07 -0500 Subject: [PATCH 126/201] Added support for result, version overrides and dry run This allows to replicates a run of the NuGet updater using a single file. --- src/NuGet.Shared/Entities/FeedVersion.cs | 7 ++ src/NuGet.Shared/Entities/PackageFeed.cs | 2 +- .../Extensions/DictionaryExtensions.cs | 10 +++ .../Extensions/XmlDocumentExtensions.cs | 2 +- src/NuGet.Shared/Helpers/FileHelper.Net.cs | 20 ++--- src/NuGet.Shared/Helpers/FileHelper.UAP.cs | 4 +- src/NuGet.Shared/Helpers/SolutionHelper.cs | 4 +- src/NuGet.Updater.Tests/NuGetUpdaterTests.cs | 4 +- .../PackageReferenceTests.cs | 27 ++++++- .../NuGet.Updater.Tool.csproj | 1 + src/NuGet.Updater.Tool/Program.cs | 47 +++++++++-- src/NuGet.Updater/Entities/UpdateResult.cs | 19 +++++ .../Entities/UpdaterParameters.cs | 27 +++++-- .../Extensions/PackageReferenceExtensions.cs | 6 ++ .../Extensions/UpdateOperationExtensions.cs | 21 +++++ .../Extensions/UpdaterParametersExtension.cs | 5 -- .../Extensions/XmlDocumentExtensions.cs | 80 ++++++------------- src/NuGet.Updater/Log/UpdateOperation.cs | 70 ++++++++++------ src/NuGet.Updater/Log/UpdaterLogger.cs | 21 ++--- src/NuGet.Updater/NuGetUpdater.cs | 77 +++++++++++------- 20 files changed, 295 insertions(+), 159 deletions(-) create mode 100644 src/NuGet.Updater/Entities/UpdateResult.cs create mode 100644 src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs diff --git a/src/NuGet.Shared/Entities/FeedVersion.cs b/src/NuGet.Shared/Entities/FeedVersion.cs index 148b699..3d34775 100644 --- a/src/NuGet.Shared/Entities/FeedVersion.cs +++ b/src/NuGet.Shared/Entities/FeedVersion.cs @@ -10,6 +10,11 @@ internal FeedVersion(string version, Uri feedUri) { } + public FeedVersion(NuGetVersion version) + : this(version, null) + { + } + public FeedVersion(NuGetVersion version, Uri feedUri) { Version = version; @@ -20,6 +25,8 @@ public FeedVersion(NuGetVersion version, Uri feedUri) public Uri FeedUri { get; } + public bool IsOverride => FeedUri == null; + public int CompareTo(FeedVersion other) => Version.CompareTo(other.Version); } } diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs index a0243c3..ece2745 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -53,7 +53,7 @@ public async Task GetPackageVersions( logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions from {author}" : $"No versions from {author} found"); } - Logger.LogInformation(logMessage.ToString()); + Logger.LogInformation(logMessage.ToString().Trim()); return versions .Select(m => new FeedVersion(m.Version, Url)) diff --git a/src/NuGet.Shared/Extensions/DictionaryExtensions.cs b/src/NuGet.Shared/Extensions/DictionaryExtensions.cs index 1017c22..c7f9d84 100644 --- a/src/NuGet.Shared/Extensions/DictionaryExtensions.cs +++ b/src/NuGet.Shared/Extensions/DictionaryExtensions.cs @@ -25,6 +25,16 @@ public static bool TryAdd(this IDictionary dictionar return true; } + + public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key) + { + if(dictionary.ContainsKey(key)) + { + return dictionary[key]; + } + + return default; + } #endif } } diff --git a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs index 57f8c91..2c76682 100644 --- a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs @@ -110,7 +110,7 @@ public static async Task Save(this XmlDocument document, CancellationToken ct, s #if UAP var xml = document.GetXml(); - xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding") + xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding", StringComparison.OrdinalIgnoreCase) ? x.Result("$1${declaration} encoding=\"utf-8\"$2") // restore encoding declaration that is stripped by `GetXml` : x.Value ); diff --git a/src/NuGet.Shared/Helpers/FileHelper.Net.cs b/src/NuGet.Shared/Helpers/FileHelper.Net.cs index 3e45bb2..209c399 100644 --- a/src/NuGet.Shared/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Shared/Helpers/FileHelper.Net.cs @@ -4,25 +4,17 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Uno.Extensions; namespace NuGet.Shared.Helpers { - internal class FileHelper + public class FileHelper { - public static void LogToFile(string outputFilePath, IEnumerable log) + public static void LogToFile(string outputFilePath, string line) { - if (File.Exists(outputFilePath)) + using (var writer = File.AppendText(outputFilePath)) { - File.WriteAllText(outputFilePath, ""); - } - - using (var file = File.OpenWrite(outputFilePath)) - using (var writer = new StreamWriter(file)) - { - foreach (var line in log) - { - writer.WriteLine(line); - } + writer.WriteLine(line); } } @@ -32,7 +24,7 @@ public static async Task GetFiles(CancellationToken ct, string path, s ? "*" + extensionFilter : null; - if (nameFilter != null && filter == null) + if (nameFilter.HasValue()) { filter = nameFilter; } diff --git a/src/NuGet.Shared/Helpers/FileHelper.UAP.cs b/src/NuGet.Shared/Helpers/FileHelper.UAP.cs index d43d277..cf25399 100644 --- a/src/NuGet.Shared/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Shared/Helpers/FileHelper.UAP.cs @@ -12,9 +12,9 @@ namespace NuGet.Shared.Helpers /// /// UAP-Specific methods. /// - internal class FileHelper + public class FileHelper { - public static void LogToFile(string outputFilePath, IEnumerable log) + public static void LogToFile(string outputFilePath, string line) { } diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs index f58796e..1b9105c 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -113,7 +113,7 @@ private static async Task GetDirectoryFile(CancellationToken ct, string file = Path.Combine(solutionFolder, target.GetDescription()); } - if(file != null && File.Exists(file)) + if(file.HasValue() && File.Exists(file)) { log.LogInformation($"Found {target.GetDescription()}"); return file; @@ -138,7 +138,7 @@ private static async Task GetNuspecFiles(CancellationToken ct, string var files = await FileHelper.GetFiles(ct, solutionFolder, extensionFilter: ".nuspec"); //Nuspec files are generated in obj when using the new csproj format - files = files.Where(f => !f.Contains("\\obj\\")).ToArray(); + files = files.Where(f => !f.Contains("\\obj\\", StringComparison.OrdinalIgnoreCase)).ToArray(); log.LogInformation($"Found {files.Length} nuspec files"); diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs index fae104e..d3de47e 100644 --- a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs +++ b/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs @@ -30,8 +30,8 @@ public async Task GivenUnspecifiedTarget_NoUpdateIsMade() { SolutionRoot = "MySolution.sln", UpdateTarget = FileType.Unspecified, - TargetVersions = new[] { "stable" }, - Feeds = new List { TestFeed }, + TargetVersions = { "stable" }, + Feeds = { TestFeed }, }; var logger = new UpdaterLogger(Console.Out); diff --git a/src/NuGet.Updater.Tests/PackageReferenceTests.cs b/src/NuGet.Updater.Tests/PackageReferenceTests.cs index 4ba2916..ca2a0ad 100644 --- a/src/NuGet.Updater.Tests/PackageReferenceTests.cs +++ b/src/NuGet.Updater.Tests/PackageReferenceTests.cs @@ -7,6 +7,7 @@ using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Tests.Entities; +using NuGet.Versioning; namespace NuGet.Updater.Tests { @@ -27,8 +28,8 @@ public async Task GivenPackageWithMatchingVersion_VersionIsFound() { var parameters = new UpdaterParameters { - TargetVersions = new[] { "beta" }, - Feeds = new[] { TestFeed }, + TargetVersions = { "beta" }, + Feeds = { TestFeed }, }; var packageVersion = "1.0-beta.1"; @@ -47,7 +48,7 @@ public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() { var parameters = new UpdaterParameters { - TargetVersions = new[] { "stable" }, + TargetVersions = { "stable" }, }; var packageVersion = "1.0-beta.1"; @@ -59,5 +60,25 @@ public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() Assert.IsNull(version); } + + [TestMethod] + public async Task GivenManualUpdates_AndVersionNotInFeed_ManualVersionIsFound() + { + var reference = new PackageReference("nventive.NuGet.Updater", "1.0"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "stable" }, + Feeds = { TestFeed }, + VersionOverrides = + { + { reference.Identity.Id, reference.Identity.Version }, + }, + }; + + var version = await reference.GetLatestVersion(CancellationToken.None, parameters); + + Assert.AreEqual(version.Version, reference.Identity.Version); + } } } diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index d78eb7a..ecc39c6 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -29,6 +29,7 @@ + diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 434f026..33e96c4 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Mono.Options; +using Newtonsoft.Json; +using NuGet.Packaging; using NuGet.Shared.Entities; +using NuGet.Shared.Helpers; +using NuGet.Shared.Log; using NuGet.Updater.Entities; +using NuGet.Versioning; namespace NuGet.Updater.Tool { @@ -21,6 +27,7 @@ public static async Task Main(string[] args) var isHelp = false; var isSilent = false; string summaryFile = default; + string resultFile = default; var options = new OptionSet { @@ -36,6 +43,9 @@ public static async Task Main(string[] args) { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Feeds.Add(PackageFeed.NuGetOrg)) }, { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, + { "dryrun", "Runs the updater but doesn't write the updates to files.", _ => Set(p => p.IsDryRun = true) }, + { "result|r=", "The path to the file where the update result should be saved.", s => resultFile = s }, + { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", s => Set(p => p.VersionOverrides.AddRange(LoadManualOperations(s))) }, }; _isParameterSet = false; @@ -43,9 +53,6 @@ public static async Task Main(string[] args) { SolutionRoot = Environment.CurrentDirectory, UpdateTarget = FileType.All, - Feeds = new List(), - PackagesToIgnore = new List(), - PackagesToUpdate = new List(), }; options.Parse(args); @@ -58,9 +65,11 @@ public static async Task Main(string[] args) } else { - var updater = new NuGetUpdater(_parameters, isSilent ? null : Console.Out, summaryFile); + var updater = new NuGetUpdater(_parameters, isSilent ? null : Console.Out, GetSummaryWriter(summaryFile)); - await updater.UpdatePackages(CancellationToken.None); + var result = await updater.UpdatePackages(CancellationToken.None); + + Save(result, resultFile); } } catch(Exception ex) @@ -75,6 +84,32 @@ private static void Set(Action set) _isParameterSet = true; } - private static string[] GetList(string value) => !string.IsNullOrEmpty(value) ? value.Split(",;".ToArray(), StringSplitOptions.RemoveEmptyEntries) : null; + private static TextWriter GetSummaryWriter(string summaryFile) => summaryFile == null ? null : new SimpleTextWriter(line => FileHelper.LogToFile(summaryFile, line)); + + private static void Save(IEnumerable result, string path) + { + if(path == null || path == "") + { + return; + } + + var serializer = JsonSerializer.CreateDefault(); + + using(var writer = File.CreateText(path)) + { + serializer.Serialize(writer, result); + } + } + + private static Dictionary LoadManualOperations(string inputFilePath) + { + using(var fileReader = File.OpenText(inputFilePath)) + using(var jsonReader = new JsonTextReader(fileReader)) + { + var result = JsonSerializer.CreateDefault().Deserialize>(jsonReader); + + return result.ToDictionary(r => r.PackageId, r => new NuGetVersion(r.UpdatedVersion)); + } + } } } diff --git a/src/NuGet.Updater/Entities/UpdateResult.cs b/src/NuGet.Updater/Entities/UpdateResult.cs new file mode 100644 index 0000000..da4c099 --- /dev/null +++ b/src/NuGet.Updater/Entities/UpdateResult.cs @@ -0,0 +1,19 @@ +using System; +using NuGet.Versioning; + +namespace NuGet.Updater.Entities +{ + public class UpdateResult : IEquatable + { + public string PackageId { get; set; } + + public string PreviousVersion { get; set; } + + public string UpdatedVersion { get; set; } + + public override int GetHashCode() => PackageId?.GetHashCode() ?? 0; + + public bool Equals(UpdateResult other) => other == null + || (other.PackageId.Equals(PackageId, StringComparison.OrdinalIgnoreCase) && other.UpdatedVersion.Equals(UpdatedVersion, StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 284a28e..1256910 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using NuGet.Shared.Entities; +using NuGet.Versioning; namespace NuGet.Updater.Entities { @@ -11,14 +12,14 @@ public class UpdaterParameters public string SolutionRoot { get; set; } /// - /// Gets or sets a list of feeds to get packages from. + /// Gets a list of feeds to get packages from. /// - public ICollection Feeds { get; set; } + public ICollection Feeds { get; } = new List(); /// - /// Gets or sets the versions to update to (stable, dev, beta, etc.), in order of priority. + /// Gets the versions to update to (stable, dev, beta, etc.), in order of priority. /// - public ICollection TargetVersions { get; set; } = new List { "stable" }; + public ICollection TargetVersions { get; } = new List(); /// /// Gets or sets a value indicating whether the version should exactly match the target version. @@ -41,18 +42,28 @@ public class UpdaterParameters public FileType UpdateTarget { get; set; } /// - /// Gets or sets a list of packages to ignore. + /// Gets a list of packages to ignore. /// - public ICollection PackagesToIgnore { get; set; } = new List(); + public ICollection PackagesToIgnore { get; } = new List(); /// - /// Gets or sets a list of packages to update; all packages found will be updated if nothing is specified. + /// Gets a list of packages to update; all packages found will be updated if nothing is specified. /// - public ICollection PackagesToUpdate { get; set; } = new List(); + public ICollection PackagesToUpdate { get; } = new List(); /// /// Gets or sets the name of the author of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. /// public string PackageAuthor { get; set; } + + /// + /// Gets the version to set for specific packages. + /// + public IDictionary VersionOverrides { get; } = new Dictionary(); + + /// + /// Gets or sets a value indicating whether to actually write the updates to the files. + /// + public bool IsDryRun { get; set; } } } diff --git a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs index 1d40d17..d95f646 100644 --- a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs @@ -50,6 +50,12 @@ public static async Task GetLatestVersion( UpdaterParameters parameters ) { + if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion)) + { + PackageFeed.Logger.LogInformation($"Overriding version for {reference.Identity.Id}"); + return new FeedVersion(manualVersion); + } + var availableVersions = await Task.WhenAll(parameters .Feeds .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor)) diff --git a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs b/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs new file mode 100644 index 0000000..6e9ddbe --- /dev/null +++ b/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs @@ -0,0 +1,21 @@ +using NuGet.Updater.Entities; +using NuGet.Updater.Log; + +namespace NuGet.Updater.Extensions +{ + public static class UpdateOperationExtensions + { + public static UpdateOperation ToUpdateOperation(this UpdaterPackage package, bool canDowngrade) => new UpdateOperation( + package.PackageId, + package.Version, + canDowngrade + ); + + public static UpdateResult ToUpdateResult(this UpdateOperation operation) => new UpdateResult + { + PackageId = operation.PackageId, + PreviousVersion = operation.PreviousVersion.OriginalVersion, + UpdatedVersion = operation.UpdatedVersion.OriginalVersion, + }; + } +} diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 4dbb4de..2804bad 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -60,11 +60,6 @@ public static UpdaterParameters Validate(this UpdaterParameters parameters) throw new InvalidOperationException("The solution root must be specified"); } - if(parameters.Feeds.None()) - { - throw new InvalidOperationException("At least one NuGet feed should be specified"); - } - return parameters; } } diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index c6605b1..19512fa 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,18 +1,11 @@ using System.Collections.Generic; using System.Linq; -using NuGet.Shared.Entities; using NuGet.Shared.Extensions; using NuGet.Updater.Log; -using NuGet.Versioning; using Uno.Extensions; #if UAP -using System.Text.RegularExpressions; -using Windows.Data.Xml.Dom; -using Windows.Storage; using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; -using XmlElement = Windows.Data.Xml.Dom.XmlElement; -using XmlNode = Windows.Data.Xml.Dom.IXmlNode; #else using XmlDocument = System.Xml.XmlDocument; #endif @@ -22,22 +15,17 @@ namespace NuGet.Updater.Extensions public static class XmlDocumentExtensions { /// - /// Updates the PackageReferences with the given id in the given XmlDocument. + /// Runs an on the PackageReferences contained in a . /// /// - /// - /// - /// - /// + /// /// - public static UpdateOperation[] UpdatePackageReferences( + public static IEnumerable UpdatePackageReferences( this XmlDocument document, - string packageId, - FeedVersion version, - string path, - bool isDowngradeAllowed) + UpdateOperation operation + ) { - var operations = new List(); + var packageId = operation.PackageId; var packageReferences = document.SelectElements("PackageReference", $"[@Include='{packageId}' or @Update='{packageId}']"); var dotnetCliReferences = document.SelectElements("DotNetCliToolReference", $"[@Include='{packageId}' or @Update='{packageId}']"); @@ -48,16 +36,14 @@ public static UpdateOperation[] UpdatePackageReferences( if(packageVersion.HasValue()) { - var currentVersion = new NuGetVersion(packageVersion); - - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); + operation = operation.WithPreviousVersion(packageVersion); - if(operation.IsUpdate) + if(operation.ShouldProceed) { - packageReference.SetAttribute("Version", version.Version.ToString()); + packageReference.SetAttribute("Version", operation.UpdatedVersion.ToString()); } - operations.Add(operation); + yield return operation; } else { @@ -65,63 +51,47 @@ public static UpdateOperation[] UpdatePackageReferences( if(node != null) { - var currentVersion = new NuGetVersion(node.InnerText); + operation = operation.WithPreviousVersion(node.InnerText); - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - - if(operation.IsUpdate) + if(operation.ShouldProceed) { - node.InnerText = version.Version.ToString(); + node.InnerText = operation.UpdatedVersion.ToString(); } - operations.Add(operation); + yield return operation; } } } - - return operations.ToArray(); } /// - /// Updates the dependencies with the given id in the given XmlDocument. + /// Runs an on the dependencies contained in a loaded from a .nuspec file. /// /// - /// - /// - /// - /// + /// /// - public static UpdateOperation[] UpdateDependencies( + public static IEnumerable UpdateDependencies( this XmlDocument document, - string packageId, - FeedVersion version, - string path, - bool isDowngradeAllowed + UpdateOperation operation ) { - var operations = new List(); - - foreach(var node in document.SelectElements("dependency", $"[@id='{packageId}']")) + foreach(var node in document.SelectElements("dependency", $"[@id='{operation.PackageId}']")) { var versionNodeValue = node.GetAttribute("version"); // only nodes with explicit version, skip expansion. - if(!versionNodeValue.Contains("{")) + if(!versionNodeValue.Contains("{", System.StringComparison.OrdinalIgnoreCase)) { - var currentVersion = new NuGetVersion(versionNodeValue); - - var operation = new UpdateOperation(isDowngradeAllowed, packageId, currentVersion, version, path); - - if(operation.IsUpdate) + operation = operation.WithPreviousVersion(versionNodeValue); + + if(operation.ShouldProceed) { - node.SetAttribute("version", version.Version.ToString()); + node.SetAttribute("version", operation.UpdatedVersion.ToString()); } - operations.Add(operation); + yield return operation; } } - - return operations.ToArray(); } } } diff --git a/src/NuGet.Updater/Log/UpdateOperation.cs b/src/NuGet.Updater/Log/UpdateOperation.cs index a97d7ad..40a707d 100644 --- a/src/NuGet.Updater/Log/UpdateOperation.cs +++ b/src/NuGet.Updater/Log/UpdateOperation.cs @@ -1,30 +1,38 @@ using System; +using System.IO; using NuGet.Shared.Entities; using NuGet.Shared.Extensions; +using NuGet.Updater.Entities; using NuGet.Versioning; namespace NuGet.Updater.Log { public class UpdateOperation { - public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion previousVersion, FeedVersion updatedVersion, string filePath) + private readonly bool _canDowngrade; + + public UpdateOperation(string packageId, FeedVersion updatedVersion, bool canDowngrade) + : this(packageId, previousVersion: null, updatedVersion, filePath: null, canDowngrade) + { + } + + private UpdateOperation(string packageId, NuGetVersion previousVersion, FeedVersion updatedVersion, string filePath, bool canDowngrade) + : this(packageId, previousVersion, updatedVersion.Version, updatedVersion.FeedUri, filePath, canDowngrade) + { + } + + private UpdateOperation(string packageId, NuGetVersion previousVersion, NuGetVersion updatedVersion, Uri feedUri, string filePath, bool canDowngrade) { - Date = DateTimeOffset.Now; + _canDowngrade = canDowngrade; - PackageName = packageName; + PackageId = packageId; PreviousVersion = previousVersion; - UpdatedVersion = updatedVersion.Version; + UpdatedVersion = updatedVersion; FilePath = filePath; - FeedUri = updatedVersion.FeedUri; - - IsLatestVersion = PreviousVersion == UpdatedVersion; - IsDowngrade = PreviousVersion.IsGreaterThan(UpdatedVersion) && isDowngradeAllowed; - IsUpdate = UpdatedVersion.IsGreaterThan(PreviousVersion) || (!IsLatestVersion && isDowngradeAllowed); + FeedUri = feedUri; } - public DateTimeOffset Date { get; } - - public string PackageName { get; } + public string PackageId { get; } public NuGetVersion PreviousVersion { get; } @@ -34,30 +42,46 @@ public UpdateOperation(bool isDowngradeAllowed, string packageName, NuGetVersion public Uri FeedUri { get; } - public bool IsUpdate { get; } + public bool ShouldProceed => UpdatedVersion.IsGreaterThan(PreviousVersion) || ShouldDowngrade; - public bool IsLatestVersion { get; } - - public bool IsDowngrade { get; } + public bool ShouldDowngrade => PreviousVersion.IsGreaterThan(UpdatedVersion) && _canDowngrade; public string GetLogMessage() { - if(IsLatestVersion) + if(PreviousVersion == UpdatedVersion) { - return $"Version [{UpdatedVersion}] of [{PackageName}] already found in [{FilePath}]. Skipping."; + return $"Version [{UpdatedVersion}] of [{PackageId}] already found in [{FilePath}]. Skipping."; } - else if(IsDowngrade) + else if(ShouldDowngrade) { - return $"Downgrading [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; + return $"Downgrading [{PackageId}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; } - else if(IsUpdate) + else if(ShouldProceed) { - return $"Updating [{PackageName}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; + return $"Updating [{PackageId}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; } else { - return $"Version [{PreviousVersion}] of [{PackageName}] found in [{FilePath}]. Higher than [{UpdatedVersion}]. Skipping."; + return $"Version [{PreviousVersion}] of [{PackageId}] found in [{FilePath}]. Higher than [{UpdatedVersion}]. Skipping."; } } + + public UpdateOperation WithPreviousVersion(string version) => new UpdateOperation( + PackageId, + previousVersion: new NuGetVersion(version), + UpdatedVersion, + FeedUri, + FilePath, + _canDowngrade + ); + + public UpdateOperation WithFilePath(string filePath) => new UpdateOperation( + PackageId, + PreviousVersion, + UpdatedVersion, + FeedUri, + filePath, + _canDowngrade + ); } } diff --git a/src/NuGet.Updater/Log/UpdaterLogger.cs b/src/NuGet.Updater/Log/UpdaterLogger.cs index 5ef5aa8..07ed668 100644 --- a/src/NuGet.Updater/Log/UpdaterLogger.cs +++ b/src/NuGet.Updater/Log/UpdaterLogger.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using NuGet.Common; -using NuGet.Shared.Helpers; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; @@ -16,9 +15,9 @@ public class UpdaterLogger : ILogger { private readonly List _updateOperations = new List(); private readonly TextWriter _writer; - private readonly string _summaryFilePath; + private readonly TextWriter _summaryWriter; - public UpdaterLogger(TextWriter writer, string summaryFilePath = null) + public UpdaterLogger(TextWriter writer, TextWriter summaryWriter = null) { _writer = writer #if DEBUG @@ -26,7 +25,7 @@ public UpdaterLogger(TextWriter writer, string summaryFilePath = null) #else ?? TextWriter.Null; #endif - _summaryFilePath = summaryFilePath; + _summaryWriter = summaryWriter; } public void Clear() => _updateOperations.Clear(); @@ -56,19 +55,21 @@ public void WriteSummary(UpdaterParameters parameters) Write(line); } - if (_summaryFilePath != null) + if (_summaryWriter != null) { try { - FileHelper.LogToFile(_summaryFilePath, GetSummary(parameters, includeUrl: true)); + _summaryWriter.Write(string.Join(Environment.NewLine, GetSummary(parameters, includeUrl: true))); } catch (Exception ex) { - Write($"Failed to write to {_summaryFilePath}. Reason : {ex.Message}"); + Write($"Failed to write summary. Reason : {ex.Message}"); } } } + public IEnumerable GetResult() => _updateOperations.Where(o => o.ShouldProceed).Select(o => o.ToUpdateResult()).Distinct(); + private IEnumerable GetSummary(UpdaterParameters parameters, bool includeUrl = false) { yield return $"# Package update summary"; @@ -83,12 +84,12 @@ private IEnumerable GetSummary(UpdaterParameters parameters, bool includ yield return line; } - foreach(var message in LogPackageOperations(_updateOperations.Where(o => o.IsUpdate), isUpdate: true, includeUrl)) + foreach(var message in LogPackageOperations(_updateOperations.Where(o => o.ShouldProceed), isUpdate: true, includeUrl)) { yield return message; } - foreach(var message in LogPackageOperations(_updateOperations.Where(o => !o.IsUpdate), isUpdate: false, includeUrl)) + foreach(var message in LogPackageOperations(_updateOperations.Where(o => !o.ShouldProceed), isUpdate: false, includeUrl)) { yield return message; } @@ -103,7 +104,7 @@ private IEnumerable LogPackageOperations(IEnumerable op var packages = operations .Select(o => ( - name: o.PackageName, + name: o.PackageId, version: isUpdate ? o.UpdatedVersion : o.PreviousVersion, uri: o.FeedUri )) diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 60256c1..b6a1b0e 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -9,6 +10,7 @@ using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Log; +using Uno.Extensions; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; @@ -25,20 +27,20 @@ public class NuGetUpdater private readonly UpdaterParameters _parameters; private readonly UpdaterLogger _log; - public static async Task UpdateAsync( + public static async Task> UpdateAsync( CancellationToken ct, UpdaterParameters parameters, TextWriter logWriter = null, - string summaryOutputFilePath = null + TextWriter summaryWriter = null ) { - var updater = new NuGetUpdater(parameters, logWriter, summaryOutputFilePath); + var updater = new NuGetUpdater(parameters, logWriter, summaryWriter); return await updater.UpdatePackages(ct); } - public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, string summaryOutputFilePath) - : this(parameters, new UpdaterLogger(logWriter, summaryOutputFilePath)) + public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, TextWriter summaryWriter) + : this(parameters, new UpdaterLogger(logWriter, summaryWriter)) { } @@ -50,7 +52,7 @@ internal NuGetUpdater(UpdaterParameters parameters, UpdaterLogger log) PackageFeed.Logger = _log; } - public async Task UpdatePackages(CancellationToken ct) + public async Task> UpdatePackages(CancellationToken ct) { _log.Clear(); @@ -62,17 +64,31 @@ public async Task UpdatePackages(CancellationToken ct) foreach(var package in packages) { - var latestVersion = package.Version; - - if(latestVersion == null) + if(package.Version == null) { - _log.Write($"Skipping [{package.PackageId}]: no {string.Join(" or ", _parameters.TargetVersions)} version found."); + var targetVersionText = string.Join(" or ", _parameters.TargetVersions); + + if(targetVersionText.HasValue()) + { + targetVersionText += " "; + } + + _log.Write($"Skipping [{package.PackageId}]: no {targetVersionText}version found."); } else { - _log.Write($"Latest matching version for [{package.PackageId}] is [{latestVersion.Version}] on {latestVersion.FeedUri}"); + var operation = package.ToUpdateOperation(_parameters.IsDowngradeAllowed); + + if(package.Version.IsOverride) + { + _log.Write($"Version forced to [{operation.UpdatedVersion}] for [{operation.PackageId}]"); + } + else + { + _log.Write($"Latest matching version for [{operation.PackageId}] is [{operation.UpdatedVersion}] on {operation.FeedUri}"); + } - _log.Write(await UpdateFiles(ct, package.PackageId, latestVersion, package.Reference.Files, documents)); + _log.Write(await UpdateFiles(ct, operation, package.Reference.Files, documents)); } _log.Write(""); @@ -80,7 +96,7 @@ public async Task UpdatePackages(CancellationToken ct) _log.WriteSummary(_parameters); - return true; + return _log.GetResult(); } /// @@ -94,8 +110,11 @@ internal async Task GetPackages(CancellationToken ct) var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget, _log); _log.Write($"Found {references.Length} references"); - _log.Write(""); - _log.Write($"Retrieving packages from {_parameters.Feeds.Count} feeds"); + + if(_parameters.Feeds.Any()) + { + _log.Write($"Retrieving packages from {_parameters.Feeds.Count} feeds"); + } foreach(var reference in references.OrderBy(r => r.Identity)) { @@ -107,8 +126,6 @@ internal async Task GetPackages(CancellationToken ct) } packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, _parameters))); - - _log.Write(""); } return packages.ToArray(); @@ -118,14 +135,12 @@ internal async Task GetPackages(CancellationToken ct) /// Updates a package to the given value in the given files. /// /// - /// - /// + /// /// /// private async Task UpdateFiles( CancellationToken ct, - string packageId, - FeedVersion version, + UpdateOperation operation, Dictionary targetFiles, Dictionary documents ) @@ -138,19 +153,27 @@ Dictionary documents foreach(var path in files.Value) { - var document = documents[path]; - var updates = new UpdateOperation[0]; + var document = documents.GetValueOrDefault(path); + + if(document == null) + { + continue; + } + + IEnumerable updates = Array.Empty(); + + operation = operation.WithFilePath(path); if(fileType.HasFlag(FileType.Nuspec)) { - updates = document.UpdateDependencies(packageId, version, path, _parameters.IsDowngradeAllowed); + updates = document.UpdateDependencies(operation); } else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj)) { - updates = document.UpdatePackageReferences(packageId, version, path, _parameters.IsDowngradeAllowed); + updates = document.UpdatePackageReferences(operation); } - if(updates.Any(u => u.IsUpdate)) + if(!_parameters.IsDryRun && updates.Any(u => u.ShouldProceed)) { await document.Save(ct, path); } From 60e739c869372b5105e1dca255676871be50d070 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 12 Nov 2019 14:21:14 -0500 Subject: [PATCH 127/201] Replaced iconUrl with icon --- src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj | 13 ++++++------- src/NuGet.Updater.sln | 1 + src/NuGet.Updater/NuGet.Updater.csproj | 9 ++++++++- src/icon.png | Bin 0 -> 2486 bytes 4 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 src/icon.png diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index ecc39c6..fcc8cf2 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -18,22 +18,21 @@ nventive nventive Apache-2.0 - https://nventive-email-assets.s3.amazonaws.com/packages/nv-package-logo%400%2C25x.png + icon.png https://github.com/nventive/NuGet.Updater - - - - - + - + + True + + diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index f388558..254b661 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props ..\gitversion.yml = ..\gitversion.yml global.json = global.json + icon.png = icon.png ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj index 8673a98..89e47a2 100644 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -12,7 +12,7 @@ nventive Nuget Updater allows to automatically update the NuGet packages in a solution at build time Apache-2.0 - https://nventive-email-assets.s3.amazonaws.com/packages/nv-package-logo%400%2C25x.png + icon.png https://github.com/nventive/NuGet.Updater @@ -44,6 +44,13 @@ + + + True + + + + diff --git a/src/icon.png b/src/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..814cdb60b0fa756e5b7ab912804718471e659d58 GIT binary patch literal 2486 zcmcJR={MU6AH{!JNNgn!CH9uK*0I!59ZQHb=pl9nwM;uot-(YQBx3E{O2F zR@9`|(5m4P7~E9Q%JE-qBX|}~xEB%h9+29tM>W2ff`qL3egg5B!J*%?Uo5GQEthRQ zSTbhqzc3yN-ID*iYl(drzZCsta7*qEFAN4V^BIKiuS6??wJe}M*>IW+f_zy4h`<1g zX~25~sQ7;#;QFhNSd;oM%zX)J2J znVFO14dCqCWQ8E7bZ=Na;C1YUy)$J2zQ&!_VWT z#E?gL$1BnG*cj;`&e!%eYUlND{+71)O#`H1HK@Y!ZOWvCvT}{ZeP;os&W_qhkk?T= z)QeN)bo+TWQ{F&0huSunW)#^tP(#9d)qrG_WE+Cddy1N}@LNm67k^R89v;%Q{dYBSGc{3h z<*nDTWXVWLZN*eEyf}iOUN^Ke>^0>Y=1%fEy+s}P<)(o#?b^8|Nw)`4;Qk<0#94d>SCQWP?3Bd-+Z zl=1SX2b#WILUQVaN5-P>KB1$QMF5GLCR5>41dELf%k-S2Ovx~8Na-cxVPwSQuBhb*+J^Xs5m!`H<>9c`$wdqlpS@(D)8{F)x#!mB|2>)A+h7i{biE20S)yv6U8rADW+yM(h@cw!b9^@Wu43(u9Dy1cbKS-!U1D#(tWHVG#d^UmCXP-PEqHDuV|z@j4$&Q0#h^%ejw zj{*%ZU{mRdFRv{uwr#xcmi*q5^_SWuR;ZWDMJP;^LFNVFEuPgCwC&$3W$KL8YUF$L z;@xBN^>1JK*knmCjtm{Jez;ksY1)V)GnUw zN@rCCGEckj1M`{iHfh*V{7U0f5Tr00RHxFE_R4OicACIX;~H3Z!T_UFaWuO zDVv$24{p0GVFV2{9uZ|IJkHs2mzmk8Kc3H5amAX|+uF`niDCelddK?J1=AGA7OW3< zP7gf(fmDueeJ*{H_?*{}>!*-2&`6$08OkD;mdQyK0m-0e75aj`XO1em1S0~-^ph!v zd-B4rN5dc z1ps7JoB-49Iy0I`J&OUrQCbjT|EPq?0+dS{fa#j5y07hPbk0_4HEd)E0ZQ|cq7_|R z#_ZlF@hbobYe--bj`2}$SmWd3a$rt6oI;ybW8~v5l!g&Q(s$C(W>7moIMArmgh4Pg zU=xpqS2cRL^I!$CLSUhF@{)YNg1nMEnB?7KAzheDTFMQ{pPM2(tKE91T@@HxV$reZ z@n)Ft;masOv^rFd8WNoz7POkBDJ;o4D}&td-boDAcOx;Z6}KI|trmF95)+H2)n0RJ z=UHmKlZ(zDUm!I{SD3O|oOuuXN=15R94F9Gi8uE7Tlo|G_)YNt4 Date: Tue, 12 Nov 2019 14:37:20 -0500 Subject: [PATCH 128/201] Updated documentation --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c54360b..7364b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added NuGet.Downloader +#### NuGet.Updater: +- Added option to do a dry run +- Added option to have a summary of the operations exectued, allowing to re-execute a specific run ### Changed - Re-organized code to improve reusability From 285efc6fd682eefdd526f8bc8950e2dd854df42d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 12 Nov 2019 17:15:09 -0500 Subject: [PATCH 129/201] Prevented duplicate updates on documents This was causing the output to incorrectly report the package as already being at the latest version. --- CHANGELOG.md | 4 + .../Extensions/XmlDocumentExtensions.cs | 75 +++++++++++++------ src/NuGet.Updater/NuGetUpdater.cs | 14 ++-- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7364b2b..deffd48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added NuGet.Downloader + #### NuGet.Updater: - Added option to do a dry run - Added option to have a summary of the operations exectued, allowing to re-execute a specific run @@ -15,6 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Re-organized code to improve reusability +### Fixed +- Fixed a double call of the method used to update the documents; this was causing the update to work but a wrong output. + ## Version 1.0 ### Added diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index 19512fa..62b0d74 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; using System.Linq; +using System.Xml; using NuGet.Shared.Extensions; using NuGet.Updater.Log; using Uno.Extensions; #if UAP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; #else using XmlDocument = System.Xml.XmlDocument; #endif @@ -25,6 +27,8 @@ public static IEnumerable UpdatePackageReferences( UpdateOperation operation ) { + var operations = new List(); + var packageId = operation.PackageId; var packageReferences = document.SelectElements("PackageReference", $"[@Include='{packageId}' or @Update='{packageId}']"); @@ -32,35 +36,58 @@ UpdateOperation operation foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) { - var packageVersion = packageReference.GetAttribute("Version"); + var packageVersion = packageReference.GetAttributeOrChild("Version"); if(packageVersion.HasValue()) { - operation = operation.WithPreviousVersion(packageVersion); + var currentOperation = operation.WithPreviousVersion(packageVersion); - if(operation.ShouldProceed) + if(currentOperation.ShouldProceed) { - packageReference.SetAttribute("Version", operation.UpdatedVersion.ToString()); + packageReference.SetAttributeOrChild("Version", currentOperation.UpdatedVersion.ToString()); } - yield return operation; + operations.Add(currentOperation); } - else - { - var node = packageReference.SelectNode("Version"); + } - if(node != null) - { - operation = operation.WithPreviousVersion(node.InnerText); + return operations; + } - if(operation.ShouldProceed) - { - node.InnerText = operation.UpdatedVersion.ToString(); - } + /// + /// Gets the attribute or child (in this order) of the given with the given name. + /// + private static string GetAttributeOrChild(this XmlElement element, string name) + { + var attribute = element.GetAttribute(name); - yield return operation; - } - } + if(attribute.HasValue()) + { + return attribute; + } + + return element.SelectNode(name)?.InnerText; + } + + /// + /// Sets the attribute or child (in this order) of the given with the given name. + /// + private static void SetAttributeOrChild(this XmlElement element, string name, string value) + { + var attribute = element.GetAttribute(name); + + if(attribute.HasValue()) + { + element.SetAttribute(name, value); + } + else + { + var node = element.SelectNode(name); + + if(node != null) + { + node.InnerText = value; + } } } @@ -75,6 +102,8 @@ public static IEnumerable UpdateDependencies( UpdateOperation operation ) { + var operations = new List(); + foreach(var node in document.SelectElements("dependency", $"[@id='{operation.PackageId}']")) { var versionNodeValue = node.GetAttribute("version"); @@ -82,16 +111,18 @@ UpdateOperation operation // only nodes with explicit version, skip expansion. if(!versionNodeValue.Contains("{", System.StringComparison.OrdinalIgnoreCase)) { - operation = operation.WithPreviousVersion(versionNodeValue); + var currentOperation = operation.WithPreviousVersion(versionNodeValue); - if(operation.ShouldProceed) + if(currentOperation.ShouldProceed) { - node.SetAttribute("version", operation.UpdatedVersion.ToString()); + node.SetAttribute("version", currentOperation.UpdatedVersion.ToString()); } - yield return operation; + operations.Add(currentOperation); } } + + return operations; } } } diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index b6a1b0e..c0f1db8 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -64,7 +64,9 @@ public async Task> UpdatePackages(CancellationToken ct foreach(var package in packages) { - if(package.Version == null) + var version = package.Version; + + if(version == null) { var targetVersionText = string.Join(" or ", _parameters.TargetVersions); @@ -79,7 +81,7 @@ public async Task> UpdatePackages(CancellationToken ct { var operation = package.ToUpdateOperation(_parameters.IsDowngradeAllowed); - if(package.Version.IsOverride) + if(version.IsOverride) { _log.Write($"Version forced to [{operation.UpdatedVersion}] for [{operation.PackageId}]"); } @@ -90,8 +92,6 @@ public async Task> UpdatePackages(CancellationToken ct _log.Write(await UpdateFiles(ct, operation, package.Reference.Files, documents)); } - - _log.Write(""); } _log.WriteSummary(_parameters); @@ -162,15 +162,15 @@ Dictionary documents IEnumerable updates = Array.Empty(); - operation = operation.WithFilePath(path); + var currentOperation = operation.WithFilePath(path); if(fileType.HasFlag(FileType.Nuspec)) { - updates = document.UpdateDependencies(operation); + updates = document.UpdateDependencies(currentOperation); } else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj)) { - updates = document.UpdatePackageReferences(operation); + updates = document.UpdatePackageReferences(currentOperation); } if(!_parameters.IsDryRun && updates.Any(u => u.ShouldProceed)) From e54b00496149c6ca518adf849518394a398bbd67 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 15 Nov 2019 14:38:53 -0500 Subject: [PATCH 130/201] Improved output of the nuget updater --- CHANGELOG.md | 4 ++ src/NuGet.Shared/Helpers/MarkdownHelper.cs | 63 +++++++++++++++++++ src/NuGet.Shared/Helpers/StringHelper.cs | 30 +++++++++ src/NuGet.Shared/NuGet.Shared.projitems | 2 + src/NuGet.Updater/Entities/UpdateResult.cs | 2 +- .../Entities/UpdaterParameters.cs | 5 -- .../Extensions/UpdateOperationExtensions.cs | 2 +- .../Extensions/UpdaterParametersExtension.cs | 22 ++++--- src/NuGet.Updater/Log/UpdaterLogger.cs | 45 +++++++++---- 9 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 src/NuGet.Shared/Helpers/MarkdownHelper.cs create mode 100644 src/NuGet.Shared/Helpers/StringHelper.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index deffd48..8f850f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Re-organized code to improve reusability +- Improved summary structure ### Fixed - Fixed a double call of the method used to update the documents; this was causing the update to work but a wrong output. +### Removed +- Removed the "IncludeNuGetOrg" parameter; the NuGet feed must be added to the feeds instead (PackageFeed.NuGetOrg). + ## Version 1.0 ### Added diff --git a/src/NuGet.Shared/Helpers/MarkdownHelper.cs b/src/NuGet.Shared/Helpers/MarkdownHelper.cs new file mode 100644 index 0000000..1c9c9f3 --- /dev/null +++ b/src/NuGet.Shared/Helpers/MarkdownHelper.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Uno.Extensions; + +namespace NuGet.Shared.Helpers +{ + public static class MarkdownHelper + { + internal static string Link(string text, string url) => url.HasValue() ? $"[{text}]({url})" : text; + + internal static string Bold(string text) => $"**{text}**"; + + internal static string CodeBlocksEnumeration(IEnumerable values) => StringHelper.Enumeration(values.Select(v => CodeBlock(v))); + + internal static string CodeBlock(Uri url, bool isMultiline = false, string language = null) + => CodeBlock(url.OriginalString, isMultiline, language); + + internal static string CodeBlock(string text, bool isMultiline = false, string language = null) + { + if(isMultiline) + { + return new StringBuilder() + .AppendLine($"```{language}") + .AppendLine(text) + .AppendLine("```") + .ToString(); + } + + return $"`{text}`"; + } + + internal class TableBuilder + { + private readonly List _header = new List(); + private readonly List _body = new List(); + + public void AddHeader(string text) => _header.Add(text); + + public void AddLine(params string[] columns) => _body.Add(columns.Trim().ToArray()); + + public string Build() + { + var builder = new StringBuilder(); + + if(_header.Any()) + { + builder + .AppendLine($"|{string.Join("|", _header)}|") + .AppendLine($"|{string.Join("|", Enumerable.Repeat("-", _header.Count))}|"); + } + + foreach(var line in _body) + { + builder.AppendLine($"|{string.Join("|", line)}|"); + } + + return builder.ToString(); + } + } + } +} diff --git a/src/NuGet.Shared/Helpers/StringHelper.cs b/src/NuGet.Shared/Helpers/StringHelper.cs new file mode 100644 index 0000000..568e712 --- /dev/null +++ b/src/NuGet.Shared/Helpers/StringHelper.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Uno.Extensions; + +namespace NuGet.Shared.Helpers +{ + public static class StringHelper + { + internal static string Enumeration(IEnumerable values) + { + if(values.None()) + { + return string.Empty; + } + else if(values.Count() == 1) + { + return values.First(); + } + else if(values.Count() == 2) + { + return string.Join(" and ", values); + } + else + { + return $"{string.Join(", ", EnumerableExtensions.SkipLast(values, 1))} and {values.Last()}"; + } + } + } +} diff --git a/src/NuGet.Shared/NuGet.Shared.projitems b/src/NuGet.Shared/NuGet.Shared.projitems index 6da0fd6..3efa2dc 100644 --- a/src/NuGet.Shared/NuGet.Shared.projitems +++ b/src/NuGet.Shared/NuGet.Shared.projitems @@ -12,6 +12,8 @@ + + diff --git a/src/NuGet.Updater/Entities/UpdateResult.cs b/src/NuGet.Updater/Entities/UpdateResult.cs index da4c099..b270f90 100644 --- a/src/NuGet.Updater/Entities/UpdateResult.cs +++ b/src/NuGet.Updater/Entities/UpdateResult.cs @@ -7,7 +7,7 @@ public class UpdateResult : IEquatable { public string PackageId { get; set; } - public string PreviousVersion { get; set; } + public string OriginalVersion { get; set; } public string UpdatedVersion { get; set; } diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 1256910..6184332 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -26,11 +26,6 @@ public class UpdaterParameters /// public bool Strict { get; set; } - /// - /// Gets or sets a value indicating whether whether to include packages from NuGet.org. - /// - public bool IncludeNuGetOrg { get; set; } - /// /// Gets or sets a value indicating whether whether the packages can be downgraded if the version found is lower than the current one. /// diff --git a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs b/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs index 6e9ddbe..1418e3c 100644 --- a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs +++ b/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs @@ -14,7 +14,7 @@ public static class UpdateOperationExtensions public static UpdateResult ToUpdateResult(this UpdateOperation operation) => new UpdateResult { PackageId = operation.PackageId, - PreviousVersion = operation.PreviousVersion.OriginalVersion, + OriginalVersion = operation.PreviousVersion.OriginalVersion, UpdatedVersion = operation.UpdatedVersion.OriginalVersion, }; } diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index 2804bad..c85a74c 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -3,6 +3,7 @@ using System.Linq; using NuGet.Shared.Entities; using NuGet.Shared.Extensions; +using NuGet.Shared.Helpers; using NuGet.Updater.Entities; using Uno.Extensions; @@ -14,42 +15,43 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters { yield return $"## Configuration"; + yield return $"- Targeting solution {MarkdownHelper.CodeBlock(parameters.SolutionRoot)}"; + var files = parameters.UpdateTarget == FileType.All - ? string.Join(", ", Enum + ? Enum .GetValues(typeof(FileType)) .Cast() .Select(t => t.GetDescription()) .Trim() - ) - : parameters.UpdateTarget.GetDescription(); + : new[] { parameters.UpdateTarget.GetDescription() }; - yield return $"- Update targeting {files} files under {parameters.SolutionRoot}"; + yield return $"- Updating files of type {MarkdownHelper.CodeBlocksEnumeration(files)}"; if(parameters.Feeds?.Any() ?? false) { - yield return $"- Using NuGet packages from {string.Join(", ", parameters.Feeds.Select(s => s.Url))}"; + yield return $"- Fetching packages from {MarkdownHelper.CodeBlocksEnumeration(parameters.Feeds.Select(s => s.Url.OriginalString))}"; } if(parameters.PackageAuthor.HasValue()) { - yield return $"- Using only public packages authored by {parameters.PackageAuthor}"; + yield return $"- Limiting to public packages authored by {MarkdownHelper.Bold(parameters.PackageAuthor)}"; } - yield return $"- Using {(parameters.Strict ? "exact " : "")}target version {string.Join(", then ", parameters.TargetVersions)}"; + yield return $"- Using {MarkdownHelper.CodeBlocksEnumeration(parameters.TargetVersions)} versions {(parameters.Strict ? "(exact match)" : "")}"; if (parameters.IsDowngradeAllowed) { - yield return $"- Allowing package downgrade if a lower version is found"; + yield return $"- Downgrading packages if a lower version is found"; } if (parameters.PackagesToIgnore?.Any() ?? false) { - yield return $"- Ignoring {string.Join(",", parameters.PackagesToIgnore)}"; + yield return $"- Ignoring {MarkdownHelper.CodeBlocksEnumeration(parameters.PackagesToIgnore)}"; } if (parameters.PackagesToUpdate?.Any() ?? false) { - yield return $"- Updating only {string.Join(",", parameters.PackagesToUpdate)}"; + yield return $"- Updating only {MarkdownHelper.CodeBlocksEnumeration(parameters.PackagesToUpdate)}"; } } diff --git a/src/NuGet.Updater/Log/UpdaterLogger.cs b/src/NuGet.Updater/Log/UpdaterLogger.cs index 07ed668..4159caa 100644 --- a/src/NuGet.Updater/Log/UpdaterLogger.cs +++ b/src/NuGet.Updater/Log/UpdaterLogger.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using NuGet.Common; +using NuGet.Shared.Helpers; using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; @@ -50,7 +51,7 @@ public void Write(UpdateOperation operation) public void WriteSummary(UpdaterParameters parameters) { - foreach (var line in GetSummary(parameters)) + foreach (var line in GetSummary(parameters, includeUrl: true)) { Write(line); } @@ -68,7 +69,9 @@ public void WriteSummary(UpdaterParameters parameters) } } - public IEnumerable GetResult() => _updateOperations.Where(o => o.ShouldProceed).Select(o => o.ToUpdateResult()).Distinct(); + public IEnumerable GetResult() => _updateOperations + .Select(o => o.ToUpdateResult()) + .Distinct(); private IEnumerable GetSummary(UpdaterParameters parameters, bool includeUrl = false) { @@ -79,11 +82,6 @@ private IEnumerable GetSummary(UpdaterParameters parameters, bool includ yield return $"No packages have been updated."; } - foreach(var line in parameters.GetSummary()) - { - yield return line; - } - foreach(var message in LogPackageOperations(_updateOperations.Where(o => o.ShouldProceed), isUpdate: true, includeUrl)) { yield return message; @@ -93,6 +91,11 @@ private IEnumerable GetSummary(UpdaterParameters parameters, bool includ { yield return message; } + + foreach(var line in parameters.GetSummary()) + { + yield return line; + } } private IEnumerable LogPackageOperations(IEnumerable operations, bool isUpdate, bool includeUrl) @@ -105,22 +108,38 @@ private IEnumerable LogPackageOperations(IEnumerable op var packages = operations .Select(o => ( name: o.PackageId, - version: isUpdate ? o.UpdatedVersion : o.PreviousVersion, + oldVersion: o.PreviousVersion, + newVersion: isUpdate ? o.UpdatedVersion : o.PreviousVersion, uri: o.FeedUri )) .Distinct() .ToArray(); - yield return $"## {(isUpdate ? "Updated" : "Skipped")} {packages.Length} packages:"; + yield return $"## {(isUpdate ? "Updated" : "Skipped")} {packages.Length} packages"; - foreach(var p in packages) + var tableBuilder = new MarkdownHelper.TableBuilder(); + + tableBuilder.AddHeader("Package"); + + if(isUpdate) + { + tableBuilder.AddHeader("Original version"); + tableBuilder.AddHeader("Updated version"); + } + else { - var logMessage = $"[{p.name}] {(isUpdate ? "to" : "is at version")} [{p.version}]"; + tableBuilder.AddHeader("Version"); + } - var url = includeUrl ? PackageHelper.GetUrl(p.name, p.version, p.uri) : default; + foreach(var p in packages) + { + var oldVersionUrl = includeUrl ? PackageHelper.GetUrl(p.name, p.oldVersion, p.uri) : default; + var newVersionUrl = includeUrl ? PackageHelper.GetUrl(p.name, p.newVersion, p.uri) : default; - yield return url == null ? $"- {logMessage}" : $"- [{logMessage}]({url})"; + tableBuilder.AddLine(p.name, MarkdownHelper.Link(p.oldVersion.OriginalVersion, oldVersionUrl), isUpdate ? MarkdownHelper.Link(p.newVersion.OriginalVersion, newVersionUrl) : null); } + + yield return tableBuilder.Build(); } #region ILogger From 9d4343c37e6479fb02d564b1c1cf03788bce4a9c Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 20 Nov 2019 14:41:46 -0500 Subject: [PATCH 131/201] Adjust directory separator --- src/NuGet.Shared/Helpers/SolutionHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs index 1b9105c..18941f3 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -86,7 +86,7 @@ private static async Task GetProjectFiles(CancellationToken ct, string files = Regex .Matches(solutionContent, "[^\\s\"]*\\.csproj") .Cast() - .Select(m => Path.Combine(solutionFolder, m.Value)) + .Select(m => Path.Combine(solutionFolder, m.Value.Replace('\\', Path.DirectorySeparatorChar))) .ToArray(); } @@ -138,7 +138,7 @@ private static async Task GetNuspecFiles(CancellationToken ct, string var files = await FileHelper.GetFiles(ct, solutionFolder, extensionFilter: ".nuspec"); //Nuspec files are generated in obj when using the new csproj format - files = files.Where(f => !f.Contains("\\obj\\", StringComparison.OrdinalIgnoreCase)).ToArray(); + files = files.Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)).ToArray(); log.LogInformation($"Found {files.Length} nuspec files"); From 2fadbc3380b9ef8f23c1e7a7b1a3dd727f4d4112 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 22 Nov 2019 11:00:38 -0500 Subject: [PATCH 132/201] Ensured multiple package version per file are handled properly --- CHANGELOG.md | 1 + .../Extensions/XmlDocumentExtensions.cs | 19 +++++++++++-------- src/NuGet.Shared/Helpers/SolutionHelper.cs | 6 ++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f850f5..d8a4b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - Fixed a double call of the method used to update the documents; this was causing the update to work but a wrong output. +- Fixed handling of multiple references of the same package in a nuspec file ### Removed - Removed the "IncludeNuGetOrg" parameter; the NuGet feed must be added to the feeds instead (PackageFeed.NuGetOrg). diff --git a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs index 2c76682..314d1f4 100644 --- a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using NuGet.Packaging.Core; +using NuGet.Versioning; using Uno.Extensions; #if UAP @@ -27,9 +29,9 @@ public static class XmlDocumentExtensions /// /// /// A Dictionary where the key is the id of a package and the value its version. - public static Dictionary GetPackageReferences(this XmlDocument document) + public static PackageIdentity[] GetPackageReferences(this XmlDocument document) { - var references = new Dictionary(); + var references = new List(); var packageReferences = document.SelectElements("PackageReference"); var dotnetCliReferences = document.SelectElements("DotNetCliToolReference"); @@ -43,19 +45,19 @@ public static Dictionary GetPackageReferences(this XmlDocument d if(packageVersion.HasValue()) { - references.TryAdd(packageId, packageReference.GetAttribute("Version")); + references.Add(new PackageIdentity(packageId, new NuGetVersion(packageReference.GetAttribute("Version")))); } else { var node = packageReference.SelectNode("Version"); if(node != null) { - references.TryAdd(packageId, node.InnerText); + references.Add(new PackageIdentity(packageId, new NuGetVersion(node.InnerText))); } } } - return references; + return references.ToArray(); } /// @@ -63,10 +65,11 @@ public static Dictionary GetPackageReferences(this XmlDocument d /// /// /// A Dictionary where the key is the id of a package and the value its version. - public static Dictionary GetDependencies(this XmlDocument document) + public static PackageIdentity[] GetDependencies(this XmlDocument document) => document .SelectElements("dependency") - .ToDictionary(dependency => dependency.GetAttribute("id"), dependency => dependency.GetAttribute("version")); + .Select(e => new PackageIdentity(e.GetAttribute("id"), new NuGetVersion(e.GetAttribute("version")))) + .ToArray(); #region Utilities @@ -143,7 +146,7 @@ public static IEnumerable SelectElements(this XmlDocument document, public static XmlNode SelectNode(this XmlElement element, string name) => element .ChildNodes .OfType() - .FirstOrDefault(e => e.LocalName.ToString().Equals(name, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(e => e.LocalName.ToString().Equals(name, StringComparison.OrdinalIgnoreCase)); //LocalName is not a string in UWP #endregion } } diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs index 18941f3..b68b605 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using NuGet.Common; +using NuGet.Packaging.Core; using NuGet.Shared.Entities; using NuGet.Shared.Extensions; using NuGet.Versioning; @@ -153,7 +154,7 @@ private static async Task GetFileReferences(CancellationToke } var document = await file.LoadDocument(ct); - var references = new Dictionary(); + var references = Array.Empty(); if(target.HasAnyFlag(FileType.Csproj, FileType.DirectoryProps, FileType.DirectoryTargets)) { @@ -165,7 +166,8 @@ private static async Task GetFileReferences(CancellationToke } return references - .Select(g => new PackageReference(g.Key, new NuGetVersion(g.Value), file, target)) + .GroupBy(r => r.Id) + .Select(g => new PackageReference(g.Key, new NuGetVersion(g.FirstOrDefault().Version), file, target)) .ToArray(); } } From 423bf104abea5f4ec0644f59bd6ec014c7729c6f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 22 Nov 2019 11:48:30 -0500 Subject: [PATCH 133/201] Adding parsing of the versions --- CHANGELOG.md | 1 + .../Extensions/XmlDocumentExtensions.cs | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a4b25..c8f2871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added NuGet.Downloader +- Added proper parsing of the versions read in the files #### NuGet.Updater: - Added option to do a dry run diff --git a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs index 314d1f4..e8acdc4 100644 --- a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs @@ -45,19 +45,21 @@ public static PackageIdentity[] GetPackageReferences(this XmlDocument document) if(packageVersion.HasValue()) { - references.Add(new PackageIdentity(packageId, new NuGetVersion(packageReference.GetAttribute("Version")))); + references.Add(CreatePackageIdentity(packageId, packageReference.GetAttribute("Version"))); } else { var node = packageReference.SelectNode("Version"); if(node != null) { - references.Add(new PackageIdentity(packageId, new NuGetVersion(node.InnerText))); + references.Add(CreatePackageIdentity(packageId, node.InnerText)); } } } - return references.ToArray(); + return references + .Trim() + .ToArray(); } /// @@ -68,9 +70,20 @@ public static PackageIdentity[] GetPackageReferences(this XmlDocument document) public static PackageIdentity[] GetDependencies(this XmlDocument document) => document .SelectElements("dependency") - .Select(e => new PackageIdentity(e.GetAttribute("id"), new NuGetVersion(e.GetAttribute("version")))) + .Select(e => CreatePackageIdentity(e.GetAttribute("id"), e.GetAttribute("version"))) + .Trim() .ToArray(); + private static PackageIdentity CreatePackageIdentity(string id, string version) + { + if(NuGetVersion.TryParse(version, out var parsedVersion)) + { + return new PackageIdentity(id, parsedVersion); + } + + return default; + } + #region Utilities /// From baac9f53600a4128b6d27341b60e402d944f325c Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Thu, 28 Nov 2019 10:27:17 -0500 Subject: [PATCH 134/201] Show actual exceptions --- src/NuGet.Downloader.Tool/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NuGet.Downloader.Tool/Program.cs b/src/NuGet.Downloader.Tool/Program.cs index d8bae1e..e449619 100644 --- a/src/NuGet.Downloader.Tool/Program.cs +++ b/src/NuGet.Downloader.Tool/Program.cs @@ -47,6 +47,7 @@ public static async Task Main(string[] args) catch(Exception ex) { Console.Error.WriteLine($"Failed to download nuget packages: {ex.Message}"); + Console.Error.WriteLine($"{ex}"); } } From bc084db663af709f6e9efeee07f881a9f593b261 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Thu, 28 Nov 2019 10:27:40 -0500 Subject: [PATCH 135/201] Fix support for multiple Directory.Build.* --- src/NuGet.Shared/Helpers/SolutionHelper.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs index b68b605..72d5ccd 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -42,16 +42,20 @@ ILogger log { const FileType currentTarget = FileType.DirectoryProps; - var file = await GetDirectoryFile(ct, solutionPath, currentTarget, log); - packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) + { + packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + } } if(fileType.HasFlag(FileType.DirectoryTargets)) { const FileType currentTarget = FileType.DirectoryTargets; - var file = await GetDirectoryFile(ct, solutionPath, currentTarget, log); - packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) + { + packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + } } if(fileType.HasFlag(FileType.Nuspec)) @@ -98,7 +102,7 @@ private static async Task GetProjectFiles(CancellationToken ct, string //To improve: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019#search-scope //The file should be looked for at all levels - private static async Task GetDirectoryFile(CancellationToken ct, string solutionPath, FileType target, ILogger log) + private static async Task GetDirectoryFiles(CancellationToken ct, string solutionPath, FileType target, ILogger log) { string file; @@ -106,7 +110,7 @@ private static async Task GetDirectoryFile(CancellationToken ct, string { var matchingFiles = await FileHelper.GetFiles(ct, solutionPath, nameFilter: target.GetDescription()); - file = matchingFiles.SingleOrDefault(); + return matchingFiles.ToArray(); } else { @@ -117,10 +121,10 @@ private static async Task GetDirectoryFile(CancellationToken ct, string if(file.HasValue() && File.Exists(file)) { log.LogInformation($"Found {target.GetDescription()}"); - return file; + return new[] { file }; } - return null; + return new string[0]; } private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, ILogger log) From 46559975ce3a22130546b6dc0689d16dcda68c4d Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 13 Dec 2019 10:49:01 -0500 Subject: [PATCH 136/201] Package author is now ignore for private feeds --- CHANGELOG.md | 1 + src/NuGet.Shared/Entities/PackageFeed.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f2871..84a8368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - Fixed a double call of the method used to update the documents; this was causing the update to work but a wrong output. - Fixed handling of multiple references of the same package in a nuspec file +- Fixed the author filter being used for private feeds ### Removed - Removed the "IncludeNuGetOrg" parameter; the NuGet feed must be added to the feeds instead (PackageFeed.NuGetOrg). diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs index ece2745..bf9dce1 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -46,7 +46,7 @@ public async Task GetPackageVersions( logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions" : "No versions found"); - if(author.HasValue() && versions.Any()) + if(!IsPrivate && author.HasValue() && versions.Any()) { versions = versions.Where(m => m.HasAuthor(author)).ToArray(); From 78198d81278ac6d3aeaec37b83086149bd084a2a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 13 Dec 2019 13:34:27 -0500 Subject: [PATCH 137/201] Fixed IsPrivate condition --- src/NuGet.Shared/Entities/PackageFeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs index bf9dce1..9b9d7d5 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -30,7 +30,7 @@ private PackageFeed(PackageSource source) public Uri Url => _packageSource.SourceUri; - public bool IsPrivate => _packageSource.Credentials == null; + public bool IsPrivate => _packageSource.Credentials != null; public async Task GetPackageVersions( CancellationToken ct, From 21fb78799ecae14986abffe51870e475b6336649 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 20 Jan 2020 15:58:54 -0500 Subject: [PATCH 138/201] Improved nuget.downloader logging --- src/NuGet.Downloader.Tool/Program.cs | 10 ++- .../Entities/DownloaderResult.cs | 11 ++++ src/NuGet.Downloader/NuGetDownloader.cs | 63 +++++++++++++------ src/NuGet.Shared/Entities/IPackageFeed.cs | 4 +- src/NuGet.Shared/Entities/PackageFeed.cs | 8 +-- .../Entities/PackageNotFoundException.cs | 2 +- .../Extensions/PackageSourceExtensions.cs | 13 ++-- 7 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 src/NuGet.Downloader/Entities/DownloaderResult.cs diff --git a/src/NuGet.Downloader.Tool/Program.cs b/src/NuGet.Downloader.Tool/Program.cs index e449619..9b27d38 100644 --- a/src/NuGet.Downloader.Tool/Program.cs +++ b/src/NuGet.Downloader.Tool/Program.cs @@ -5,7 +5,6 @@ using Mono.Options; using NuGet.Downloader.Entities; using NuGet.Shared.Entities; -using NuGet.Shared.Extensions; using Uno.Extensions; namespace NuGet.Downloader.Tool @@ -40,9 +39,14 @@ public static async Task Main(string[] args) options.WriteOptionDescriptions(Console.Out); } - var packages = await NuGetDownloader.DownloadAsync(CancellationToken.None, parameters, ConsoleLogger.Instance); + var result = await NuGetDownloader.RunAsync(CancellationToken.None, parameters, ConsoleLogger.Instance); - Console.WriteLine($"{packages.Length} packages have been downloaded under {parameters.PackageOutputPath}"); + Console.WriteLine($"{result.DownloadedPackages.Length} packages have been downloaded under {parameters.PackageOutputPath}"); + + if(parameters.Target != null) + { + Console.WriteLine($"{result.PushedPackages?.Length ?? 0} packages have been pushed to {parameters.Target.Url}"); + } } catch(Exception ex) { diff --git a/src/NuGet.Downloader/Entities/DownloaderResult.cs b/src/NuGet.Downloader/Entities/DownloaderResult.cs new file mode 100644 index 0000000..73bda3a --- /dev/null +++ b/src/NuGet.Downloader/Entities/DownloaderResult.cs @@ -0,0 +1,11 @@ +using NuGet.Shared.Entities; + +namespace NuGet.Downloader.Entities +{ + public class DownloaderResult + { + public LocalPackage[] DownloadedPackages { get; set; } + + public LocalPackage[] PushedPackages { get; set; } + } +} diff --git a/src/NuGet.Downloader/NuGetDownloader.cs b/src/NuGet.Downloader/NuGetDownloader.cs index 97c2363..5888464 100644 --- a/src/NuGet.Downloader/NuGetDownloader.cs +++ b/src/NuGet.Downloader/NuGetDownloader.cs @@ -16,11 +16,11 @@ namespace NuGet.Downloader { public class NuGetDownloader { - public static async Task DownloadAsync(CancellationToken ct, DownloaderParameters parameters, ILogger log) + public static async Task RunAsync(CancellationToken ct, DownloaderParameters parameters, ILogger log) { var downloader = new NuGetDownloader(log); - return await downloader.DownloadPackages(ct, parameters); + return await downloader.RunAsync(ct, parameters); } private readonly ILogger _log; @@ -30,45 +30,70 @@ private NuGetDownloader(ILogger log) _log = log; } - public async Task DownloadPackages(CancellationToken ct, DownloaderParameters parameters) + public async Task RunAsync(CancellationToken ct, DownloaderParameters parameters) { var stopwatch = Stopwatch.StartNew(); - var localPackages = new List(); - - Directory.CreateDirectory(parameters.PackageOutputPath); + var result = new DownloaderResult(); var packages = await GetPackagesToDownload(ct, parameters.SolutionPath, parameters.Source); - _log.LogInformation($"Found {packages.Count()} packages to download."); + _log.LogInformation($"Found {packages.Count()} packages to download"); + + result.DownloadedPackages = await DownloadPackages(ct, packages, parameters.Source, parameters.PackageOutputPath); + result.PushedPackages = await PushPackages(ct, result.DownloadedPackages, parameters.Target); + + stopwatch.Stop(); + + _log.LogInformation($"Operation completed in {stopwatch.Elapsed}"); + + return result; + } + + private async Task DownloadPackages( + CancellationToken ct, + IEnumerable packages, + IPackageFeed sourceFeed, + string outputPath + ) + { + var downloadedPackages = new List(); + + Directory.CreateDirectory(outputPath); foreach(var package in packages) { - var localPackage = await parameters.Source.DownloadPackage(ct, package, parameters.PackageOutputPath); + var localPackage = await sourceFeed.DownloadPackage(ct, package, outputPath); if(localPackage == null) { - throw new PackageNotFoundException(package, parameters.Source.Url); //Shouldn't happen + throw new PackageNotFoundException(package, sourceFeed.Url); //Shouldn't happen } - localPackages.Add(localPackage); + downloadedPackages.Add(localPackage); } - if(parameters.Target != null) + return downloadedPackages.ToArray(); + } + + private async Task PushPackages(CancellationToken ct, IEnumerable packages, IPackageFeed targetFeed) + { + var pushedPackages = new List(); + + if(targetFeed != null) { - _log.LogInformation($"Pushing packages to {parameters.Target.Url}."); + _log.LogInformation($"Pushing packages to {targetFeed.Url}"); - foreach(var package in localPackages) + foreach(var package in packages) { - await parameters.Target.PushPackage(ct, package); + if(await targetFeed.PushPackage(ct, package)) + { + pushedPackages.Add(package); + } } } - stopwatch.Stop(); - - _log.LogInformation($"Operation completed in {stopwatch.Elapsed}."); - - return localPackages.ToArray(); + return pushedPackages.ToArray(); } private async Task> GetPackagesToDownload(CancellationToken ct, string solutionPath, IPackageFeed source) diff --git a/src/NuGet.Shared/Entities/IPackageFeed.cs b/src/NuGet.Shared/Entities/IPackageFeed.cs index d5c72d1..091e572 100644 --- a/src/NuGet.Shared/Entities/IPackageFeed.cs +++ b/src/NuGet.Shared/Entities/IPackageFeed.cs @@ -49,7 +49,7 @@ public interface IPackageFeed /// /// /// - /// - Task PushPackage(CancellationToken ct, LocalPackage package); + /// A value indicating whether or not the package has been pushed + Task PushPackage(CancellationToken ct, LocalPackage package); } } diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs index 9b9d7d5..34964a2 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -101,17 +101,17 @@ string downloadLocation return new LocalPackage(packageIdentity, localPackagePath); } - public async Task PushPackage(CancellationToken ct, LocalPackage package) + public async Task PushPackage(CancellationToken ct, LocalPackage package) { var version = await _packageSource.GetPackageVersion(ct, package.Identity); if(version != null) { - Logger.LogInformation($"{package.Identity} already exists in source, skipping."); - return; + Logger.LogInformation($"{package.Identity} already exists in source, skipping"); + return false; } - await _packageSource.PushPackage(ct, package, Logger); + return await _packageSource.PushPackage(ct, package, Logger); } } } diff --git a/src/NuGet.Shared/Entities/PackageNotFoundException.cs b/src/NuGet.Shared/Entities/PackageNotFoundException.cs index e9370b5..98272c1 100644 --- a/src/NuGet.Shared/Entities/PackageNotFoundException.cs +++ b/src/NuGet.Shared/Entities/PackageNotFoundException.cs @@ -11,7 +11,7 @@ public PackageNotFoundException() } public PackageNotFoundException(PackageIdentity package, Uri sourceUrl) - : this($"{package} not found in {sourceUrl.AbsoluteUri}.") + : this($"{package} not found in {sourceUrl.AbsoluteUri}") { } diff --git a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs index ce624c0..53d415c 100644 --- a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs @@ -90,14 +90,19 @@ ILogger log .GetDownloadResourceResultAsync(identity, downloadContext, downloadDirectory, log, ct); } - public static Task PushPackage( + public static async Task PushPackage( this PackageSource source, CancellationToken ct, LocalPackage package, ILogger log - ) => source - .GetResource() - .Push(package.Path, null, 100, true, s => s, s => s, true, log); + ) + { + await source + .GetResource() + .Push(package.Path, null, 100, true, s => s, s => s, true, log); + + return true; + } private static TResource GetResource(this PackageSource source) where TResource : class, INuGetResource From 36f4ce4a75fac500e998e5a1b018b7f88e37645f Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Mon, 20 Jan 2020 17:57:43 -0500 Subject: [PATCH 139/201] Improved NuGetUpdater logging --- src/NuGet.Shared/Helpers/MarkdownHelper.cs | 61 +++++-- src/NuGet.Shared/Helpers/PackageHelper.cs | 5 + .../Entities/LogDisplayOptions.cs | 21 +++ .../Extensions/FeedVersionExtensions.cs | 33 ++++ .../Extensions/UpdateOperationExtensions.cs | 33 +++- .../Extensions/UpdaterParametersExtension.cs | 5 - .../Extensions/XmlDocumentExtensions.cs | 4 +- src/NuGet.Updater/Log/UpdateOperation.cs | 45 ++---- src/NuGet.Updater/Log/UpdaterLogger.cs | 150 ++++++++++++------ src/NuGet.Updater/NuGetUpdater.cs | 5 +- 10 files changed, 264 insertions(+), 98 deletions(-) create mode 100644 src/NuGet.Updater/Entities/LogDisplayOptions.cs create mode 100644 src/NuGet.Updater/Extensions/FeedVersionExtensions.cs diff --git a/src/NuGet.Shared/Helpers/MarkdownHelper.cs b/src/NuGet.Shared/Helpers/MarkdownHelper.cs index 1c9c9f3..9fb5c61 100644 --- a/src/NuGet.Shared/Helpers/MarkdownHelper.cs +++ b/src/NuGet.Shared/Helpers/MarkdownHelper.cs @@ -31,33 +31,76 @@ internal static string CodeBlock(string text, bool isMultiline = false, string l return $"`{text}`"; } - internal class TableBuilder + internal static string Table( + string[] header, + string[][] rows, + bool prettify + ) => new TableBuilder() + .AddHeader(header) + .AddRows(rows) + .Build(prettify); + + private class TableBuilder { private readonly List _header = new List(); private readonly List _body = new List(); - public void AddHeader(string text) => _header.Add(text); + public TableBuilder AddHeader(params string[] header) + => this.Apply(b => b._header.AddRange(header)); - public void AddLine(params string[] columns) => _body.Add(columns.Trim().ToArray()); + public TableBuilder AddRows(params string[][] rows) + => this.Apply(b => _body.AddRange(rows)); - public string Build() + public string Build(bool prettify) { var builder = new StringBuilder(); if(_header.Any()) { builder - .AppendLine($"|{string.Join("|", _header)}|") - .AppendLine($"|{string.Join("|", Enumerable.Repeat("-", _header.Count))}|"); + .AppendLine(GetTableRow(_header, prettify)) + .AppendLine(GetTableRow(_header.Select(_ => "-"), prettify)); } - foreach(var line in _body) + foreach(var row in _body) { - builder.AppendLine($"|{string.Join("|", line)}|"); + builder.AppendLine(GetTableRow(row, prettify)); } return builder.ToString(); } + + private string GetTableRow(IEnumerable row, bool prettify) + => "|" + + row.Select((c, index) => GetTableCellContent(c, index, prettify)).JoinBy("|") + + "|"; + + private string GetTableCellContent(string content, int columnIndex, bool prettify) + { + var columnLength = prettify ? GetColumnLength(columnIndex) + 2 : 0; + + if(columnLength <= content.Length) + { + return content; + } + else if(content == "-") + { + return string.Join("", Enumerable.Repeat(content, columnLength)); + } + else + { + return content + .PadLeft(content.Length + 1) + .PadRight(columnLength); + } + } + + private int GetColumnLength(int index) + => _body + .Select(l => l.ElementAtOrDefault(index)) + .Concat(_header.ElementAtOrDefault(index)) + .Trim() + .Max(s => s.Length); } - } +} } diff --git a/src/NuGet.Shared/Helpers/PackageHelper.cs b/src/NuGet.Shared/Helpers/PackageHelper.cs index 470e5db..e7a232e 100644 --- a/src/NuGet.Shared/Helpers/PackageHelper.cs +++ b/src/NuGet.Shared/Helpers/PackageHelper.cs @@ -11,6 +11,11 @@ internal static class PackageHelper public static string GetUrl(string packageId, NuGetVersion version, Uri feedUri) { + if(feedUri == null) + { + return default; + } + if(feedUri.AbsoluteUri.StartsWith("https://api.nuget.org", StringComparison.OrdinalIgnoreCase)) { return $"https://www.nuget.org/packages/{packageId}/{version.ToFullString()}"; diff --git a/src/NuGet.Updater/Entities/LogDisplayOptions.cs b/src/NuGet.Updater/Entities/LogDisplayOptions.cs new file mode 100644 index 0000000..5b8b430 --- /dev/null +++ b/src/NuGet.Updater/Entities/LogDisplayOptions.cs @@ -0,0 +1,21 @@ +namespace NuGet.Updater.Entities +{ + internal struct LogDisplayOptions + { + public static readonly LogDisplayOptions Summary = new LogDisplayOptions + { + IncludeUrls = true, + PrettifyTable = false, + }; + + public static readonly LogDisplayOptions Console = new LogDisplayOptions + { + IncludeUrls = false, + PrettifyTable = true, + }; + + public bool IncludeUrls { get; set; } + + public bool PrettifyTable { get; set; } + } +} diff --git a/src/NuGet.Updater/Extensions/FeedVersionExtensions.cs b/src/NuGet.Updater/Extensions/FeedVersionExtensions.cs new file mode 100644 index 0000000..7ff149c --- /dev/null +++ b/src/NuGet.Updater/Extensions/FeedVersionExtensions.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Shared.Entities; +using NuGet.Updater.Entities; + +namespace NuGet.Updater.Extensions +{ + public static class FeedVersionExtensions + { + /// + /// Gets the latest version for the given reference by looking up first in a list of known packages. + /// Useful in the cases where refernces to multiple versions of the same packages are found. + /// + public static async Task GetLatestVersion( + this PackageReference reference, + CancellationToken ct, + IEnumerable knownPackages, + UpdaterParameters parameters + ) + { + var knownVersion = knownPackages.FirstOrDefault(p => p.PackageId == reference.Identity.Id)?.Version; + + if(knownVersion == null) + { + knownVersion = await reference.GetLatestVersion(ct, parameters); + } + + return knownVersion; + } + } +} diff --git a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs b/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs index 1418e3c..f58eb77 100644 --- a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs +++ b/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs @@ -1,4 +1,5 @@ -using NuGet.Updater.Entities; +using NuGet.Shared.Extensions; +using NuGet.Updater.Entities; using NuGet.Updater.Log; namespace NuGet.Updater.Extensions @@ -17,5 +18,35 @@ public static class UpdateOperationExtensions OriginalVersion = operation.PreviousVersion.OriginalVersion, UpdatedVersion = operation.UpdatedVersion.OriginalVersion, }; + + public static bool ShouldProceed(this UpdateOperation operation) + => !operation.IsIgnored + && (operation.UpdatedVersion.IsGreaterThan(operation.PreviousVersion) || operation.IsDowngrade()); + + public static bool IsDowngrade(this UpdateOperation operation) => operation.PreviousVersion.IsGreaterThan(operation.UpdatedVersion) && operation.CanDowngrade; + + public static string GetLogMessage(this UpdateOperation operation) + { + if(operation.IsIgnored) + { + return $"Ignoring {operation.PackageId}"; + } + else if(operation.PreviousVersion == operation.UpdatedVersion) + { + return $"Skipping {operation.PackageId}: version {operation.UpdatedVersion} already found in {operation.FilePath}"; + } + else if(operation.IsDowngrade()) + { + return $"Downgrading {operation.PackageId} from {operation.PreviousVersion} to {operation.UpdatedVersion} in {operation.FilePath}"; + } + else if(operation.ShouldProceed()) + { + return $"Updating {operation.PackageId} from {operation.PreviousVersion} to {operation.UpdatedVersion} in {operation.FilePath}"; + } + else + { + return $"Skipping {operation.PackageId}: version {operation.PreviousVersion} found in {operation.FilePath}, version {operation.UpdatedVersion} found in {operation.FeedUri}"; + } + } } } diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs index c85a74c..33c7ef5 100644 --- a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs @@ -44,11 +44,6 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Downgrading packages if a lower version is found"; } - if (parameters.PackagesToIgnore?.Any() ?? false) - { - yield return $"- Ignoring {MarkdownHelper.CodeBlocksEnumeration(parameters.PackagesToIgnore)}"; - } - if (parameters.PackagesToUpdate?.Any() ?? false) { yield return $"- Updating only {MarkdownHelper.CodeBlocksEnumeration(parameters.PackagesToUpdate)}"; diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index 62b0d74..86b6d85 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -42,7 +42,7 @@ UpdateOperation operation { var currentOperation = operation.WithPreviousVersion(packageVersion); - if(currentOperation.ShouldProceed) + if(currentOperation.ShouldProceed()) { packageReference.SetAttributeOrChild("Version", currentOperation.UpdatedVersion.ToString()); } @@ -113,7 +113,7 @@ UpdateOperation operation { var currentOperation = operation.WithPreviousVersion(versionNodeValue); - if(currentOperation.ShouldProceed) + if(currentOperation.ShouldProceed()) { node.SetAttribute("version", currentOperation.UpdatedVersion.ToString()); } diff --git a/src/NuGet.Updater/Log/UpdateOperation.cs b/src/NuGet.Updater/Log/UpdateOperation.cs index 40a707d..dae8a3e 100644 --- a/src/NuGet.Updater/Log/UpdateOperation.cs +++ b/src/NuGet.Updater/Log/UpdateOperation.cs @@ -1,15 +1,17 @@ using System; -using System.IO; +using NuGet.Packaging.Core; using NuGet.Shared.Entities; -using NuGet.Shared.Extensions; -using NuGet.Updater.Entities; using NuGet.Versioning; namespace NuGet.Updater.Log { public class UpdateOperation { - private readonly bool _canDowngrade; + public UpdateOperation(PackageIdentity identity, bool isIgnored) + : this(identity.Id, identity.Version, null, null, null, canDowngrade: false) + { + IsIgnored = isIgnored; + } public UpdateOperation(string packageId, FeedVersion updatedVersion, bool canDowngrade) : this(packageId, previousVersion: null, updatedVersion, filePath: null, canDowngrade) @@ -23,8 +25,7 @@ private UpdateOperation(string packageId, NuGetVersion previousVersion, FeedVers private UpdateOperation(string packageId, NuGetVersion previousVersion, NuGetVersion updatedVersion, Uri feedUri, string filePath, bool canDowngrade) { - _canDowngrade = canDowngrade; - + CanDowngrade = canDowngrade; PackageId = packageId; PreviousVersion = previousVersion; UpdatedVersion = updatedVersion; @@ -32,6 +33,10 @@ private UpdateOperation(string packageId, NuGetVersion previousVersion, NuGetVer FeedUri = feedUri; } + public bool IsIgnored { get; } + + public bool CanDowngrade { get; } + public string PackageId { get; } public NuGetVersion PreviousVersion { get; } @@ -42,37 +47,13 @@ private UpdateOperation(string packageId, NuGetVersion previousVersion, NuGetVer public Uri FeedUri { get; } - public bool ShouldProceed => UpdatedVersion.IsGreaterThan(PreviousVersion) || ShouldDowngrade; - - public bool ShouldDowngrade => PreviousVersion.IsGreaterThan(UpdatedVersion) && _canDowngrade; - - public string GetLogMessage() - { - if(PreviousVersion == UpdatedVersion) - { - return $"Version [{UpdatedVersion}] of [{PackageId}] already found in [{FilePath}]. Skipping."; - } - else if(ShouldDowngrade) - { - return $"Downgrading [{PackageId}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; - } - else if(ShouldProceed) - { - return $"Updating [{PackageId}] from [{PreviousVersion}] to [{UpdatedVersion}] in [{FilePath}]"; - } - else - { - return $"Version [{PreviousVersion}] of [{PackageId}] found in [{FilePath}]. Higher than [{UpdatedVersion}]. Skipping."; - } - } - public UpdateOperation WithPreviousVersion(string version) => new UpdateOperation( PackageId, previousVersion: new NuGetVersion(version), UpdatedVersion, FeedUri, FilePath, - _canDowngrade + CanDowngrade ); public UpdateOperation WithFilePath(string filePath) => new UpdateOperation( @@ -81,7 +62,7 @@ public string GetLogMessage() UpdatedVersion, FeedUri, filePath, - _canDowngrade + CanDowngrade ); } } diff --git a/src/NuGet.Updater/Log/UpdaterLogger.cs b/src/NuGet.Updater/Log/UpdaterLogger.cs index 4159caa..725a0df 100644 --- a/src/NuGet.Updater/Log/UpdaterLogger.cs +++ b/src/NuGet.Updater/Log/UpdaterLogger.cs @@ -8,11 +8,11 @@ using NuGet.Updater.Entities; using NuGet.Updater.Extensions; using NuGet.Updater.Helpers; -using Uno.Extensions; +using NuGet.Versioning; namespace NuGet.Updater.Log { - public class UpdaterLogger : ILogger + public class UpdaterLogger : ILogger, IEqualityComparer { private readonly List _updateOperations = new List(); private readonly TextWriter _writer; @@ -37,7 +37,7 @@ public UpdaterLogger(TextWriter writer, TextWriter summaryWriter = null) public void Write(IEnumerable operations) { - foreach (var o in operations) + foreach(var o in operations) { Write(o); } @@ -51,18 +51,18 @@ public void Write(UpdateOperation operation) public void WriteSummary(UpdaterParameters parameters) { - foreach (var line in GetSummary(parameters, includeUrl: true)) + foreach(var line in GetSummary(parameters, LogDisplayOptions.Console)) { Write(line); } - if (_summaryWriter != null) + if(_summaryWriter != null) { try { - _summaryWriter.Write(string.Join(Environment.NewLine, GetSummary(parameters, includeUrl: true))); + _summaryWriter.Write(string.Join(Environment.NewLine, GetSummary(parameters, LogDisplayOptions.Summary))); } - catch (Exception ex) + catch(Exception ex) { Write($"Failed to write summary. Reason : {ex.Message}"); } @@ -73,21 +73,16 @@ public IEnumerable GetResult() => _updateOperations .Select(o => o.ToUpdateResult()) .Distinct(); - private IEnumerable GetSummary(UpdaterParameters parameters, bool includeUrl = false) + private IEnumerable GetSummary(UpdaterParameters parameters, LogDisplayOptions options) { yield return $"# Package update summary"; - if (_updateOperations.Count == 0) + if(_updateOperations.Count == 0) { - yield return $"No packages have been updated."; + yield return $"No packages have been updated"; } - foreach(var message in LogPackageOperations(_updateOperations.Where(o => o.ShouldProceed), isUpdate: true, includeUrl)) - { - yield return message; - } - - foreach(var message in LogPackageOperations(_updateOperations.Where(o => !o.ShouldProceed), isUpdate: false, includeUrl)) + foreach(var message in LogPackageOperations(_updateOperations, options)) { yield return message; } @@ -98,50 +93,103 @@ private IEnumerable GetSummary(UpdaterParameters parameters, bool includ } } - private IEnumerable LogPackageOperations(IEnumerable operations, bool isUpdate, bool includeUrl) + private IEnumerable LogPackageOperations(IEnumerable operations, LogDisplayOptions options) { - if(operations.None()) + var ignores = operations + .Where(o => o.IsIgnored) + .Distinct(this) + .ToArray(); + + if(ignores.Any()) { - yield break; + yield return $"## Ignored {ignores.Length} packages"; + + yield return GetOperationsTable( + ignores, + new Dictionary> + { + { "Package", o => o.PackageId }, + { "Version", o => GetPreviousVersionText(o, options.IncludeUrls) }, + }, + options.PrettifyTable + ); } - var packages = operations - .Select(o => ( - name: o.PackageId, - oldVersion: o.PreviousVersion, - newVersion: isUpdate ? o.UpdatedVersion : o.PreviousVersion, - uri: o.FeedUri - )) - .Distinct() + var updates = operations + .Where(o => o.ShouldProceed()) + .Distinct(this) .ToArray(); - yield return $"## {(isUpdate ? "Updated" : "Skipped")} {packages.Length} packages"; - - var tableBuilder = new MarkdownHelper.TableBuilder(); - - tableBuilder.AddHeader("Package"); - - if(isUpdate) + if(updates.Any()) { - tableBuilder.AddHeader("Original version"); - tableBuilder.AddHeader("Updated version"); - } - else - { - tableBuilder.AddHeader("Version"); + yield return $"## Updated {updates.Length} packages"; + + yield return GetOperationsTable( + updates, + new Dictionary> + { + { "Package", o => o.PackageId }, + { "Referenced version", o => GetPreviousVersionText(o, options.IncludeUrls) }, + { "Updated version", o => GetUpdatedVersionText(o, options.IncludeUrls) }, + }, + options.PrettifyTable + ); } - foreach(var p in packages) - { - var oldVersionUrl = includeUrl ? PackageHelper.GetUrl(p.name, p.oldVersion, p.uri) : default; - var newVersionUrl = includeUrl ? PackageHelper.GetUrl(p.name, p.newVersion, p.uri) : default; + var skips = operations + .Where(o => !o.IsIgnored && !o.ShouldProceed()) + .Distinct(this) + .ToArray(); - tableBuilder.AddLine(p.name, MarkdownHelper.Link(p.oldVersion.OriginalVersion, oldVersionUrl), isUpdate ? MarkdownHelper.Link(p.newVersion.OriginalVersion, newVersionUrl) : null); + if(skips.Any()) + { + yield return $"## Skipped {skips.Length} packages"; + + yield return GetOperationsTable( + skips, + new Dictionary> + { + { "Package", o => o.PackageId }, + { "Referenced version", o => GetPreviousVersionText(o, options.IncludeUrls) }, + { "Available version", o => GetUpdatedVersionText(o, options.IncludeUrls) }, + }, + options.PrettifyTable + ); } - - yield return tableBuilder.Build(); } + private string GetOperationsTable( + IEnumerable operations, + Dictionary> columnBuilder, + bool prettify + ) => MarkdownHelper.Table( + columnBuilder.Keys.ToArray(), + operations.Select(o => columnBuilder.Select(p => p.Value(o)).ToArray()).ToArray(), + prettify + ); + + private string GetUpdatedVersionText(UpdateOperation operation, bool includeUrl) + => GetVersionText( + operation.UpdatedVersion, + operation.PackageId, + operation.FeedUri, + includeUrl + ); + + private string GetPreviousVersionText(UpdateOperation operation, bool includeUrl) + => GetVersionText( + operation.PreviousVersion, + operation.PackageId, + operation.FeedUri, + includeUrl + ); + + private string GetVersionText(NuGetVersion version, string packageId, Uri feedUri, bool includeUrl) + => MarkdownHelper.Link( + version.OriginalVersion, + includeUrl ? PackageHelper.GetUrl(packageId, version, feedUri) : null + ); + #region ILogger public void LogDebug(string data) => Log(LogLevel.Debug, data); @@ -165,5 +213,13 @@ private IEnumerable LogPackageOperations(IEnumerable op public async Task LogAsync(ILogMessage message) => Log(message); #endregion + + #region IEqualityComparer + public bool Equals(UpdateOperation x, UpdateOperation y) + => x != null && y != null + && (x.PackageId == y.PackageId && x.PreviousVersion == y.PreviousVersion && x.UpdatedVersion == x.UpdatedVersion); + + public int GetHashCode(UpdateOperation obj) => obj?.PackageId.GetHashCode() ?? 0; + #endregion } } diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index c0f1db8..01be0cc 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -122,10 +122,11 @@ internal async Task GetPackages(CancellationToken ct) (_parameters.PackagesToUpdate.Any() && !_parameters.PackagesToUpdate.Contains(reference.Identity.Id)) ) { + _log.Write(new UpdateOperation(reference.Identity, isIgnored: true)); continue; } - packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, _parameters))); + packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, packages, _parameters))); } return packages.ToArray(); @@ -173,7 +174,7 @@ Dictionary documents updates = document.UpdatePackageReferences(currentOperation); } - if(!_parameters.IsDryRun && updates.Any(u => u.ShouldProceed)) + if(!_parameters.IsDryRun && updates.Any(u => u.ShouldProceed())) { await document.Save(ct, path); } From 637323bb18e92cab5779bb83c8e2b8ef181b5b23 Mon Sep 17 00:00:00 2001 From: Xiaotian Gu <32882405+XiaotianNetlift@users.noreply.github.com> Date: Fri, 7 Feb 2020 10:44:45 -0500 Subject: [PATCH 140/201] fixed broken link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3220dec..03a1b14 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ NuGet packages manipulations made easy. ## NuGet.Updater -Documentation about the NuGet.Updater can be found [here](src/NuGet.Updater.Tool/README.md) +Documentation about the NuGet.Updater can be found [here](src/NuGet.Updater.Tool/Readme.md) ## NuGet.Downloader From 3888ff66d67876f2cfc81c413237231f32e9e5a5 Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Fri, 7 Feb 2020 16:21:54 -0500 Subject: [PATCH 141/201] refactored updater tool for unit testing --- src/NuGet.Updater.Tool/ConsoleArgsParser.cs | 79 +++++++++++++++++++++ src/NuGet.Updater.Tool/Program.cs | 60 ++-------------- 2 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 src/NuGet.Updater.Tool/ConsoleArgsParser.cs diff --git a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs b/src/NuGet.Updater.Tool/ConsoleArgsParser.cs new file mode 100644 index 0000000..e978c85 --- /dev/null +++ b/src/NuGet.Updater.Tool/ConsoleArgsParser.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Mono.Options; +using Newtonsoft.Json; +using NuGet.Packaging; +using NuGet.Shared.Entities; +using NuGet.Updater.Entities; +using NuGet.Versioning; + +namespace NuGet.Updater.Tool +{ + public static class ConsoleArgsParser + { + public static (ConsoleArgsContext Context, OptionSet Options) Parse(string[] args) + { + var isParameterSet = false; + var context = new ConsoleArgsContext + { + Parameters = new UpdaterParameters { UpdateTarget = FileType.All }, + }; + + var options = new OptionSet + { + { "help|h", "Displays this help screen", s => context.IsHelp = true }, + { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.SolutionRoot = s) }, + { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => Set(p => p.Feeds.Add(PackageFeed.FromString(s)))}, + { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, + { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, + { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.PackagesToUpdate.Add(s)) }, + { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.PackageAuthor = s)}, + { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => context.SummaryFile = s }, + { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, + { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Feeds.Add(PackageFeed.NuGetOrg)) }, + { "silent", "Suppress all output from NuGet Updater", _ => context.IsSilent = true }, + { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, + { "dryrun", "Runs the updater but doesn't write the updates to files.", _ => Set(p => p.IsDryRun = true) }, + { "result|r=", "The path to the file where the update result should be saved.", s => context.ResultFile = s }, + { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", s => Set(p => p.VersionOverrides.AddRange(LoadManualOperations(s))) }, + }; + + options.Parse(args); + context.IsHelp |= !isParameterSet; + + return (context, options); + + void Set(Action set) + { + set(context.Parameters); + isParameterSet = true; + } + } + + private static Dictionary LoadManualOperations(string inputFilePath) + { + using(var fileReader = File.OpenText(inputFilePath)) + using(var jsonReader = new JsonTextReader(fileReader)) + { + var result = JsonSerializer.CreateDefault().Deserialize>(jsonReader); + + return result.ToDictionary(r => r.PackageId, r => new NuGetVersion(r.UpdatedVersion)); + } + } + + public class ConsoleArgsContext + { + public bool IsHelp { get; set; } + + public bool IsSilent { get; set; } + + public string SummaryFile { get; set; } + + public string ResultFile { get; set; } + + public UpdaterParameters Parameters { get; set; } + } + } +} diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 33e96c4..27310ca 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -17,47 +17,14 @@ namespace NuGet.Updater.Tool { public class Program { - private static bool _isParameterSet = default; - private static UpdaterParameters _parameters = default; - public static async Task Main(string[] args) { try { - var isHelp = false; - var isSilent = false; - string summaryFile = default; - string resultFile = default; - - var options = new OptionSet - { - { "help|h", "Displays this help screen", s => isHelp = true }, - { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.SolutionRoot = s) }, - { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => Set(p => p.Feeds.Add(PackageFeed.FromString(s)))}, - { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, - { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, - { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.PackagesToUpdate.Add(s)) }, - { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.PackageAuthor = s)}, - { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => summaryFile = s }, - { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, - { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Feeds.Add(PackageFeed.NuGetOrg)) }, - { "silent", "Suppress all output from NuGet Updater", _ => isSilent = true }, - { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, - { "dryrun", "Runs the updater but doesn't write the updates to files.", _ => Set(p => p.IsDryRun = true) }, - { "result|r=", "The path to the file where the update result should be saved.", s => resultFile = s }, - { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", s => Set(p => p.VersionOverrides.AddRange(LoadManualOperations(s))) }, - }; - - _isParameterSet = false; - _parameters = new UpdaterParameters - { - SolutionRoot = Environment.CurrentDirectory, - UpdateTarget = FileType.All, - }; - - options.Parse(args); + var (context, options) = ConsoleArgsParser.Parse(args); + context.Parameters.SolutionRoot ??= Environment.CurrentDirectory; - if(isHelp || !_isParameterSet) + if(context.IsHelp) { Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); Console.WriteLine(); @@ -65,11 +32,11 @@ public static async Task Main(string[] args) } else { - var updater = new NuGetUpdater(_parameters, isSilent ? null : Console.Out, GetSummaryWriter(summaryFile)); + var updater = new NuGetUpdater(context.Parameters, context.IsSilent ? null : Console.Out, GetSummaryWriter(context.SummaryFile)); var result = await updater.UpdatePackages(CancellationToken.None); - Save(result, resultFile); + Save(result, context.ResultFile); } } catch(Exception ex) @@ -78,12 +45,6 @@ public static async Task Main(string[] args) } } - private static void Set(Action set) - { - set(_parameters); - _isParameterSet = true; - } - private static TextWriter GetSummaryWriter(string summaryFile) => summaryFile == null ? null : new SimpleTextWriter(line => FileHelper.LogToFile(summaryFile, line)); private static void Save(IEnumerable result, string path) @@ -100,16 +61,5 @@ private static void Save(IEnumerable result, string path) serializer.Serialize(writer, result); } } - - private static Dictionary LoadManualOperations(string inputFilePath) - { - using(var fileReader = File.OpenText(inputFilePath)) - using(var jsonReader = new JsonTextReader(fileReader)) - { - var result = JsonSerializer.CreateDefault().Deserialize>(jsonReader); - - return result.ToDictionary(r => r.PackageId, r => new NuGetVersion(r.UpdatedVersion)); - } - } } } From 213db472a59b72f7d00b8470b6fd0b6141b3ba0a Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Fri, 7 Feb 2020 16:44:15 -0500 Subject: [PATCH 142/201] Added ConsoleArgsParserTests --- .../ConsoleArgsParserTests.cs | 31 +++++++++++++++++++ .../NuGet.Updater.Tests.csproj | 5 +-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs new file mode 100644 index 0000000..643186a --- /dev/null +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Updater.Tool; + +namespace NuGet.Updater.Tests +{ + [TestClass] + public class ConsoleArgsParserTests + { + [TestMethod] + public void Given_HelpArgument_ContextIsHelp() + { + var arguments = new[] { "-help" }; + var (context, _) = ConsoleArgsParser.Parse(arguments); + + Assert.IsTrue(context.IsHelp); + } + + [TestMethod] + public void Given_InvalidArgument_ContextIsError() + { + var arguments = new[] { "--absolutelyWrong" }; + var (context, _) = ConsoleArgsParser.Parse(arguments); + + //throw new NotImplementedException(); + Assert.IsTrue(context.IsHelp); + } + } +} diff --git a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj b/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj index cdbb4f9..6d03a73 100644 --- a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj +++ b/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj @@ -1,19 +1,20 @@  - netcoreapp2.1 + netcoreapp3.0 false - + + From a4bfed6de013f0ffd1f6635e23c04a37c1c2bc81 Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Fri, 7 Feb 2020 17:58:48 -0500 Subject: [PATCH 143/201] added handling for unrecognized argument --- .../ConsoleArgsParserTests.cs | 11 ++- src/NuGet.Updater.Tool/ConsoleArgsParser.cs | 92 +++++++++++++------ src/NuGet.Updater.Tool/Program.cs | 13 ++- 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index 643186a..1d182f5 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -13,19 +13,20 @@ public class ConsoleArgsParserTests public void Given_HelpArgument_ContextIsHelp() { var arguments = new[] { "-help" }; - var (context, _) = ConsoleArgsParser.Parse(arguments); + var context = ConsoleArgsParser.Parse(arguments); Assert.IsTrue(context.IsHelp); } [TestMethod] - public void Given_InvalidArgument_ContextIsError() + public void Given_UnrecognizedArgument_ContextHasError() { var arguments = new[] { "--absolutelyWrong" }; - var (context, _) = ConsoleArgsParser.Parse(arguments); + var context = ConsoleArgsParser.Parse(arguments); - //throw new NotImplementedException(); - Assert.IsTrue(context.IsHelp); + Assert.IsTrue(context.HasError); + Assert.AreEqual(context.Errors[0].Argument, arguments[0]); + Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.UnrecognizedArgument); } } } diff --git a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs b/src/NuGet.Updater.Tool/ConsoleArgsParser.cs index e978c85..8174b0b 100644 --- a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs +++ b/src/NuGet.Updater.Tool/ConsoleArgsParser.cs @@ -4,52 +4,56 @@ using System.Linq; using Mono.Options; using Newtonsoft.Json; -using NuGet.Packaging; using NuGet.Shared.Entities; using NuGet.Updater.Entities; using NuGet.Versioning; +using Uno.Extensions; namespace NuGet.Updater.Tool { public static class ConsoleArgsParser { - public static (ConsoleArgsContext Context, OptionSet Options) Parse(string[] args) - { - var isParameterSet = false; - var context = new ConsoleArgsContext - { - Parameters = new UpdaterParameters { UpdateTarget = FileType.All }, - }; + public static OptionSet GetOptions() => CreateOptionsFor(default); - var options = new OptionSet + private static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) + { + return new OptionSet { { "help|h", "Displays this help screen", s => context.IsHelp = true }, - { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.SolutionRoot = s) }, - { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => Set(p => p.Feeds.Add(PackageFeed.FromString(s)))}, - { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.TargetVersions.Add(s))}, - { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.PackagesToIgnore.Add(s)) }, - { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.PackagesToUpdate.Add(s)) }, - { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.PackageAuthor = s)}, + { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.Parameters.SolutionRoot = s) }, + { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => Set(p => p.Parameters.Feeds.Add(PackageFeed.FromString(s)))}, + { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.Parameters.TargetVersions.Add(s))}, + { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.Parameters.PackagesToIgnore.Add(s)) }, + { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.Parameters.PackagesToUpdate.Add(s)) }, + { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.Parameters.PackageAuthor = s)}, { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => context.SummaryFile = s }, - { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.IsDowngradeAllowed = true)}, - { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Feeds.Add(PackageFeed.NuGetOrg)) }, + { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.Parameters.IsDowngradeAllowed = true)}, + { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Parameters.Feeds.Add(PackageFeed.NuGetOrg)) }, { "silent", "Suppress all output from NuGet Updater", _ => context.IsSilent = true }, - { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Strict = true) }, - { "dryrun", "Runs the updater but doesn't write the updates to files.", _ => Set(p => p.IsDryRun = true) }, + { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Parameters.Strict = true) }, + { "dryrun", "Runs the updater but doesn't write the updates to files.", _ => Set(p => p.Parameters.IsDryRun = true) }, { "result|r=", "The path to the file where the update result should be saved.", s => context.ResultFile = s }, - { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", s => Set(p => p.VersionOverrides.AddRange(LoadManualOperations(s))) }, + { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", s => Set(p => p.Parameters.VersionOverrides.AddRange(LoadManualOperations(s))) }, }; - options.Parse(args); - context.IsHelp |= !isParameterSet; - - return (context, options); + void Set(Action setter) => context?.Apply(setter); + } - void Set(Action set) + public static ConsoleArgsContext Parse(string[] args) + { + if (args.Empty()) { - set(context.Parameters); - isParameterSet = true; + return new ConsoleArgsContext { IsHelp = true }; } + + var context = new ConsoleArgsContext + { + Parameters = new UpdaterParameters { UpdateTarget = FileType.All }, + }; + var unparsed = CreateOptionsFor(context).Parse(args); + context.Errors.AddRange(unparsed.Select(ConsoleArgError.UnrecognizedArgument)); + + return context; } private static Dictionary LoadManualOperations(string inputFilePath) @@ -65,6 +69,10 @@ private static Dictionary LoadManualOperations(string inpu public class ConsoleArgsContext { + public bool HasError => Errors.Any(); + + public IList Errors { get; } = new List(); + public bool IsHelp { get; set; } public bool IsSilent { get; set; } @@ -75,5 +83,35 @@ public class ConsoleArgsContext public UpdaterParameters Parameters { get; set; } } + + public class ConsoleArgError + { + public ErrorType Type { get; set; } + + public string Argument { get; set; } + + public Exception Exception { get; set; } + + public ConsoleArgError(string argument, ErrorType type, Exception e = null) + { + Argument = argument; + Type = type; + Exception = e; + } + + public string Message => Type switch + { + ErrorType.UnrecognizedArgument => "unrecognized argument: " + Argument, + _ => $"{Type}: " + Argument, + }; + + internal static ConsoleArgError UnrecognizedArgument(string argument) => new ConsoleArgError(argument, ErrorType.UnrecognizedArgument); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Self-Explantory")] + public enum ErrorType + { + UnrecognizedArgument, + } + } } } diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index 27310ca..b6d8a0d 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -21,17 +21,22 @@ public static async Task Main(string[] args) { try { - var (context, options) = ConsoleArgsParser.Parse(args); - context.Parameters.SolutionRoot ??= Environment.CurrentDirectory; + var context = ConsoleArgsParser.Parse(args); - if(context.IsHelp) + if (context.HasError) + { + Console.Error.WriteLine(context.Errors.FirstOrDefault().Message); + Environment.Exit(-1); + } + else if (context.IsHelp) { Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); Console.WriteLine(); - options.WriteOptionDescriptions(Console.Out); + ConsoleArgsParser.GetOptions().WriteOptionDescriptions(Console.Out); } else { + context.Parameters.SolutionRoot ??= Environment.CurrentDirectory; var updater = new NuGetUpdater(context.Parameters, context.IsSilent ? null : Console.Out, GetSummaryWriter(context.SummaryFile)); var result = await updater.UpdatePackages(CancellationToken.None); From 0c92379c32ac823ccf5d3946c8e23a56920ea53e Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Mon, 10 Feb 2020 17:24:06 -0500 Subject: [PATCH 144/201] added exception handling for argument parsing/assignment --- .../ConsoleArgsParserTests.cs | 28 ++++++- src/NuGet.Updater.Tool/ConsoleArgsParser.cs | 82 ++++++++++++++----- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index 1d182f5..12a720f 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.IO; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using NuGet.Updater.Tool; @@ -28,5 +29,30 @@ public void Given_UnrecognizedArgument_ContextHasError() Assert.AreEqual(context.Errors[0].Argument, arguments[0]); Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.UnrecognizedArgument); } + + [TestMethod] + [Ignore("fixme: Mono.Options recognizes `- asd` because it is parsed as parameter `-a` with a value of `sd`")] + public void Given_UnrecognizedArgument_ContextHasError2() + { + var arguments = new[] { "-asd" }; + var context = ConsoleArgsParser.Parse(arguments); + + Assert.IsTrue(context.HasError); + Assert.AreEqual(context.Errors[0].Argument, arguments[0]); + Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.UnrecognizedArgument); + } + + [TestMethod] + public void Given_InvalidArgumentParameter_ContextHasError() + { + const string MissingFile = @"c:\not\existing\file.mia"; + var arguments = new[] { $"--versionOverrides={MissingFile}" }; + var context = ConsoleArgsParser.Parse(arguments); + + Assert.IsTrue(context.HasError); + Assert.AreEqual(context.Errors[0].Argument, MissingFile); + Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.ValueParsingError); + Assert.IsInstanceOfType(context.Errors[0].Exception, typeof(DirectoryNotFoundException)); + } } } diff --git a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs b/src/NuGet.Updater.Tool/ConsoleArgsParser.cs index 8174b0b..40c352d 100644 --- a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs +++ b/src/NuGet.Updater.Tool/ConsoleArgsParser.cs @@ -19,24 +19,65 @@ private static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) { return new OptionSet { - { "help|h", "Displays this help screen", s => context.IsHelp = true }, - { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", s => Set(p => p.Parameters.SolutionRoot = s) }, - { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", s => Set(p => p.Parameters.Feeds.Add(PackageFeed.FromString(s)))}, - { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", s => Set(p => p.Parameters.TargetVersions.Add(s))}, - { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", s => Set(p => p.Parameters.PackagesToIgnore.Add(s)) }, - { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", s => Set(p => p.Parameters.PackagesToUpdate.Add(s)) }, - { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", s => Set(p => p.Parameters.PackageAuthor = s)}, - { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", s => context.SummaryFile = s }, - { "allowDowngrade|d", "Whether package downgrade is allowed", s => Set(p => p.Parameters.IsDowngradeAllowed = true)}, - { "useNuGetorg|n", "Whether to use packages from NuGet.org", _ => Set(p => p.Parameters.Feeds.Add(PackageFeed.NuGetOrg)) }, - { "silent", "Suppress all output from NuGet Updater", _ => context.IsSilent = true }, - { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", _ => Set(p => p.Parameters.Strict = true) }, - { "dryrun", "Runs the updater but doesn't write the updates to files.", _ => Set(p => p.Parameters.IsDryRun = true) }, - { "result|r=", "The path to the file where the update result should be saved.", s => context.ResultFile = s }, - { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", s => Set(p => p.Parameters.VersionOverrides.AddRange(LoadManualOperations(s))) }, + { "help|h", "Displays this help screen", TrySet(_ => context.IsHelp = true) }, + { "solution=|s=", "The {path} to the solution or folder to update; defaults to the current folder", TrySet(x => context.Parameters.SolutionRoot = x) }, + { "feed=|f=", "A NuGet feed to use for the update; a private feed can be specified with the format {url|accessToken}; can be specified multiple times", TryParseAndSet(PackageFeed.FromString, x => context.Parameters.Feeds.Add(x)) }, + { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", TrySet(x => context.Parameters.TargetVersions.Add(x)) }, + { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", TrySet(x => context.Parameters.PackagesToIgnore.Add(x)) }, + { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", TrySet(x => context.Parameters.PackagesToUpdate.Add(x)) }, + { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", TrySet(x => context.Parameters.PackageAuthor = x)}, + { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", TrySet(x => context.SummaryFile = x) }, + { "allowDowngrade|d", "Whether package downgrade is allowed", TrySet(x => context.Parameters.IsDowngradeAllowed = true)}, + { "useNuGetorg|n", "Whether to use packages from NuGet.org", TrySet(_ => context.Parameters.Feeds.Add(PackageFeed.NuGetOrg)) }, + { "silent", "Suppress all output from NuGet Updater", TrySet(_ => context.IsSilent = true) }, + { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", TrySet(_ => context.Parameters.Strict = true) }, + { "dryrun", "Runs the updater but doesn't write the updates to files.", TrySet(_ => context.Parameters.IsDryRun = true) }, + { "result|r=", "The path to the file where the update result should be saved.", TrySet(x => context.ResultFile = x) }, + { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", TryParseAndSet(LoadManualOperations, x => context.Parameters.VersionOverrides.AddRange(x)) }, }; - void Set(Action setter) => context?.Apply(setter); + Action TrySet(Action set) + { + return value => + { + if (context != null) + { + try + { + set(value); + } + catch(Exception e) + { + context.Errors.Add(new ConsoleArgError(value, ConsoleArgError.ErrorType.ValueAssignmentError, e)); + } + } + }; + } + + Action TryParseAndSet(Func parse, Action set) + { + return value => + { + if(context != null) + { + var isParsing = true; + try + { + var parsed = parse(value); + isParsing = false; + set(parsed); + } + catch(Exception e) + { + context.Errors.Add(new ConsoleArgError( + value, + isParsing ? ConsoleArgError.ErrorType.ValueParsingError : ConsoleArgError.ErrorType.ValueAssignmentError, + e + )); + } + } + }; + } } public static ConsoleArgsContext Parse(string[] args) @@ -51,7 +92,7 @@ public static ConsoleArgsContext Parse(string[] args) Parameters = new UpdaterParameters { UpdateTarget = FileType.All }, }; var unparsed = CreateOptionsFor(context).Parse(args); - context.Errors.AddRange(unparsed.Select(ConsoleArgError.UnrecognizedArgument)); + context.Errors.AddRange(unparsed.Select(x => new ConsoleArgError(x, ConsoleArgError.ErrorType.UnrecognizedArgument))); return context; } @@ -102,15 +143,18 @@ public ConsoleArgError(string argument, ErrorType type, Exception e = null) public string Message => Type switch { ErrorType.UnrecognizedArgument => "unrecognized argument: " + Argument, + ErrorType.ValueAssignmentError => "error while trying to assign value: " + Argument, + ErrorType.ValueParsingError => "error while trying to parse value: " + Argument, + _ => $"{Type}: " + Argument, }; - internal static ConsoleArgError UnrecognizedArgument(string argument) => new ConsoleArgError(argument, ErrorType.UnrecognizedArgument); - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Self-Explantory")] public enum ErrorType { UnrecognizedArgument, + ValueAssignmentError, + ValueParsingError, } } } From 339b7d8bbb5466951aff213d7122e7eee7bca6da Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Tue, 11 Feb 2020 13:35:38 -0500 Subject: [PATCH 145/201] refactored nested classes --- .../ConsoleArgsParserTests.cs | 23 ++--- .../Arguments/ConsoleArgError.cs | 31 ++++++ .../Arguments/ConsoleArgErrorType.cs | 13 +++ .../ConsoleArgsContext.Properties.cs | 27 ++++++ .../ConsoleArgsContext.cs} | 94 +++++-------------- src/NuGet.Updater.Tool/Program.cs | 9 +- 6 files changed, 108 insertions(+), 89 deletions(-) create mode 100644 src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs create mode 100644 src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs create mode 100644 src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs rename src/NuGet.Updater.Tool/{ConsoleArgsParser.cs => Arguments/ConsoleArgsContext.cs} (69%) diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index 12a720f..df8abd0 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -3,18 +3,20 @@ using System.IO; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Updater.Tool; +using NuGet.Updater.Tool.Arguments; namespace NuGet.Updater.Tests { [TestClass] public class ConsoleArgsParserTests { + private const string NotExistingFilePath = @"c:\not\existing\file.mia"; + [TestMethod] public void Given_HelpArgument_ContextIsHelp() { var arguments = new[] { "-help" }; - var context = ConsoleArgsParser.Parse(arguments); + var context = ConsoleArgsContext.Parse(arguments); Assert.IsTrue(context.IsHelp); } @@ -23,11 +25,11 @@ public void Given_HelpArgument_ContextIsHelp() public void Given_UnrecognizedArgument_ContextHasError() { var arguments = new[] { "--absolutelyWrong" }; - var context = ConsoleArgsParser.Parse(arguments); + var context = ConsoleArgsContext.Parse(arguments); Assert.IsTrue(context.HasError); Assert.AreEqual(context.Errors[0].Argument, arguments[0]); - Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.UnrecognizedArgument); + Assert.AreEqual(context.Errors[0].Type, ConsoleArgErrorType.UnrecognizedArgument); } [TestMethod] @@ -35,23 +37,22 @@ public void Given_UnrecognizedArgument_ContextHasError() public void Given_UnrecognizedArgument_ContextHasError2() { var arguments = new[] { "-asd" }; - var context = ConsoleArgsParser.Parse(arguments); + var context = ConsoleArgsContext.Parse(arguments); Assert.IsTrue(context.HasError); Assert.AreEqual(context.Errors[0].Argument, arguments[0]); - Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.UnrecognizedArgument); + Assert.AreEqual(context.Errors[0].Type, ConsoleArgErrorType.UnrecognizedArgument); } [TestMethod] public void Given_InvalidArgumentParameter_ContextHasError() { - const string MissingFile = @"c:\not\existing\file.mia"; - var arguments = new[] { $"--versionOverrides={MissingFile}" }; - var context = ConsoleArgsParser.Parse(arguments); + var arguments = new[] { $"--versionOverrides={NotExistingFilePath}" }; + var context = ConsoleArgsContext.Parse(arguments); Assert.IsTrue(context.HasError); - Assert.AreEqual(context.Errors[0].Argument, MissingFile); - Assert.AreEqual(context.Errors[0].Type, ConsoleArgsParser.ConsoleArgError.ErrorType.ValueParsingError); + Assert.AreEqual(context.Errors[0].Argument, NotExistingFilePath); + Assert.AreEqual(context.Errors[0].Type, ConsoleArgErrorType.ValueParsingError); Assert.IsInstanceOfType(context.Errors[0].Exception, typeof(DirectoryNotFoundException)); } } diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs new file mode 100644 index 0000000..0ca16a2 --- /dev/null +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NuGet.Updater.Tool.Arguments +{ + public class ConsoleArgError + { + public ConsoleArgErrorType Type { get; set; } + + public string Argument { get; set; } + + public Exception Exception { get; set; } + + public ConsoleArgError(string argument, ConsoleArgErrorType type, Exception e = null) + { + Argument = argument; + Type = type; + Exception = e; + } + + public string Message => Type switch + { + ConsoleArgErrorType.UnrecognizedArgument => "unrecognized argument: " + Argument, + ConsoleArgErrorType.ValueAssignmentError => "error while trying to assign value: " + Argument, + ConsoleArgErrorType.ValueParsingError => "error while trying to parse value: " + Argument, + + _ => $"{Type}: " + Argument, + }; + } +} diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs new file mode 100644 index 0000000..a94821d --- /dev/null +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NuGet.Updater.Tool.Arguments +{ + public enum ConsoleArgErrorType + { + UnrecognizedArgument, + ValueAssignmentError, + ValueParsingError, + } +} diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs new file mode 100644 index 0000000..7cd60a2 --- /dev/null +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NuGet.Updater.Entities; + +namespace NuGet.Updater.Tool.Arguments +{ + public partial class ConsoleArgsContext + { + private ConsoleArgsContext() { } + + public bool HasError => Errors.Any(); + + public IList Errors { get; } = new List(); + + public bool IsHelp { get; set; } + + public bool IsSilent { get; set; } + + public string SummaryFile { get; set; } + + public string ResultFile { get; set; } + + public UpdaterParameters Parameters { get; set; } + } +} diff --git a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs similarity index 69% rename from src/NuGet.Updater.Tool/ConsoleArgsParser.cs rename to src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs index 40c352d..2e671fb 100644 --- a/src/NuGet.Updater.Tool/ConsoleArgsParser.cs +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs @@ -9,13 +9,28 @@ using NuGet.Versioning; using Uno.Extensions; -namespace NuGet.Updater.Tool +namespace NuGet.Updater.Tool.Arguments { - public static class ConsoleArgsParser + public partial class ConsoleArgsContext { - public static OptionSet GetOptions() => CreateOptionsFor(default); + public static ConsoleArgsContext Parse(string[] args) + { + if(args.Length == 0) + { + return new ConsoleArgsContext { IsHelp = true }; + } + + var context = new ConsoleArgsContext + { + Parameters = new UpdaterParameters { UpdateTarget = FileType.All }, + }; + var unparsed = CreateOptionsFor(context).Parse(args); + context.Errors.AddRange(unparsed.Select(x => new ConsoleArgError(x, ConsoleArgErrorType.UnrecognizedArgument))); + + return context; + } - private static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) + internal static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) { return new OptionSet { @@ -48,7 +63,7 @@ Action TrySet(Action set) } catch(Exception e) { - context.Errors.Add(new ConsoleArgError(value, ConsoleArgError.ErrorType.ValueAssignmentError, e)); + context.Errors.Add(new ConsoleArgError(value, ConsoleArgErrorType.ValueAssignmentError, e)); } } }; @@ -71,7 +86,7 @@ Action TryParseAndSet(Func parse, Action set) { context.Errors.Add(new ConsoleArgError( value, - isParsing ? ConsoleArgError.ErrorType.ValueParsingError : ConsoleArgError.ErrorType.ValueAssignmentError, + isParsing ? ConsoleArgErrorType.ValueParsingError : ConsoleArgErrorType.ValueAssignmentError, e )); } @@ -80,22 +95,7 @@ Action TryParseAndSet(Func parse, Action set) } } - public static ConsoleArgsContext Parse(string[] args) - { - if (args.Empty()) - { - return new ConsoleArgsContext { IsHelp = true }; - } - - var context = new ConsoleArgsContext - { - Parameters = new UpdaterParameters { UpdateTarget = FileType.All }, - }; - var unparsed = CreateOptionsFor(context).Parse(args); - context.Errors.AddRange(unparsed.Select(x => new ConsoleArgError(x, ConsoleArgError.ErrorType.UnrecognizedArgument))); - - return context; - } + public void WriteOptionDescriptions(TextWriter writer) => CreateOptionsFor(default).WriteOptionDescriptions(writer); private static Dictionary LoadManualOperations(string inputFilePath) { @@ -107,55 +107,5 @@ private static Dictionary LoadManualOperations(string inpu return result.ToDictionary(r => r.PackageId, r => new NuGetVersion(r.UpdatedVersion)); } } - - public class ConsoleArgsContext - { - public bool HasError => Errors.Any(); - - public IList Errors { get; } = new List(); - - public bool IsHelp { get; set; } - - public bool IsSilent { get; set; } - - public string SummaryFile { get; set; } - - public string ResultFile { get; set; } - - public UpdaterParameters Parameters { get; set; } - } - - public class ConsoleArgError - { - public ErrorType Type { get; set; } - - public string Argument { get; set; } - - public Exception Exception { get; set; } - - public ConsoleArgError(string argument, ErrorType type, Exception e = null) - { - Argument = argument; - Type = type; - Exception = e; - } - - public string Message => Type switch - { - ErrorType.UnrecognizedArgument => "unrecognized argument: " + Argument, - ErrorType.ValueAssignmentError => "error while trying to assign value: " + Argument, - ErrorType.ValueParsingError => "error while trying to parse value: " + Argument, - - _ => $"{Type}: " + Argument, - }; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Self-Explantory")] - public enum ErrorType - { - UnrecognizedArgument, - ValueAssignmentError, - ValueParsingError, - } - } } } diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index b6d8a0d..da63c0b 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -4,14 +4,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Mono.Options; using Newtonsoft.Json; -using NuGet.Packaging; -using NuGet.Shared.Entities; using NuGet.Shared.Helpers; using NuGet.Shared.Log; using NuGet.Updater.Entities; -using NuGet.Versioning; +using NuGet.Updater.Tool.Arguments; namespace NuGet.Updater.Tool { @@ -21,7 +18,7 @@ public static async Task Main(string[] args) { try { - var context = ConsoleArgsParser.Parse(args); + var context = ConsoleArgsContext.Parse(args); if (context.HasError) { @@ -32,7 +29,7 @@ public static async Task Main(string[] args) { Console.WriteLine("NuGet Updater is a tool allowing the automatic update of the NuGet packages found in a solution"); Console.WriteLine(); - ConsoleArgsParser.GetOptions().WriteOptionDescriptions(Console.Out); + context.WriteOptionDescriptions(Console.Out); } else { From 2c7acc63325f23981d2db8aa5fc6096a080d124b Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Tue, 11 Feb 2020 13:35:57 -0500 Subject: [PATCH 146/201] ignored invalid test --- src/NuGet.Updater.Tests/SolutionHelperTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs index 25ee5ee..5c13d4d 100644 --- a/src/NuGet.Updater.Tests/SolutionHelperTests.cs +++ b/src/NuGet.Updater.Tests/SolutionHelperTests.cs @@ -10,6 +10,7 @@ namespace NuGet.Updater.Tests [TestClass] public class SolutionHelperTests { + [Ignore("hardcoded local path")] [TestMethod] public async Task GivenSolution_PackageReferencesAreFound() { From 310aa7aed7e6747231b429c28f6f84865b1f79db Mon Sep 17 00:00:00 2001 From: Xiaoy312 Date: Fri, 14 Feb 2020 11:10:03 -0500 Subject: [PATCH 147/201] added more tests for cli parameters --- .../ConsoleArgsParserTests.cs | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index df8abd0..c6f093b 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Updater.Entities; using NuGet.Updater.Tool.Arguments; namespace NuGet.Updater.Tests @@ -11,6 +12,7 @@ namespace NuGet.Updater.Tests public class ConsoleArgsParserTests { private const string NotExistingFilePath = @"c:\not\existing\file.mia"; + private const string SomeText = nameof(SomeText); [TestMethod] public void Given_HelpArgument_ContextIsHelp() @@ -55,5 +57,65 @@ public void Given_InvalidArgumentParameter_ContextHasError() Assert.AreEqual(context.Errors[0].Type, ConsoleArgErrorType.ValueParsingError); Assert.IsInstanceOfType(context.Errors[0].Exception, typeof(DirectoryNotFoundException)); } + + [DataTestMethod] + [DataRow(nameof(ConsoleArgsContext.IsHelp), "--help", true)] + [DataRow(nameof(ConsoleArgsContext.IsHelp), "-h", true)] + [DataRow(nameof(ConsoleArgsContext.IsSilent), "--silent", true)] + [DataRow(nameof(ConsoleArgsContext.ResultFile), "--result=" + NotExistingFilePath, NotExistingFilePath)] + [DataRow(nameof(ConsoleArgsContext.ResultFile), "-r=" + NotExistingFilePath, NotExistingFilePath)] + [DataRow(nameof(ConsoleArgsContext.SummaryFile), "--outputFile=" + NotExistingFilePath, NotExistingFilePath)] + [DataRow(nameof(ConsoleArgsContext.SummaryFile), "-of=" + NotExistingFilePath, NotExistingFilePath)] + public void Given_ContextArgument_ContextPropertyIsSet(string propertyName, string argument, object expectedValue) + { + Func propertySelector = propertyName switch + { + nameof(ConsoleArgsContext.IsHelp) => x => x.IsHelp, + nameof(ConsoleArgsContext.IsSilent) => x => x.IsSilent, + nameof(ConsoleArgsContext.ResultFile) => x => x.ResultFile, + nameof(ConsoleArgsContext.SummaryFile) => x => x.SummaryFile, + + _ => throw new ArgumentOutOfRangeException(argument), + }; + + var arguments = new[] { argument }; + var context = ConsoleArgsContext.Parse(arguments); + + Assert.IsFalse(context.HasError); + + var actualValue = propertySelector(context); + Assert.AreEqual(expectedValue, actualValue); + } + + [DataTestMethod] + [DataRow(nameof(UpdaterParameters.SolutionRoot), "--solution=" + NotExistingFilePath, NotExistingFilePath)] + [DataRow(nameof(UpdaterParameters.SolutionRoot), "-s=" + NotExistingFilePath, NotExistingFilePath)] + [DataRow(nameof(UpdaterParameters.PackageAuthor), "--packageAuthor=" + SomeText, SomeText)] + [DataRow(nameof(UpdaterParameters.PackageAuthor), "-a=" + SomeText, SomeText)] + [DataRow(nameof(UpdaterParameters.IsDowngradeAllowed), "--allowDowngrade", true)] + [DataRow(nameof(UpdaterParameters.IsDowngradeAllowed), "-d", true)] + [DataRow(nameof(UpdaterParameters.Strict), "--strict", true)] + [DataRow(nameof(UpdaterParameters.IsDryRun), "--dryrun", true)] + public void Given_UpdaterParametersArgument_UpdaterParametersPropertyIsSet(string propertyName, string argument, object expectedValue) + { + Func propertySelector = propertyName switch + { + nameof(UpdaterParameters.SolutionRoot) => x => x.SolutionRoot, + nameof(UpdaterParameters.PackageAuthor) => x => x.PackageAuthor, + nameof(UpdaterParameters.IsDowngradeAllowed) => x => x.IsDowngradeAllowed, + nameof(UpdaterParameters.Strict) => x => x.Strict, + nameof(UpdaterParameters.IsDryRun) => x => x.IsDryRun, + + _ => throw new ArgumentOutOfRangeException(propertyName), + }; + + var arguments = new[] { argument }; + var context = ConsoleArgsContext.Parse(arguments); + + Assert.IsFalse(context.HasError); + + var actualValue = propertySelector(context.Parameters); + Assert.AreEqual(expectedValue, actualValue); + } } } From 45861ff9d2c8160bf8c10f93b01abe259fc5d335 Mon Sep 17 00:00:00 2001 From: Xiaotian Gu Date: Mon, 17 Feb 2020 15:00:02 -0500 Subject: [PATCH 148/201] added test for cli parameters of collection type --- src/NuGet.Shared/Entities/PackageFeed.cs | 15 +++++++ .../ConsoleArgsParserTests.cs | 39 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NuGet.Shared/Entities/PackageFeed.cs index 9b9d7d5..10306a3 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NuGet.Shared/Entities/PackageFeed.cs @@ -113,5 +113,20 @@ public async Task PushPackage(CancellationToken ct, LocalPackage package) await _packageSource.PushPackage(ct, package, Logger); } + + public override int GetHashCode() => _packageSource.GetHashCode(); + + public override bool Equals(object obj) + { + if(obj is PackageFeed other) + { + if(_packageSource != null && other?._packageSource != null) + { + return this._packageSource.Equals(other._packageSource); + } + } + + return base.Equals(obj); + } } } diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index c6f093b..1123b58 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -1,8 +1,12 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Linq.Expressions; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NuGet.Shared.Entities; using NuGet.Updater.Entities; using NuGet.Updater.Tool.Arguments; @@ -13,6 +17,8 @@ public class ConsoleArgsParserTests { private const string NotExistingFilePath = @"c:\not\existing\file.mia"; private const string SomeText = nameof(SomeText); + private const string SomePublicFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json"; + private const string SomePrivateFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json|hunter2"; [TestMethod] public void Given_HelpArgument_ContextIsHelp() @@ -117,5 +123,38 @@ public void Given_UpdaterParametersArgument_UpdaterParametersPropertyIsSet(strin var actualValue = propertySelector(context.Parameters); Assert.AreEqual(expectedValue, actualValue); } + + private static IEnumerable CollectionPropertiesTestSetup() => new (Expression>, string, object)[] + { + ( x => x.Feeds, "--useNuGetorg", PackageFeed.NuGetOrg ), + ( x => x.Feeds, "-n", PackageFeed.NuGetOrg ), + ( x => x.Feeds, "--feed=" + SomePublicFeed, PackageFeed.FromString(SomePublicFeed) ), + ( x => x.Feeds, "--feed=" + SomePrivateFeed, PackageFeed.FromString(SomePrivateFeed) ), + ( x => x.Feeds, "-f=" + SomePublicFeed, PackageFeed.FromString(SomePublicFeed) ), + ( x => x.Feeds, "-f=" + SomePrivateFeed, PackageFeed.FromString(SomePrivateFeed) ), + ( x => x.PackagesToUpdate, "--updatePackages=" + SomeText, SomeText ), + ( x => x.PackagesToUpdate, "--update=" + SomeText, SomeText ), + ( x => x.PackagesToUpdate, "-u=" + SomeText, SomeText ), + ( x => x.PackagesToIgnore, "--ignorePackages=" + SomeText, SomeText ), + ( x => x.PackagesToIgnore, "--ignore=" + SomeText, SomeText ), + ( x => x.PackagesToIgnore, "-i=" + SomeText, SomeText ), + ( x => x.TargetVersions, "--versions=" + SomeText, SomeText ), + ( x => x.TargetVersions, "--version=" + SomeText, SomeText ), + ( x => x.TargetVersions, "-v=" + SomeText, SomeText ), + }.Select(x => new[] { x.Item1, x.Item2, x.Item3 }); + + [DataTestMethod] + [DynamicData(nameof(CollectionPropertiesTestSetup), DynamicDataSourceType.Method)] + public void Given_UpdaterParametersArgument_ContextCollectionPropertyIsSet(Expression> propertySelector, string argument, object expectedValue) + { + var arguments = new[] { argument }; + var context = ConsoleArgsContext.Parse(arguments); + + Assert.IsFalse(context.HasError); + + var collection = propertySelector.Compile()(context.Parameters); + var actualValue = collection?.Cast().FirstOrDefault(); + Assert.AreEqual(expectedValue, actualValue); + } } } From c63c000261224beb9067e5c5872e39c4c568b772 Mon Sep 17 00:00:00 2001 From: Xiaotian Gu Date: Mon, 17 Feb 2020 16:44:52 -0500 Subject: [PATCH 149/201] added test for cli parameter: versionOverrides --- .../ConsoleArgsParserTests.cs | 17 +++++++++++++++++ .../NuGet.Updater.Tests.csproj | 6 ++++++ .../Resources/version_overrides.json | 4 ++++ .../Arguments/ConsoleArgsContext.cs | 2 +- .../NuGet.Updater.Tool.csproj | 6 ++++++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/NuGet.Updater.Tests/Resources/version_overrides.json diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index 1123b58..1b26b14 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -19,6 +19,7 @@ public class ConsoleArgsParserTests private const string SomeText = nameof(SomeText); private const string SomePublicFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json"; private const string SomePrivateFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json|hunter2"; + private const string PinnedVersionJsonPath = @"Resources\version_overrides.json"; [TestMethod] public void Given_HelpArgument_ContextIsHelp() @@ -156,5 +157,21 @@ public void Given_UpdaterParametersArgument_ContextCollectionPropertyIsSet(Expre var actualValue = collection?.Cast().FirstOrDefault(); Assert.AreEqual(expectedValue, actualValue); } + + [TestMethod] + [DeploymentItem(PinnedVersionJsonPath)] + public void Given_UpdaterParametersArgument_ContextTargetVersionIsSet() + { + var arguments = new[] { "--versionOverrides=" + PinnedVersionJsonPath }; + var context = ConsoleArgsContext.Parse(arguments); + + Assert.IsFalse(context.HasError); + + var actualValues = context.Parameters.VersionOverrides + .ToDictionary(x => x.Key, x => x.Value); + var expectedValues = ConsoleArgsContext.LoadManualOperations(PinnedVersionJsonPath); + + CollectionAssert.AreEqual(expectedValues, actualValues); + } } } diff --git a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj b/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj index 6d03a73..4d4e4f1 100644 --- a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj +++ b/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj @@ -17,4 +17,10 @@ + + + PreserveNewest + + + diff --git a/src/NuGet.Updater.Tests/Resources/version_overrides.json b/src/NuGet.Updater.Tests/Resources/version_overrides.json new file mode 100644 index 0000000..8c9164d --- /dev/null +++ b/src/NuGet.Updater.Tests/Resources/version_overrides.json @@ -0,0 +1,4 @@ +[ + { "PackageId": "Newtonsoft.Json", "UpdatedVersion": "12.0.1" }, + { "PackageId": "Microsoft.Extensions.Logging", "UpdatedVersion": "3.1.2" } +] diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs index 2e671fb..67f97f8 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs @@ -97,7 +97,7 @@ Action TryParseAndSet(Func parse, Action set) public void WriteOptionDescriptions(TextWriter writer) => CreateOptionsFor(default).WriteOptionDescriptions(writer); - private static Dictionary LoadManualOperations(string inputFilePath) + internal static Dictionary LoadManualOperations(string inputFilePath) { using(var fileReader = File.OpenText(inputFilePath)) using(var jsonReader = new JsonTextReader(fileReader)) diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index fcc8cf2..2b2a8e8 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -35,6 +35,12 @@ + + + <_Parameter1>NuGet.Updater.Tests + + + From 1e323ce45d5d78fea563b3ab8740e63fc4ef00eb Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 25 Feb 2020 14:42:19 -0500 Subject: [PATCH 150/201] Forced package creation --- .azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index ec7413f..67d9ad7 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -9,6 +9,8 @@ trigger: variables: - name: PackageOutputPath value: '$(Build.ArtifactStagingDirectory)' +- name: GeneratePackageOnBuild + value: true steps: - task: GitVersion@4 From 1dbc6a097ebaccf75b05ff6ec614d554ec8bb488 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 27 Feb 2020 15:43:31 -0500 Subject: [PATCH 151/201] Fixed TestPackageFeed implementation --- src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs b/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs index 218865e..f37da19 100644 --- a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs +++ b/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs @@ -45,6 +45,6 @@ public async Task GetPackageVersions( ?.Select(v => new FeedVersion(v, Url)) .ToArray() ?? new FeedVersion[0]; - public Task PushPackage(CancellationToken ct, LocalPackage package) => throw new NotSupportedException(); + public Task PushPackage(CancellationToken ct, LocalPackage package) => throw new NotSupportedException(); } } From e30532790a9c295c806df88ac10beafc47b8e4b2 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Mar 2020 15:37:10 -0400 Subject: [PATCH 152/201] Fixed Nuget.Downloader metadata --- src/NuGet.Downloader/NuGet.Downloader.csproj | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/NuGet.Downloader/NuGet.Downloader.csproj b/src/NuGet.Downloader/NuGet.Downloader.csproj index 6fe46f3..7b5ea95 100644 --- a/src/NuGet.Downloader/NuGet.Downloader.csproj +++ b/src/NuGet.Downloader/NuGet.Downloader.csproj @@ -4,6 +4,17 @@ netstandard20;net472 + + + nventive.NuGet.Downloader + nventive + nventive + Nuget Dowloader allows to download the NuGet packages found in a solution + Apache-2.0 + icon.png + https://github.com/nventive/NuGet.Updater + + From 701feaca9dabefb285ddc031b0f6b513225c3047 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Mar 2020 15:37:59 -0400 Subject: [PATCH 153/201] Fixed metadata for Nuget.Downloader.Tool --- .../NuGet.Downloader.Tool.csproj | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj index 8ff589f..7f092e1 100644 --- a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj +++ b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj @@ -5,6 +5,17 @@ netcoreapp2.2 + + + nventive.NuGet.Downloader.Tool + nventive + nventive + Nuget Dowloader allows to download the NuGet packages found in a solution + Apache-2.0 + icon.png + https://github.com/nventive/NuGet.Updater + + From 29655f4dc5b3cde55ae0731269092151581e0482 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Mar 2020 16:10:25 -0400 Subject: [PATCH 154/201] Bumped GitVersion to 5 --- .azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 67d9ad7..18c2bb0 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -13,7 +13,7 @@ variables: value: true steps: -- task: GitVersion@4 +- task: GitVersion@5 - task: NuGetToolInstaller@1 inputs: From 95efc7e7a98c8c6efe0a475e3d28c6d520b373ac Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Mar 2020 16:16:24 -0400 Subject: [PATCH 155/201] Added icon to nuget.downloader --- src/NuGet.Downloader/NuGet.Downloader.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/NuGet.Downloader/NuGet.Downloader.csproj b/src/NuGet.Downloader/NuGet.Downloader.csproj index 7b5ea95..50e2f1c 100644 --- a/src/NuGet.Downloader/NuGet.Downloader.csproj +++ b/src/NuGet.Downloader/NuGet.Downloader.csproj @@ -15,6 +15,13 @@ https://github.com/nventive/NuGet.Updater + + + True + + + + From 7240747d4773c46b4ec53a7d76856fe704d7aea8 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Mar 2020 16:17:06 -0400 Subject: [PATCH 156/201] Added icon to Nuget.Downloader.tool --- src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj index 7f092e1..a35f2c5 100644 --- a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj +++ b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj @@ -15,6 +15,13 @@ icon.png https://github.com/nventive/NuGet.Updater + + + + True + + + From 203dbae169bfe44618864296c8e1abdf36703228 Mon Sep 17 00:00:00 2001 From: Elie Bariche <33458222+ebariche@users.noreply.github.com> Date: Tue, 28 Apr 2020 17:56:02 -0400 Subject: [PATCH 157/201] Add version range support --- .../ConsoleArgsParserTests.cs | 5 +- .../PackageReferenceTests.cs | 49 ++++++++++++++++++- .../SolutionHelperTests.cs | 24 --------- .../Arguments/ConsoleArgsContext.cs | 14 +++++- src/NuGet.Updater.Tool/Readme.md | 18 +++++++ src/NuGet.Updater.sln | 2 + .../Entities/UpdaterParameters.cs | 4 +- .../Extensions/PackageReferenceExtensions.cs | 7 +-- 8 files changed, 88 insertions(+), 35 deletions(-) delete mode 100644 src/NuGet.Updater.Tests/SolutionHelperTests.cs diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index 1b26b14..4cead74 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -20,7 +20,7 @@ public class ConsoleArgsParserTests private const string SomePublicFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json"; private const string SomePrivateFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json|hunter2"; private const string PinnedVersionJsonPath = @"Resources\version_overrides.json"; - + [TestMethod] public void Given_HelpArgument_ContextIsHelp() { @@ -167,8 +167,7 @@ public void Given_UpdaterParametersArgument_ContextTargetVersionIsSet() Assert.IsFalse(context.HasError); - var actualValues = context.Parameters.VersionOverrides - .ToDictionary(x => x.Key, x => x.Value); + var actualValues = (ICollection)context.Parameters.VersionOverrides; var expectedValues = ConsoleArgsContext.LoadManualOperations(PinnedVersionJsonPath); CollectionAssert.AreEqual(expectedValues, actualValues); diff --git a/src/NuGet.Updater.Tests/PackageReferenceTests.cs b/src/NuGet.Updater.Tests/PackageReferenceTests.cs index ca2a0ad..5b270b6 100644 --- a/src/NuGet.Updater.Tests/PackageReferenceTests.cs +++ b/src/NuGet.Updater.Tests/PackageReferenceTests.cs @@ -19,6 +19,7 @@ public class PackageReferenceTests private static readonly Dictionary TestPackages = new Dictionary { {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, + {"Uno.UI", new[] { "2.1.39", "2.2.0", "2.3.0-dev.44", "2.3.0-dev.48", "2.3.0-dev.58" } }, }; private static readonly TestPackageFeed TestFeed = new TestPackageFeed(TestFeedUri, TestPackages); @@ -72,7 +73,7 @@ public async Task GivenManualUpdates_AndVersionNotInFeed_ManualVersionIsFound() Feeds = { TestFeed }, VersionOverrides = { - { reference.Identity.Id, reference.Identity.Version }, + { reference.Identity.Id, (true, new VersionRange(reference.Identity.Version, true, reference.Identity.Version, true)) }, }, }; @@ -80,5 +81,51 @@ public async Task GivenManualUpdates_AndVersionNotInFeed_ManualVersionIsFound() Assert.AreEqual(version.Version, reference.Identity.Version); } + + [TestMethod] + public async Task GivenRangeOverrides_CorrectVersionsAreResolved() + { + var reference = new PackageReference("Uno.UI", "2.1.39"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "dev", "stable" }, + Feeds = { TestFeed }, + VersionOverrides = + { + { reference.Identity.Id, (false, VersionRange.Parse("(,2.3.0-dev.48]")) }, + }, + }; + + var version = await reference.GetLatestVersion(CancellationToken.None, parameters); + + Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.48"), version.Version); + + parameters.VersionOverrides["Uno.UI"] = (false, VersionRange.Parse("(,2.3.0-dev.48)")); + + version = await reference.GetLatestVersion(CancellationToken.None, parameters); + + Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.44"), version.Version); + } + + [TestMethod] + public async Task GivenRangeOverrides_CorrectVersionsAreResolved_AndTargetVersionIsHonored() + { + var reference = new PackageReference("Uno.UI", "2.1.39"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "stable" }, + Feeds = { TestFeed }, + VersionOverrides = + { + { reference.Identity.Id, (false, VersionRange.Parse("(,2.3.0-dev.48]")) }, + }, + }; + + var version = await reference.GetLatestVersion(CancellationToken.None, parameters); + + Assert.AreEqual(NuGetVersion.Parse("2.2.0"), version.Version); + } } } diff --git a/src/NuGet.Updater.Tests/SolutionHelperTests.cs b/src/NuGet.Updater.Tests/SolutionHelperTests.cs deleted file mode 100644 index 5c13d4d..0000000 --- a/src/NuGet.Updater.Tests/SolutionHelperTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Shared.Entities; -using NuGet.Shared.Helpers; - -namespace NuGet.Updater.Tests -{ - [TestClass] - public class SolutionHelperTests - { - [Ignore("hardcoded local path")] - [TestMethod] - public async Task GivenSolution_PackageReferencesAreFound() - { - var solution = @"C:\Git\MyMD\MyMD\MyMD.sln"; - - var references = await SolutionHelper.GetPackageReferences(CancellationToken.None, solution, FileType.Csproj, ConsoleLogger.Instance); - - Assert.IsTrue(references.Any()); - } - } -} diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs index 67f97f8..3ae98eb 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs @@ -97,14 +97,24 @@ Action TryParseAndSet(Func parse, Action set) public void WriteOptionDescriptions(TextWriter writer) => CreateOptionsFor(default).WriteOptionDescriptions(writer); - internal static Dictionary LoadManualOperations(string inputFilePath) + internal static Dictionary LoadManualOperations(string inputFilePath) { using(var fileReader = File.OpenText(inputFilePath)) using(var jsonReader = new JsonTextReader(fileReader)) { var result = JsonSerializer.CreateDefault().Deserialize>(jsonReader); - return result.ToDictionary(r => r.PackageId, r => new NuGetVersion(r.UpdatedVersion)); + return result.ToDictionary( + r => r.PackageId, + r => NuGetVersion.TryParse(r.UpdatedVersion, out var version) ? + (true, new VersionRange( + minVersion: version, + includeMinVersion: true, + maxVersion: version, + includeMaxVersion: true, + floatRange: null, + originalString: null)) : + (false, VersionRange.Parse(r.UpdatedVersion))); } } } diff --git a/src/NuGet.Updater.Tool/Readme.md b/src/NuGet.Updater.Tool/Readme.md index ab508f4..dbf937c 100644 --- a/src/NuGet.Updater.Tool/Readme.md +++ b/src/NuGet.Updater.Tool/Readme.md @@ -52,3 +52,21 @@ nugetupdater -s=MySolution.sln -n -f=https://pkgs.dev.azure.com/account/_packagi ``` nugetupdater -s=MySolution.sln -n --allowDowngrade ``` + +- Update packages to specific versions (forcefully and/or with nuget version ranges). See : https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges +``` +nugetupdater -s=MySolution.sln -n -v=dev -v=stable --allowDowngrade --versionOverrides=versions.json +``` +Versions.json example: +``` +[ + { + "PackageId": "Uno.UI", + "UpdatedVersion": "2.3.0-dev.76" -> Force 2.3.0-dev.76 + }, + { + "PackageId": "Uno.Wasm.Bootstrap", + "UpdatedVersion": "(,1.2.0-dev.18]" -> Resolves 1.2.0-dev.18 (-v=dev + -v=stable), 1.0.10 (-v=stable) + } +] +``` diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index 254b661..5be5713 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -39,7 +39,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tests", "N EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + NuGet.Shared\NuGet.Shared.projitems*{29277270-8efe-41a3-8cd8-d54ebf514c41}*SharedItemsImports = 5 NuGet.Shared\NuGet.Shared.projitems*{3890cca3-b7cf-450b-a6cd-4a4106b0008c}*SharedItemsImports = 13 + NuGet.Shared\NuGet.Shared.projitems*{912018bd-d988-4c90-b4ec-ba1c171207c3}*SharedItemsImports = 5 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NuGet.Updater/Entities/UpdaterParameters.cs index 6184332..52f75d7 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NuGet.Updater/Entities/UpdaterParameters.cs @@ -52,9 +52,9 @@ public class UpdaterParameters public string PackageAuthor { get; set; } /// - /// Gets the version to set for specific packages. + /// Gets the version range overrides for specific packages. /// - public IDictionary VersionOverrides { get; } = new Dictionary(); + public IDictionary VersionOverrides { get; } = new Dictionary(); /// /// Gets or sets a value indicating whether to actually write the updates to the files. diff --git a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs index d95f646..6f4964a 100644 --- a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs @@ -50,19 +50,20 @@ public static async Task GetLatestVersion( UpdaterParameters parameters ) { - if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion)) + if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion) && manualVersion.forceVersion) { PackageFeed.Logger.LogInformation($"Overriding version for {reference.Identity.Id}"); - return new FeedVersion(manualVersion); + return new FeedVersion(manualVersion.range.MinVersion); } var availableVersions = await Task.WhenAll(parameters .Feeds .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor)) ); - + var versionsPerTarget = availableVersions .SelectMany(x => x) + .Where(v => manualVersion.range?.Satisfies(v.Version) ?? true) .OrderByDescending(v => v) .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) .Where(g => g.Key.HasValue()); From fac257b74fe85e830005e7fc473fa9bdf126ca59 Mon Sep 17 00:00:00 2001 From: Elie Bariche <33458222+ebariche@users.noreply.github.com> Date: Fri, 1 May 2020 13:58:27 -0400 Subject: [PATCH 158/201] Add URL support for version overrides --- .../ConsoleArgsParserTests.cs | 2 +- .../Arguments/ConsoleArgsContext.cs | 56 +++++++++++++------ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs index 4cead74..f7acfa3 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs @@ -168,7 +168,7 @@ public void Given_UpdaterParametersArgument_ContextTargetVersionIsSet() Assert.IsFalse(context.HasError); var actualValues = (ICollection)context.Parameters.VersionOverrides; - var expectedValues = ConsoleArgsContext.LoadManualOperations(PinnedVersionJsonPath); + var expectedValues = ConsoleArgsContext.LoadOverrides(PinnedVersionJsonPath); CollectionAssert.AreEqual(expectedValues, actualValues); } diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs index 3ae98eb..ca2c7c5 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs +++ b/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; using Mono.Options; using Newtonsoft.Json; using NuGet.Shared.Entities; @@ -48,7 +51,7 @@ internal static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) { "strict", "Whether to use versions with only the specified version tag (ie. dev, but not dev.test)", TrySet(_ => context.Parameters.Strict = true) }, { "dryrun", "Runs the updater but doesn't write the updates to files.", TrySet(_ => context.Parameters.IsDryRun = true) }, { "result|r=", "The path to the file where the update result should be saved.", TrySet(x => context.ResultFile = x) }, - { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", TryParseAndSet(LoadManualOperations, x => context.Parameters.VersionOverrides.AddRange(x)) }, + { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", TryParseAndSet(LoadOverrides, x => context.Parameters.VersionOverrides.AddRange(x)) }, }; Action TrySet(Action set) @@ -97,24 +100,45 @@ Action TryParseAndSet(Func parse, Action set) public void WriteOptionDescriptions(TextWriter writer) => CreateOptionsFor(default).WriteOptionDescriptions(writer); - internal static Dictionary LoadManualOperations(string inputFilePath) + internal static Dictionary LoadOverrides(string inputPathOrUrl) { - using(var fileReader = File.OpenText(inputFilePath)) - using(var jsonReader = new JsonTextReader(fileReader)) + var results = + LoadFromStreamAsync() + .GetAwaiter() + .GetResult(); + + return results.ToDictionary( + r => r.PackageId, + r => NuGetVersion.TryParse(r.UpdatedVersion, out var version) ? + (true, new VersionRange( + minVersion: version, + includeMinVersion: true, + maxVersion: version, + includeMaxVersion: true, + floatRange: null, + originalString: null)) : + (false, VersionRange.Parse(r.UpdatedVersion))); + + async Task> LoadFromStreamAsync() { - var result = JsonSerializer.CreateDefault().Deserialize>(jsonReader); + var jsonSerializer = JsonSerializer.CreateDefault(); - return result.ToDictionary( - r => r.PackageId, - r => NuGetVersion.TryParse(r.UpdatedVersion, out var version) ? - (true, new VersionRange( - minVersion: version, - includeMinVersion: true, - maxVersion: version, - includeMaxVersion: true, - floatRange: null, - originalString: null)) : - (false, VersionRange.Parse(r.UpdatedVersion))); + if(inputPathOrUrl.StartsWith("http://") || inputPathOrUrl.StartsWith("https://")) + { + using(var httpClient = new HttpClient()) + using(var stream = await httpClient.GetStreamAsync(inputPathOrUrl)) + using(var jsonTextReader = new JsonTextReader(new StreamReader(stream, Encoding.UTF8))) + { + return jsonSerializer.Deserialize>(jsonTextReader); + } + } + else + { + using(var jsonTextReader = new JsonTextReader(File.OpenText(inputPathOrUrl))) + { + return jsonSerializer.Deserialize>(jsonTextReader); + } + } } } } From 171fd88826fcbc3fc2f122b8acd31bb09901e880 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 1 May 2020 14:38:25 -0400 Subject: [PATCH 159/201] Added a trigger from develop --- .azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 18c2bb0..f4e1828 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -5,6 +5,7 @@ trigger: branches: include: - master + - develop variables: - name: PackageOutputPath From e976317d0dcb99ee893370fdad3f2c007851284a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Fri, 23 Oct 2020 15:47:24 -0400 Subject: [PATCH 160/201] AFixed file existence check in UWP --- CHANGELOG.md | 8 ++++++++ .../Extensions/DictionaryExtensions.cs | 2 +- .../Extensions/PackageSourceExtensions.cs | 2 +- .../Extensions/XmlDocumentExtensions.cs | 6 +++--- src/NuGet.Shared/Helpers/FileHelper.Net.cs | 4 +++- src/NuGet.Shared/Helpers/FileHelper.UAP.cs | 16 ++++++++++++++-- src/NuGet.Shared/Helpers/SolutionHelper.cs | 2 +- .../Extensions/PackageReferenceExtensions.cs | 2 +- .../Extensions/XmlDocumentExtensions.cs | 2 +- src/NuGet.Updater/NuGet.Updater.csproj | 10 +++------- src/NuGet.Updater/NuGetUpdater.cs | 2 +- 11 files changed, 37 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a8368..28a0808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +### Changed +### Fixed +- Fixed issue where the UWP implementation was not properly checking for files' existence. +### Removed + +## Version 2.1 + ### Added - Added NuGet.Downloader - Added proper parsing of the versions read in the files diff --git a/src/NuGet.Shared/Extensions/DictionaryExtensions.cs b/src/NuGet.Shared/Extensions/DictionaryExtensions.cs index c7f9d84..9f927b5 100644 --- a/src/NuGet.Shared/Extensions/DictionaryExtensions.cs +++ b/src/NuGet.Shared/Extensions/DictionaryExtensions.cs @@ -13,7 +13,7 @@ params TKey[] keys .Where(g => keys.Contains(g.Key)) .ToDictionary(g => g.Key, g => g.Value); -#if !UAP +#if !WINDOWS_UWP public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value) { if(dictionary.ContainsKey(key)) diff --git a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs index 53d415c..84dc44b 100644 --- a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs +++ b/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs @@ -39,7 +39,7 @@ public static PackageSource ToPackageSource(this string input) return new PackageSource(url) { -#if UAP +#if WINDOWS_UWP Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false), #else Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false, null), diff --git a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs index e8acdc4..708a04a 100644 --- a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs @@ -7,7 +7,7 @@ using NuGet.Versioning; using Uno.Extensions; -#if UAP +#if WINDOWS_UWP using System.Text.RegularExpressions; using Windows.Data.Xml.Dom; using Windows.Storage; @@ -94,7 +94,7 @@ private static PackageIdentity CreatePackageIdentity(string id, string version) /// public static async Task LoadDocument(this string path, CancellationToken ct) { -#if UAP +#if WINDOWS_UWP var file = await StorageFile .GetFileFromPathAsync(path) .AsTask(ct); @@ -123,7 +123,7 @@ public static async Task LoadDocument(this string path, Cancellatio /// public static async Task Save(this XmlDocument document, CancellationToken ct, string path) { -#if UAP +#if WINDOWS_UWP var xml = document.GetXml(); xml = Regex.Replace(xml, @"(<\? ?xml)(?.+)( ?\?>)", x => !x.Groups["declaration"].Value.Contains("encoding", StringComparison.OrdinalIgnoreCase) diff --git a/src/NuGet.Shared/Helpers/FileHelper.Net.cs b/src/NuGet.Shared/Helpers/FileHelper.Net.cs index 209c399..7df9685 100644 --- a/src/NuGet.Shared/Helpers/FileHelper.Net.cs +++ b/src/NuGet.Shared/Helpers/FileHelper.Net.cs @@ -1,4 +1,4 @@ -#if !UAP +#if !WINDOWS_UWP using System.Collections.Generic; using System.IO; using System.Text; @@ -46,6 +46,8 @@ public static async Task IsDirectory(CancellationToken ct, string path) { return (File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory; } + + public static async Task Exists(string path) => File.Exists(path); } } #endif diff --git a/src/NuGet.Shared/Helpers/FileHelper.UAP.cs b/src/NuGet.Shared/Helpers/FileHelper.UAP.cs index cf25399..69d937c 100644 --- a/src/NuGet.Shared/Helpers/FileHelper.UAP.cs +++ b/src/NuGet.Shared/Helpers/FileHelper.UAP.cs @@ -1,6 +1,5 @@ -#if UAP +#if WINDOWS_UWP using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -73,6 +72,19 @@ public static async Task IsDirectory(CancellationToken ct, string path) return false; } } + + public static async Task Exists(string path) + { + try + { + var file = await StorageFile.GetFileFromPathAsync(path); + return true; + } + catch(Exception) + { + return false; + } + } } } #endif diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NuGet.Shared/Helpers/SolutionHelper.cs index 72d5ccd..28f9893 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NuGet.Shared/Helpers/SolutionHelper.cs @@ -118,7 +118,7 @@ private static async Task GetDirectoryFiles(CancellationToken ct, stri file = Path.Combine(solutionFolder, target.GetDescription()); } - if(file.HasValue() && File.Exists(file)) + if(file.HasValue() && await FileHelper.Exists(file)) { log.LogInformation($"Found {target.GetDescription()}"); return new[] { file }; diff --git a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs index 6f4964a..b3c0738 100644 --- a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs +++ b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs @@ -8,7 +8,7 @@ using NuGet.Updater.Entities; using Uno.Extensions; -#if UAP +#if WINDOWS_UWP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; #else using XmlDocument = System.Xml.XmlDocument; diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs index 86b6d85..38cf3d4 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs @@ -5,7 +5,7 @@ using NuGet.Updater.Log; using Uno.Extensions; -#if UAP +#if WINDOWS_UWP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; using XmlElement = Windows.Data.Xml.Dom.XmlElement; #else diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj index 89e47a2..539a2e4 100644 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ b/src/NuGet.Updater/NuGet.Updater.csproj @@ -1,6 +1,6 @@  - netstandard20;net472;uap10.0.17134 + netstandard20;net472;uap10.0.17763 true 7.2 @@ -16,10 +16,6 @@ https://github.com/nventive/NuGet.Updater - - UAP - - @@ -35,7 +31,7 @@ - + @@ -55,7 +51,7 @@ true - lib/uap10.0.17134 + lib/uap10.0.17763 diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NuGet.Updater/NuGetUpdater.cs index 01be0cc..3556ad1 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NuGet.Updater/NuGetUpdater.cs @@ -12,7 +12,7 @@ using NuGet.Updater.Log; using Uno.Extensions; -#if UAP +#if WINDOWS_UWP using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; #else using XmlDocument = System.Xml.XmlDocument; From 345b8a5ce6b4e4ce8530d9efd3bb21ac4450dfc5 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 12:27:46 -0500 Subject: [PATCH 161/201] Updated pipeline --- .azure-pipelines.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index f4e1828..fba4020 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -7,6 +7,12 @@ trigger: - master - develop +pr: + branches: + include: + - master + - develop + variables: - name: PackageOutputPath value: '$(Build.ArtifactStagingDirectory)' @@ -15,26 +21,28 @@ variables: steps: - task: GitVersion@5 + displayName: 'Calculate version' - task: NuGetToolInstaller@1 + displayName: 'Install NuGet 5.8.0' inputs: - versionSpec: 5.0.2 + versionSpec: 5.8.0 - task: NuGetCommand@2 + displayName: 'Restore NuGet packages' inputs: command: restore restoreSolution: src\Nuget.Updater.sln includeNuGetOrg: true - task: MSBuild@1 + displayName: 'Build solution in Release | Any CPU' inputs: solution: src\Nuget.Updater.sln configuration: Release platform: 'Any CPU' msbuildArguments: /p:PackageVersion=$(GitVersion.NuGetVersion) -- task: PublishBuildArtifacts@1 - inputs: - ArtifactName: Packages - PathtoPublish: $(PackageOutputPath) - publishLocation: Container +- publish: $(PackageOutputPath) + name: Packages + displayName: 'Publish NuGet packages' From c0498b201cc1e348180dc56f9cc572d7e746f7d0 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 27 Feb 2020 16:25:03 -0500 Subject: [PATCH 162/201] Refactored project structure --- .../ConsoleArgsParserTests.cs | 7 +- src/NeoGet.Tests/Constants.cs | 20 +++ .../Entities/TestPackageFeed.cs | 10 +- .../NeoGet.Tests.csproj} | 17 +-- .../Resources/version_overrides.json | 0 .../Tools/Updater}/NuGetUpdaterTests.cs | 11 +- .../Tools/Updater/PackageReferenceTests.cs | 72 ++++++++++ .../Tools/Updater/UpdaterParametersTests.cs | 60 ++++++++ .../NeoGet.Tools.Shared.projitems | 17 +++ .../NeoGet.Tools.Shared.shproj} | 4 +- .../Updater}/Arguments/ConsoleArgError.cs | 2 +- .../Updater}/Arguments/ConsoleArgErrorType.cs | 6 +- .../ConsoleArgsContext.Properties.cs | 8 +- .../Updater}/Arguments/ConsoleArgsContext.cs | 7 +- .../Entities => NeoGet/Contracts}/FileType.cs | 2 +- .../Contracts}/IPackageFeed.cs | 8 +- .../Entities/FeedVersion.cs | 4 +- .../Entities/LocalPackage.cs | 8 +- .../Entities/PackageFeed.cs | 49 ++++--- .../Entities/PackageNotFoundException.cs | 2 +- .../Entities/PackageReference.cs | 5 +- .../Extensions/DictionaryExtensions.cs | 2 +- .../Extensions/FeedVersionExtensions.cs | 4 +- .../Extensions/FileTypeExtensions.cs | 5 +- .../Extensions/NuGetResourceExtensions.cs} | 65 ++------- .../Extensions/NuGetVersionExtensions.cs | 2 +- .../PackageSearchMetadataExtensions.cs | 2 +- .../Extensions/PackageSourceExtensions.cs | 43 ++++++ .../Extensions/StringExtensions.cs} | 29 +++- .../Extensions/XmlDocumentExtensions.cs | 29 +++- .../Helpers/FileHelper.Net.cs | 16 +-- .../Helpers/FileHelper.UAP.cs | 2 +- .../Helpers/MarkdownHelper.cs | 5 +- .../Helpers/SolutionHelper.cs | 15 +- .../Log/ConsoleLogger.cs | 2 +- .../Log/SimpleTextWriter.cs | 2 +- .../NeoGet.csproj} | 29 ++-- .../Entities/DownloaderParameters.cs | 5 +- .../Downloader}/Entities/DownloaderResult.cs | 4 +- .../Tools/Downloader}/NuGetDownloader.cs | 22 +-- .../Updater}/Entities/LogDisplayOptions.cs | 2 +- .../Tools/Updater}/Entities/UpdateResult.cs | 3 +- .../Tools/Updater}/Entities/UpdaterPackage.cs | 4 +- .../Updater}/Entities/UpdaterParameters.cs | 10 +- .../Extensions/UpdateOperationExtensions.cs | 10 +- .../Extensions/UpdaterParametersExtension.cs | 0 .../Extensions/XmlDocumentExtensions.cs | 6 +- .../Tools/Updater}/Log/UpdateOperation.cs | 4 +- .../Tools/Updater}/Log/UpdaterLogger.cs | 14 +- .../Tools/Updater}/NuGetUpdater.cs | 19 +-- .../NuGet.Downloader.Tests.csproj | 16 --- .../NuGet.Downloader.Tool.csproj | 2 +- src/NuGet.Downloader.Tool/Program.cs | 7 +- src/NuGet.Shared/Helpers/StringHelper.cs | 30 ---- src/NuGet.Shared/NuGet.Shared.projitems | 35 ----- .../PackageReferenceTests.cs | 131 ------------------ .../NuGet.Updater.Tool.csproj | 6 +- src/NuGet.Updater.Tool/Program.cs | 17 ++- src/NuGet.Updater.sln | 46 ++---- .../Extensions/FeedVersionExtensions.cs | 33 ----- .../Extensions/PackageReferenceExtensions.cs | 77 ---------- src/NuGet.Updater/NuGet.Updater.csproj | 60 -------- 62 files changed, 488 insertions(+), 646 deletions(-) rename src/{NuGet.Updater.Tests => NeoGet.Tests}/ConsoleArgsParserTests.cs (98%) create mode 100644 src/NeoGet.Tests/Constants.cs rename src/{NuGet.Updater.Tests => NeoGet.Tests}/Entities/TestPackageFeed.cs (78%) rename src/{NuGet.Updater.Tests/NuGet.Updater.Tests.csproj => NeoGet.Tests/NeoGet.Tests.csproj} (56%) rename src/{NuGet.Updater.Tests => NeoGet.Tests}/Resources/version_overrides.json (100%) rename src/{NuGet.Updater.Tests => NeoGet.Tests/Tools/Updater}/NuGetUpdaterTests.cs (85%) create mode 100644 src/NeoGet.Tests/Tools/Updater/PackageReferenceTests.cs create mode 100644 src/NeoGet.Tests/Tools/Updater/UpdaterParametersTests.cs create mode 100644 src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems rename src/{NuGet.Shared/NuGet.Shared.shproj => NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj} (87%) rename src/{NuGet.Updater.Tool => NeoGet.Tools.Shared/Updater}/Arguments/ConsoleArgError.cs (94%) rename src/{NuGet.Updater.Tool => NeoGet.Tools.Shared/Updater}/Arguments/ConsoleArgErrorType.cs (50%) rename src/{NuGet.Updater.Tool => NeoGet.Tools.Shared/Updater}/Arguments/ConsoleArgsContext.Properties.cs (75%) rename src/{NuGet.Updater.Tool => NeoGet.Tools.Shared/Updater}/Arguments/ConsoleArgsContext.cs (97%) rename src/{NuGet.Shared/Entities => NeoGet/Contracts}/FileType.cs (95%) rename src/{NuGet.Shared/Entities => NeoGet/Contracts}/IPackageFeed.cs (86%) rename src/{NuGet.Shared => NeoGet}/Entities/FeedVersion.cs (86%) rename src/{NuGet.Shared => NeoGet}/Entities/LocalPackage.cs (61%) rename src/{NuGet.Shared => NeoGet}/Entities/PackageFeed.cs (62%) rename src/{NuGet.Shared => NeoGet}/Entities/PackageNotFoundException.cs (94%) rename src/{NuGet.Shared => NeoGet}/Entities/PackageReference.cs (89%) rename src/{NuGet.Shared => NeoGet}/Extensions/DictionaryExtensions.cs (96%) rename src/{NuGet.Shared => NeoGet}/Extensions/FeedVersionExtensions.cs (93%) rename src/{NuGet.Shared => NeoGet}/Extensions/FileTypeExtensions.cs (88%) rename src/{NuGet.Shared/Extensions/PackageSourceExtensions.cs => NeoGet/Extensions/NuGetResourceExtensions.cs} (50%) rename src/{NuGet.Shared => NeoGet}/Extensions/NuGetVersionExtensions.cs (99%) rename src/{NuGet.Shared => NeoGet}/Extensions/PackageSearchMetadataExtensions.cs (90%) create mode 100644 src/NeoGet/Extensions/PackageSourceExtensions.cs rename src/{NuGet.Shared/Helpers/PackageHelper.cs => NeoGet/Extensions/StringExtensions.cs} (65%) rename src/{NuGet.Shared => NeoGet}/Extensions/XmlDocumentExtensions.cs (88%) rename src/{NuGet.Shared => NeoGet}/Helpers/FileHelper.Net.cs (80%) rename src/{NuGet.Shared => NeoGet}/Helpers/FileHelper.UAP.cs (98%) rename src/{NuGet.Shared => NeoGet}/Helpers/MarkdownHelper.cs (95%) rename src/{NuGet.Shared => NeoGet}/Helpers/SolutionHelper.cs (94%) rename src/{NuGet.Shared => NeoGet}/Log/ConsoleLogger.cs (97%) rename src/{NuGet.Shared => NeoGet}/Log/SimpleTextWriter.cs (94%) rename src/{NuGet.Downloader/NuGet.Downloader.csproj => NeoGet/NeoGet.csproj} (75%) rename src/{NuGet.Downloader => NeoGet/Tools/Downloader}/Entities/DownloaderParameters.cs (85%) rename src/{NuGet.Downloader => NeoGet/Tools/Downloader}/Entities/DownloaderResult.cs (68%) rename src/{NuGet.Downloader => NeoGet/Tools/Downloader}/NuGetDownloader.cs (88%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Entities/LogDisplayOptions.cs (90%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Entities/UpdateResult.cs (89%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Entities/UpdaterPackage.cs (83%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Entities/UpdaterParameters.cs (91%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Extensions/UpdateOperationExtensions.cs (89%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Extensions/UpdaterParametersExtension.cs (100%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Extensions/XmlDocumentExtensions.cs (97%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Log/UpdateOperation.cs (96%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/Log/UpdaterLogger.cs (95%) rename src/{NuGet.Updater => NeoGet/Tools/Updater}/NuGetUpdater.cs (91%) delete mode 100644 src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj delete mode 100644 src/NuGet.Shared/Helpers/StringHelper.cs delete mode 100644 src/NuGet.Shared/NuGet.Shared.projitems delete mode 100644 src/NuGet.Updater.Tests/PackageReferenceTests.cs delete mode 100644 src/NuGet.Updater/Extensions/FeedVersionExtensions.cs delete mode 100644 src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs delete mode 100644 src/NuGet.Updater/NuGet.Updater.csproj diff --git a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs b/src/NeoGet.Tests/ConsoleArgsParserTests.cs similarity index 98% rename from src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs rename to src/NeoGet.Tests/ConsoleArgsParserTests.cs index f7acfa3..0b04810 100644 --- a/src/NuGet.Updater.Tests/ConsoleArgsParserTests.cs +++ b/src/NeoGet.Tests/ConsoleArgsParserTests.cs @@ -4,11 +4,10 @@ using System.IO; using System.Linq; using System.Linq.Expressions; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Shared.Entities; -using NuGet.Updater.Entities; -using NuGet.Updater.Tool.Arguments; +using NeoGet.Entities; +using NeoGet.Tools.Updater.Arguments; +using NeoGet.Tools.Updater.Entities; namespace NuGet.Updater.Tests { diff --git a/src/NeoGet.Tests/Constants.cs b/src/NeoGet.Tests/Constants.cs new file mode 100644 index 0000000..9e966ec --- /dev/null +++ b/src/NeoGet.Tests/Constants.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NeoGet.Tools.Tests.Entities; + +namespace NeoGet.Tests +{ + public static class Constants + { + public static readonly Uri TestFeedUri = new Uri("http://localhost"); + + public static readonly Dictionary TestPackages = new Dictionary + { + {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, + {"Uno.UI", new[] { "2.1.39", "2.2.0", "2.3.0-dev.44", "2.3.0-dev.48", "2.3.0-dev.58" } }, + }; + + public static readonly TestPackageFeed TestFeed = new TestPackageFeed(TestFeedUri, TestPackages); + } +} diff --git a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs b/src/NeoGet.Tests/Entities/TestPackageFeed.cs similarity index 78% rename from src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs rename to src/NeoGet.Tests/Entities/TestPackageFeed.cs index f37da19..3f71500 100644 --- a/src/NuGet.Updater.Tests/Entities/TestPackageFeed.cs +++ b/src/NeoGet.Tests/Entities/TestPackageFeed.cs @@ -4,10 +4,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using NuGet.Frameworks; using NuGet.Packaging.Core; -using NuGet.Shared.Entities; +using NeoGet.Contracts; +using NeoGet.Entities; -namespace NuGet.Updater.Tests.Entities +namespace NeoGet.Tools.Tests.Entities { public class TestPackageFeed : IPackageFeed { @@ -34,7 +36,7 @@ string location .Select(v => new LocalPackage(packageIdentity, Path.Combine(location, $"{packageIdentity.Id}.nupkg"))) .SingleOrDefault(); - public async Task GetDependencies(CancellationToken ct, PackageIdentity packageIdentity) => Array.Empty(); + public async Task> GetDependencies(CancellationToken ct, PackageIdentity packageIdentity) => new Dictionary(); public async Task GetPackageVersions( CancellationToken ct, @@ -43,7 +45,7 @@ public async Task GetPackageVersions( ) => _packages .GetValueOrDefault(reference.Identity.Id) ?.Select(v => new FeedVersion(v, Url)) - .ToArray() ?? new FeedVersion[0]; + .ToArray() ?? Array.Empty(); public Task PushPackage(CancellationToken ct, LocalPackage package) => throw new NotSupportedException(); } diff --git a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj b/src/NeoGet.Tests/NeoGet.Tests.csproj similarity index 56% rename from src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj rename to src/NeoGet.Tests/NeoGet.Tests.csproj index 4d4e4f1..db2b256 100644 --- a/src/NuGet.Updater.Tests/NuGet.Updater.Tests.csproj +++ b/src/NeoGet.Tests/NeoGet.Tests.csproj @@ -2,7 +2,6 @@ netcoreapp3.0 - false @@ -10,17 +9,19 @@ + - - + - - - PreserveNewest - - + + + PreserveNewest + + + + diff --git a/src/NuGet.Updater.Tests/Resources/version_overrides.json b/src/NeoGet.Tests/Resources/version_overrides.json similarity index 100% rename from src/NuGet.Updater.Tests/Resources/version_overrides.json rename to src/NeoGet.Tests/Resources/version_overrides.json diff --git a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs b/src/NeoGet.Tests/Tools/Updater/NuGetUpdaterTests.cs similarity index 85% rename from src/NuGet.Updater.Tests/NuGetUpdaterTests.cs rename to src/NeoGet.Tests/Tools/Updater/NuGetUpdaterTests.cs index d3de47e..0a08567 100644 --- a/src/NuGet.Updater.Tests/NuGetUpdaterTests.cs +++ b/src/NeoGet.Tests/Tools/Updater/NuGetUpdaterTests.cs @@ -3,13 +3,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Shared.Entities; -using NuGet.Updater.Entities; -using NuGet.Updater.Log; -using NuGet.Updater.Tests.Entities; +using NeoGet.Contracts; +using NeoGet.Tools.Tests.Entities; +using NeoGet.Tools.Updater; +using NeoGet.Tools.Updater.Entities; +using NeoGet.Tools.Updater.Log; using Uno.Extensions; -namespace NuGet.Updater.Tests +namespace NeoGet.Tests.Tools { [TestClass] public class NuGetUpdaterTests diff --git a/src/NeoGet.Tests/Tools/Updater/PackageReferenceTests.cs b/src/NeoGet.Tests/Tools/Updater/PackageReferenceTests.cs new file mode 100644 index 0000000..8a8c4e9 --- /dev/null +++ b/src/NeoGet.Tests/Tools/Updater/PackageReferenceTests.cs @@ -0,0 +1,72 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NeoGet.Entities; +using NeoGet.Tools.Updater.Entities; +using NeoGet.Tools.Updater.Extensions; +using NuGet.Versioning; + +namespace NeoGet.Tests +{ + [TestClass] + public class PackageReferenceTests + { + [TestMethod] + public async Task GivenPackageWithMatchingVersion_VersionIsFound() + { + var parameters = new UpdaterParameters + { + TargetVersions = { "beta" }, + Feeds = { Constants.TestFeed }, + }; + + var packageVersion = "1.0-beta.1"; + var packageId = "nventive.NuGet.Updater"; + + var reference = new PackageReference(packageId, packageVersion); + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.IsNotNull(version); + Assert.AreEqual(version.Version.OriginalVersion, packageVersion); + } + + [TestMethod] + public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() + { + var parameters = new UpdaterParameters + { + TargetVersions = { "stable" }, + }; + + var packageVersion = "1.0-beta.1"; + var packageId = "nventive.NuGet.Updater"; + + var reference = new PackageReference(packageId, packageVersion); + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.IsNull(version); + } + + [TestMethod] + public async Task GivenManualUpdates_AndVersionNotInFeed_ManualVersionIsFound() + { + var reference = new PackageReference("nventive.NuGet.Updater", "1.0"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "stable" }, + Feeds = { Constants.TestFeed }, + VersionOverrides = + { + { reference.Identity.Id, (true, new VersionRange(reference.Identity.Version, true, reference.Identity.Version, true)) }, + }, + }; + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(version.Version, reference.Identity.Version); + } + } +} diff --git a/src/NeoGet.Tests/Tools/Updater/UpdaterParametersTests.cs b/src/NeoGet.Tests/Tools/Updater/UpdaterParametersTests.cs new file mode 100644 index 0000000..3c1782c --- /dev/null +++ b/src/NeoGet.Tests/Tools/Updater/UpdaterParametersTests.cs @@ -0,0 +1,60 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NeoGet.Entities; +using NeoGet.Tools.Updater.Entities; +using NeoGet.Tools.Updater.Extensions; +using NuGet.Versioning; + +namespace NeoGet.Tests.Tools.Updater +{ + [TestClass] + public class UpdaterParametersTests + { + [TestMethod] + public async Task GivenRangeOverrides_CorrectVersionsAreResolved() + { + var reference = new PackageReference("Uno.UI", "2.1.39"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "dev", "stable" }, + Feeds = { Constants.TestFeed }, + VersionOverrides = + { + { reference.Identity.Id, (false, VersionRange.Parse("(,2.3.0-dev.48]")) }, + }, + }; + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.48"), version.Version); + + parameters.VersionOverrides["Uno.UI"] = (false, VersionRange.Parse("(,2.3.0-dev.48)")); + + version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.44"), version.Version); + } + + [TestMethod] + public async Task GivenRangeOverrides_CorrectVersionsAreResolved_AndTargetVersionIsHonored() + { + var reference = new PackageReference("Uno.UI", "2.1.39"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "stable" }, + Feeds = { Constants.TestFeed }, + VersionOverrides = + { + { reference.Identity.Id, (false, VersionRange.Parse("(,2.3.0-dev.48]")) }, + }, + }; + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(NuGetVersion.Parse("2.2.0"), version.Version); + } + } +} diff --git a/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems b/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems new file mode 100644 index 0000000..b472589 --- /dev/null +++ b/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems @@ -0,0 +1,17 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 4c395d24-0028-47ef-b07e-5566344e78de + + + NeoGet.Tools + + + + + + + + \ No newline at end of file diff --git a/src/NuGet.Shared/NuGet.Shared.shproj b/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj similarity index 87% rename from src/NuGet.Shared/NuGet.Shared.shproj rename to src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj index 7290aa3..bf48bb6 100644 --- a/src/NuGet.Shared/NuGet.Shared.shproj +++ b/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj @@ -1,13 +1,13 @@ - 3890cca3-b7cf-450b-a6cd-4a4106b0008c + 4c395d24-0028-47ef-b07e-5566344e78de 14.0 - + diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs similarity index 94% rename from src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs rename to src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs index 0ca16a2..4332743 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgError.cs +++ b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace NuGet.Updater.Tool.Arguments +namespace NeoGet.Tools.Updater.Arguments { public class ConsoleArgError { diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs similarity index 50% rename from src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs rename to src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs index a94821d..0ae0acb 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgErrorType.cs +++ b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NuGet.Updater.Tool.Arguments +namespace NeoGet.Tools.Updater.Arguments { public enum ConsoleArgErrorType { diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs similarity index 75% rename from src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs rename to src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs index 7cd60a2..bd5251a 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.Properties.cs +++ b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using NuGet.Updater.Entities; +using NeoGet.Tools.Updater.Entities; -namespace NuGet.Updater.Tool.Arguments +namespace NeoGet.Tools.Updater.Arguments { public partial class ConsoleArgsContext { diff --git a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs similarity index 97% rename from src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs rename to src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs index ca2c7c5..6b92367 100644 --- a/src/NuGet.Updater.Tool/Arguments/ConsoleArgsContext.cs +++ b/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs @@ -6,13 +6,14 @@ using System.Text; using System.Threading.Tasks; using Mono.Options; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Tools.Updater.Entities; using Newtonsoft.Json; -using NuGet.Shared.Entities; -using NuGet.Updater.Entities; using NuGet.Versioning; using Uno.Extensions; -namespace NuGet.Updater.Tool.Arguments +namespace NeoGet.Tools.Updater.Arguments { public partial class ConsoleArgsContext { diff --git a/src/NuGet.Shared/Entities/FileType.cs b/src/NeoGet/Contracts/FileType.cs similarity index 95% rename from src/NuGet.Shared/Entities/FileType.cs rename to src/NeoGet/Contracts/FileType.cs index 1cc1263..e9023b8 100644 --- a/src/NuGet.Shared/Entities/FileType.cs +++ b/src/NeoGet/Contracts/FileType.cs @@ -1,6 +1,6 @@ using System; -namespace NuGet.Shared.Entities +namespace NeoGet.Contracts { /// /// The type of file containing a reference. diff --git a/src/NuGet.Shared/Entities/IPackageFeed.cs b/src/NeoGet/Contracts/IPackageFeed.cs similarity index 86% rename from src/NuGet.Shared/Entities/IPackageFeed.cs rename to src/NeoGet/Contracts/IPackageFeed.cs index 091e572..3e97cd3 100644 --- a/src/NuGet.Shared/Entities/IPackageFeed.cs +++ b/src/NeoGet/Contracts/IPackageFeed.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using NuGet.Common; +using NeoGet.Entities; +using NuGet.Frameworks; using NuGet.Packaging.Core; -namespace NuGet.Shared.Entities +namespace NeoGet.Contracts { public interface IPackageFeed { @@ -33,7 +35,7 @@ public interface IPackageFeed /// /// /// - Task GetDependencies(CancellationToken ct, PackageIdentity packageIdentity); + Task> GetDependencies(CancellationToken ct, PackageIdentity packageIdentity); /// /// Downloads the package with the given identity. diff --git a/src/NuGet.Shared/Entities/FeedVersion.cs b/src/NeoGet/Entities/FeedVersion.cs similarity index 86% rename from src/NuGet.Shared/Entities/FeedVersion.cs rename to src/NeoGet/Entities/FeedVersion.cs index 3d34775..6480ca2 100644 --- a/src/NuGet.Shared/Entities/FeedVersion.cs +++ b/src/NeoGet/Entities/FeedVersion.cs @@ -1,11 +1,11 @@ using System; using NuGet.Versioning; -namespace NuGet.Shared.Entities +namespace NeoGet.Entities { public class FeedVersion : IComparable { - internal FeedVersion(string version, Uri feedUri) + public FeedVersion(string version, Uri feedUri) : this(new NuGetVersion(version), feedUri) { } diff --git a/src/NuGet.Shared/Entities/LocalPackage.cs b/src/NeoGet/Entities/LocalPackage.cs similarity index 61% rename from src/NuGet.Shared/Entities/LocalPackage.cs rename to src/NeoGet/Entities/LocalPackage.cs index 832053e..247ced0 100644 --- a/src/NuGet.Shared/Entities/LocalPackage.cs +++ b/src/NeoGet/Entities/LocalPackage.cs @@ -1,10 +1,14 @@ using NuGet.Packaging.Core; -namespace NuGet.Shared.Entities +namespace NeoGet.Entities { public class LocalPackage { - internal LocalPackage(PackageIdentity identity, string path) + public LocalPackage() + { + } + + public LocalPackage(PackageIdentity identity, string path) { Identity = identity; Path = path; diff --git a/src/NuGet.Shared/Entities/PackageFeed.cs b/src/NeoGet/Entities/PackageFeed.cs similarity index 62% rename from src/NuGet.Shared/Entities/PackageFeed.cs rename to src/NeoGet/Entities/PackageFeed.cs index 904f31f..f30ef40 100644 --- a/src/NuGet.Shared/Entities/PackageFeed.cs +++ b/src/NeoGet/Entities/PackageFeed.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -6,12 +7,15 @@ using System.Threading.Tasks; using NuGet.Common; using NuGet.Configuration; +using NuGet.Frameworks; using NuGet.Packaging.Core; -using NuGet.Protocol; -using NuGet.Shared.Extensions; +using NuGet.Protocol.Core.Types; +using NeoGet.Contracts; +using NeoGet.Extensions; using Uno.Extensions; +using NuGet.Shared.Extensions; -namespace NuGet.Shared.Entities +namespace NeoGet.Entities { public class PackageFeed : IPackageFeed { @@ -21,16 +25,23 @@ public class PackageFeed : IPackageFeed public static PackageFeed FromString(string input) => new PackageFeed(input.ToPackageSource()); - private readonly PackageSource _packageSource; + private readonly PackageMetadataResource _packageSearchMetadata; + private readonly DownloadResource _downloadResource; + private readonly PackageUpdateResource _packageUpdateResource; private PackageFeed(PackageSource source) { - _packageSource = source; + Url = source.SourceUri; + IsPrivate = source.Credentials != null; + + _packageSearchMetadata = source.GetResource(); + _downloadResource = source.GetResource(); + _packageUpdateResource = source.GetResource(); } - public Uri Url => _packageSource.SourceUri; + public Uri Url { get; } - public bool IsPrivate => _packageSource.Credentials != null; + public bool IsPrivate { get; } public async Task GetPackageVersions( CancellationToken ct, @@ -42,7 +53,7 @@ public async Task GetPackageVersions( logMessage.AppendLine($"Retrieving package {reference.Identity.Id} from {Url}"); - var versions = await _packageSource.GetPackageVersions(ct, reference.Identity.Id); + var versions = await _packageSearchMetadata.GetPackageVersions(ct, reference.Identity.Id); logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions" : "No versions found"); @@ -60,12 +71,12 @@ public async Task GetPackageVersions( .ToArray(); } - public async Task GetDependencies( + public async Task> GetDependencies( CancellationToken ct, PackageIdentity packageIdentity ) { - var version = await _packageSource.GetPackageVersion(ct, packageIdentity); + var version = await _packageSearchMetadata.GetPackageVersion(ct, packageIdentity); if(version == null) { @@ -74,9 +85,7 @@ PackageIdentity packageIdentity return version .DependencySets - .SelectMany(g => g.Packages) - .Distinct() - .ToArray(); + .ToDictionary(g => g.TargetFramework, g => g.Packages.ToArray()); } public async Task DownloadPackage( @@ -85,14 +94,14 @@ public async Task DownloadPackage( string downloadLocation ) { - var version = await _packageSource.GetPackageVersion(ct, packageIdentity); + var version = await _packageSearchMetadata.GetPackageVersion(ct, packageIdentity); if (version == null) //Package with this version doesn't exist in the source, skipping. { return null; } - var downloadResult = await _packageSource.DownloadPackage(ct, packageIdentity, downloadLocation, Logger); + var downloadResult = await _downloadResource.DownloadPackage(ct, packageIdentity, downloadLocation, Logger); var localPackagePath = Path.Combine(downloadLocation, $"{packageIdentity}.nupkg"); @@ -103,7 +112,7 @@ string downloadLocation public async Task PushPackage(CancellationToken ct, LocalPackage package) { - var version = await _packageSource.GetPackageVersion(ct, package.Identity); + var version = await _packageSearchMetadata.GetPackageVersion(ct, package.Identity); if(version != null) { @@ -111,18 +120,18 @@ public async Task PushPackage(CancellationToken ct, LocalPackage package) return false; } - return await _packageSource.PushPackage(ct, package, Logger); + return await _packageUpdateResource.PushPackage(ct, package, Logger); } - public override int GetHashCode() => _packageSource.GetHashCode(); + public override int GetHashCode() => Url.GetHashCode(); public override bool Equals(object obj) { if(obj is PackageFeed other) { - if(_packageSource != null && other?._packageSource != null) + if(Url != null && other?.Url != null) { - return this._packageSource.Equals(other._packageSource); + return this.Url.Equals(other.Url); } } diff --git a/src/NuGet.Shared/Entities/PackageNotFoundException.cs b/src/NeoGet/Entities/PackageNotFoundException.cs similarity index 94% rename from src/NuGet.Shared/Entities/PackageNotFoundException.cs rename to src/NeoGet/Entities/PackageNotFoundException.cs index 98272c1..e152680 100644 --- a/src/NuGet.Shared/Entities/PackageNotFoundException.cs +++ b/src/NeoGet/Entities/PackageNotFoundException.cs @@ -1,7 +1,7 @@ using System; using NuGet.Packaging.Core; -namespace NuGet.Shared.Entities +namespace NeoGet.Entities { [Serializable] public class PackageNotFoundException : Exception diff --git a/src/NuGet.Shared/Entities/PackageReference.cs b/src/NeoGet/Entities/PackageReference.cs similarity index 89% rename from src/NuGet.Shared/Entities/PackageReference.cs rename to src/NeoGet/Entities/PackageReference.cs index 668202e..f1d8f4c 100644 --- a/src/NuGet.Shared/Entities/PackageReference.cs +++ b/src/NeoGet/Entities/PackageReference.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using NuGet.Packaging.Core; +using NeoGet.Contracts; using NuGet.Versioning; -namespace NuGet.Shared.Entities +namespace NeoGet.Entities { public class PackageReference { - internal PackageReference(string id, string version) + public PackageReference(string id, string version) : this(new PackageIdentity(id, new NuGetVersion(version)), new Dictionary()) { } diff --git a/src/NuGet.Shared/Extensions/DictionaryExtensions.cs b/src/NeoGet/Extensions/DictionaryExtensions.cs similarity index 96% rename from src/NuGet.Shared/Extensions/DictionaryExtensions.cs rename to src/NeoGet/Extensions/DictionaryExtensions.cs index 9f927b5..784cdf4 100644 --- a/src/NuGet.Shared/Extensions/DictionaryExtensions.cs +++ b/src/NeoGet/Extensions/DictionaryExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { public static class DictionaryExtensions { diff --git a/src/NuGet.Shared/Extensions/FeedVersionExtensions.cs b/src/NeoGet/Extensions/FeedVersionExtensions.cs similarity index 93% rename from src/NuGet.Shared/Extensions/FeedVersionExtensions.cs rename to src/NeoGet/Extensions/FeedVersionExtensions.cs index a56d2ef..fbdb176 100644 --- a/src/NuGet.Shared/Extensions/FeedVersionExtensions.cs +++ b/src/NeoGet/Extensions/FeedVersionExtensions.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using NuGet.Shared.Entities; +using NeoGet.Entities; using Uno.Extensions; -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { public static class FeedVersionExtensions { diff --git a/src/NuGet.Shared/Extensions/FileTypeExtensions.cs b/src/NeoGet/Extensions/FileTypeExtensions.cs similarity index 88% rename from src/NuGet.Shared/Extensions/FileTypeExtensions.cs rename to src/NeoGet/Extensions/FileTypeExtensions.cs index aad5182..4260bce 100644 --- a/src/NuGet.Shared/Extensions/FileTypeExtensions.cs +++ b/src/NeoGet/Extensions/FileTypeExtensions.cs @@ -1,7 +1,8 @@ using System.Linq; -using NuGet.Shared.Entities; +using NeoGet.Contracts; +using NeoGet.Entities; -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { public static class FileTypeExtensions { diff --git a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs b/src/NeoGet/Extensions/NuGetResourceExtensions.cs similarity index 50% rename from src/NuGet.Shared/Extensions/PackageSourceExtensions.cs rename to src/NeoGet/Extensions/NuGetResourceExtensions.cs index 84dc44b..42f9644 100644 --- a/src/NuGet.Shared/Extensions/PackageSourceExtensions.cs +++ b/src/NeoGet/Extensions/NuGetResourceExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NuGet.Common; @@ -7,53 +6,19 @@ using NuGet.Packaging.Core; using NuGet.Protocol; using NuGet.Protocol.Core.Types; -using NuGet.Shared.Entities; -using Uno.Extensions; +using NeoGet.Entities; -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { - public static class PackageSourceExtensions + public static class NuGetResourceExtensions { - private const char PackageFeedInputSeparator = '|'; - - /// - /// Transforms a input string into a package feed - /// If the string matches the {url}|{token} format, a private source will be created. - /// Otherwise, the input will be used as the URL of a public source. - /// - /// - /// - public static PackageSource ToPackageSource(this string input) - { - var parts = input.Split(PackageFeedInputSeparator); - - var url = parts.ElementAtOrDefault(0); - var accessToken = parts.ElementAtOrDefault(1); - - if(accessToken == null) - { - return new PackageSource(url); - } - - var sourceName = Guid.NewGuid().ToStringInvariant(); - - return new PackageSource(url) - { -#if WINDOWS_UWP - Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false), -#else - Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false, null), -#endif - }; - } - public static async Task GetPackageVersion( - this PackageSource source, + this PackageMetadataResource resource, CancellationToken ct, PackageIdentity identity ) { - var versions = await source.GetPackageVersions(ct, identity.Id); + var versions = await resource.GetPackageVersions(ct, identity.Id); return versions .Cast() @@ -61,13 +26,12 @@ PackageIdentity identity } public static async Task GetPackageVersions( - this PackageSource source, + this PackageMetadataResource resource, CancellationToken ct, string packageId ) { - var versions = await source - .GetResource() + var versions = await resource .GetMetadataAsync(packageId, true, false, new SourceCacheContext { NoCache = true }, NullLogger.Instance, ct); return versions @@ -76,7 +40,7 @@ string packageId } public static async Task DownloadPackage( - this PackageSource source, + this DownloadResource resource, CancellationToken ct, PackageIdentity identity, string downloadDirectory, @@ -85,26 +49,23 @@ ILogger log { var downloadContext = new PackageDownloadContext(new SourceCacheContext { NoCache = true }, downloadDirectory, directDownload: true); - return await source - .GetResource() + return await resource .GetDownloadResourceResultAsync(identity, downloadContext, downloadDirectory, log, ct); } public static async Task PushPackage( - this PackageSource source, + this PackageUpdateResource resource, CancellationToken ct, LocalPackage package, ILogger log ) { - await source - .GetResource() - .Push(package.Path, null, 100, true, s => s, s => s, true, log); + await resource.Push(package.Path, null, 100, true, s => s, s => s, true, log); return true; } - private static TResource GetResource(this PackageSource source) + internal static TResource GetResource(this PackageSource source) where TResource : class, INuGetResource { var repositoryProvider = new SourceRepositoryProvider( diff --git a/src/NuGet.Shared/Extensions/NuGetVersionExtensions.cs b/src/NeoGet/Extensions/NuGetVersionExtensions.cs similarity index 99% rename from src/NuGet.Shared/Extensions/NuGetVersionExtensions.cs rename to src/NeoGet/Extensions/NuGetVersionExtensions.cs index 290a1a8..a402e34 100644 --- a/src/NuGet.Shared/Extensions/NuGetVersionExtensions.cs +++ b/src/NeoGet/Extensions/NuGetVersionExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using NuGet.Versioning; -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { public static class NuGetVersionExtensions { diff --git a/src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs b/src/NeoGet/Extensions/PackageSearchMetadataExtensions.cs similarity index 90% rename from src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs rename to src/NeoGet/Extensions/PackageSearchMetadataExtensions.cs index 05c01d7..34691bf 100644 --- a/src/NuGet.Shared/Extensions/PackageSearchMetadataExtensions.cs +++ b/src/NeoGet/Extensions/PackageSearchMetadataExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using NuGet.Protocol.Core.Types; -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { public static class PackageSearchMetadataExtensions { diff --git a/src/NeoGet/Extensions/PackageSourceExtensions.cs b/src/NeoGet/Extensions/PackageSourceExtensions.cs new file mode 100644 index 0000000..2ec3f8b --- /dev/null +++ b/src/NeoGet/Extensions/PackageSourceExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using NuGet.Configuration; +using Uno.Extensions; + +namespace NuGet.Shared.Extensions +{ + public static class PackageSourceExtensions + { + private const char PackageFeedInputSeparator = '|'; + + /// + /// Transforms a input string into a package feed + /// If the string matches the {url}|{token} format, a private source will be created. + /// Otherwise, the input will be used as the URL of a public source. + /// + /// + /// + public static PackageSource ToPackageSource(this string input) + { + var parts = input.Split(PackageFeedInputSeparator); + + var url = parts.ElementAtOrDefault(0); + var accessToken = parts.ElementAtOrDefault(1); + + if(accessToken == null) + { + return new PackageSource(url); + } + + var sourceName = Guid.NewGuid().ToStringInvariant(); + + return new PackageSource(url) + { +#if WINDOWS_UWP + Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false), +#else + Credentials = PackageSourceCredential.FromUserInput(sourceName, "user", accessToken, false, null), +#endif + }; + } + } +} diff --git a/src/NuGet.Shared/Helpers/PackageHelper.cs b/src/NeoGet/Extensions/StringExtensions.cs similarity index 65% rename from src/NuGet.Shared/Helpers/PackageHelper.cs rename to src/NeoGet/Extensions/StringExtensions.cs index e7a232e..985a770 100644 --- a/src/NuGet.Shared/Helpers/PackageHelper.cs +++ b/src/NeoGet/Extensions/StringExtensions.cs @@ -1,15 +1,18 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using NuGet.Versioning; +using Uno.Extensions; -namespace NuGet.Updater.Helpers +namespace NeoGet.Extensions { - internal static class PackageHelper + internal static class StringExtensions { private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; - public static string GetUrl(string packageId, NuGetVersion version, Uri feedUri) + public static string GetPackageUrl(this string packageId, NuGetVersion version, Uri feedUri) { if(feedUri == null) { @@ -40,5 +43,25 @@ public static string GetUrl(string packageId, NuGetVersion version, Uri feedUri) return default; } + + public static string GetEnumeration(this IEnumerable values) + { + if(values.None()) + { + return string.Empty; + } + else if(values.Count() == 1) + { + return values.First(); + } + else if(values.Count() == 2) + { + return string.Join(" and ", values); + } + else + { + return $"{string.Join(", ", EnumerableExtensions.SkipLast(values, 1))} and {values.Last()}"; + } + } } } diff --git a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs b/src/NeoGet/Extensions/XmlDocumentExtensions.cs similarity index 88% rename from src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs rename to src/NeoGet/Extensions/XmlDocumentExtensions.cs index 708a04a..321fb62 100644 --- a/src/NuGet.Shared/Extensions/XmlDocumentExtensions.cs +++ b/src/NeoGet/Extensions/XmlDocumentExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using NeoGet.Entities; using NuGet.Packaging.Core; using NuGet.Versioning; using Uno.Extensions; @@ -20,7 +21,7 @@ using XmlNode = System.Xml.XmlNode; #endif -namespace NuGet.Shared.Extensions +namespace NeoGet.Extensions { public static class XmlDocumentExtensions { @@ -84,6 +85,32 @@ private static PackageIdentity CreatePackageIdentity(string id, string version) return default; } + /// + /// Opens the XML files where package references were found. + /// + /// + /// + /// + public static async Task> OpenFiles( + this IEnumerable references, + CancellationToken ct + ) + { + var files = references + .SelectMany(r => r.Files) + .SelectMany(g => g.Value) + .Distinct(); + + var documents = new Dictionary(); + + foreach(var file in files) + { + documents.Add(file, await file.LoadDocument(ct)); + } + + return documents; + } + #region Utilities /// diff --git a/src/NuGet.Shared/Helpers/FileHelper.Net.cs b/src/NeoGet/Helpers/FileHelper.Net.cs similarity index 80% rename from src/NuGet.Shared/Helpers/FileHelper.Net.cs rename to src/NeoGet/Helpers/FileHelper.Net.cs index 7df9685..1d2c1f2 100644 --- a/src/NuGet.Shared/Helpers/FileHelper.Net.cs +++ b/src/NeoGet/Helpers/FileHelper.Net.cs @@ -6,9 +6,9 @@ using System.Threading.Tasks; using Uno.Extensions; -namespace NuGet.Shared.Helpers +namespace NeoGet.Helpers { - public class FileHelper + public static class FileHelper { public static void LogToFile(string outputFilePath, string line) { @@ -33,19 +33,13 @@ public static async Task GetFiles(CancellationToken ct, string path, s } public static async Task ReadFileContent(CancellationToken ct, string path) - { - return File.ReadAllText(path); - } + => File.ReadAllText(path); public static async Task SetFileContent(CancellationToken ct, string path, string content) - { - File.WriteAllText(path, content, Encoding.UTF8); - } + => File.WriteAllText(path, content, Encoding.UTF8); public static async Task IsDirectory(CancellationToken ct, string path) - { - return (File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory; - } + => (File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory; public static async Task Exists(string path) => File.Exists(path); } diff --git a/src/NuGet.Shared/Helpers/FileHelper.UAP.cs b/src/NeoGet/Helpers/FileHelper.UAP.cs similarity index 98% rename from src/NuGet.Shared/Helpers/FileHelper.UAP.cs rename to src/NeoGet/Helpers/FileHelper.UAP.cs index 69d937c..e87a558 100644 --- a/src/NuGet.Shared/Helpers/FileHelper.UAP.cs +++ b/src/NeoGet/Helpers/FileHelper.UAP.cs @@ -6,7 +6,7 @@ using Windows.Storage; using Windows.Storage.Search; -namespace NuGet.Shared.Helpers +namespace NeoGet.Helpers { /// /// UAP-Specific methods. diff --git a/src/NuGet.Shared/Helpers/MarkdownHelper.cs b/src/NeoGet/Helpers/MarkdownHelper.cs similarity index 95% rename from src/NuGet.Shared/Helpers/MarkdownHelper.cs rename to src/NeoGet/Helpers/MarkdownHelper.cs index 9fb5c61..3020b10 100644 --- a/src/NuGet.Shared/Helpers/MarkdownHelper.cs +++ b/src/NeoGet/Helpers/MarkdownHelper.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using NeoGet.Extensions; using Uno.Extensions; -namespace NuGet.Shared.Helpers +namespace NeoGet.Helpers { public static class MarkdownHelper { @@ -12,7 +13,7 @@ public static class MarkdownHelper internal static string Bold(string text) => $"**{text}**"; - internal static string CodeBlocksEnumeration(IEnumerable values) => StringHelper.Enumeration(values.Select(v => CodeBlock(v))); + internal static string CodeBlocksEnumeration(IEnumerable values) => values.Select(v => CodeBlock(v)).GetEnumeration(); internal static string CodeBlock(Uri url, bool isMultiline = false, string language = null) => CodeBlock(url.OriginalString, isMultiline, language); diff --git a/src/NuGet.Shared/Helpers/SolutionHelper.cs b/src/NeoGet/Helpers/SolutionHelper.cs similarity index 94% rename from src/NuGet.Shared/Helpers/SolutionHelper.cs rename to src/NeoGet/Helpers/SolutionHelper.cs index 28f9893..3f18b75 100644 --- a/src/NuGet.Shared/Helpers/SolutionHelper.cs +++ b/src/NeoGet/Helpers/SolutionHelper.cs @@ -7,19 +7,20 @@ using System.Threading.Tasks; using NuGet.Common; using NuGet.Packaging.Core; -using NuGet.Shared.Entities; -using NuGet.Shared.Extensions; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Extensions; using NuGet.Versioning; using Uno.Extensions; -namespace NuGet.Shared.Helpers +namespace NeoGet.Helpers { /// /// Shared solution helper methods. /// - internal static partial class SolutionHelper + public static partial class SolutionHelper { - internal static async Task GetPackageReferences( + public static async Task GetPackageReferences( CancellationToken ct, string solutionPath, FileType fileType, @@ -77,7 +78,7 @@ ILogger log private static async Task GetProjectFiles(CancellationToken ct, string solutionPath, ILogger log) { - var files = new string[0]; + var files = Array.Empty(); if(await FileHelper.IsDirectory(ct, solutionPath)) { @@ -124,7 +125,7 @@ private static async Task GetDirectoryFiles(CancellationToken ct, stri return new[] { file }; } - return new string[0]; + return Array.Empty(); } private static async Task GetNuspecFiles(CancellationToken ct, string solutionPath, ILogger log) diff --git a/src/NuGet.Shared/Log/ConsoleLogger.cs b/src/NeoGet/Log/ConsoleLogger.cs similarity index 97% rename from src/NuGet.Shared/Log/ConsoleLogger.cs rename to src/NeoGet/Log/ConsoleLogger.cs index 41bd037..3a5fc53 100644 --- a/src/NuGet.Shared/Log/ConsoleLogger.cs +++ b/src/NeoGet/Log/ConsoleLogger.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using NuGet.Common; -namespace NuGet.Shared.Entities +namespace NeoGet.Entities { public class ConsoleLogger : ILogger { diff --git a/src/NuGet.Shared/Log/SimpleTextWriter.cs b/src/NeoGet/Log/SimpleTextWriter.cs similarity index 94% rename from src/NuGet.Shared/Log/SimpleTextWriter.cs rename to src/NeoGet/Log/SimpleTextWriter.cs index e1cbf6c..03f280d 100644 --- a/src/NuGet.Shared/Log/SimpleTextWriter.cs +++ b/src/NeoGet/Log/SimpleTextWriter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text; -namespace NuGet.Shared.Log +namespace NeoGet.Log { public class SimpleTextWriter : TextWriter { diff --git a/src/NuGet.Downloader/NuGet.Downloader.csproj b/src/NeoGet/NeoGet.csproj similarity index 75% rename from src/NuGet.Downloader/NuGet.Downloader.csproj rename to src/NeoGet/NeoGet.csproj index 50e2f1c..d338c86 100644 --- a/src/NuGet.Downloader/NuGet.Downloader.csproj +++ b/src/NeoGet/NeoGet.csproj @@ -1,30 +1,21 @@ - - + netstandard20;net472 + $(NoWarn);CA1068;NU1701 + true + 7.2 - nventive.NuGet.Downloader + nventive.NuGet.Updater nventive nventive - Nuget Dowloader allows to download the NuGet packages found in a solution + Nuget Updater allows to automatically update the NuGet packages in a solution at build time Apache-2.0 icon.png https://github.com/nventive/NuGet.Updater - - - - True - - - - - - - @@ -41,6 +32,10 @@ - - + + + True + + + diff --git a/src/NuGet.Downloader/Entities/DownloaderParameters.cs b/src/NeoGet/Tools/Downloader/Entities/DownloaderParameters.cs similarity index 85% rename from src/NuGet.Downloader/Entities/DownloaderParameters.cs rename to src/NeoGet/Tools/Downloader/Entities/DownloaderParameters.cs index 3edf0d0..c3010c8 100644 --- a/src/NuGet.Downloader/Entities/DownloaderParameters.cs +++ b/src/NeoGet/Tools/Downloader/Entities/DownloaderParameters.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using NuGet.Shared.Entities; +using NeoGet.Contracts; -namespace NuGet.Downloader.Entities +namespace NeoGet.Tools.Downloader.Entities { public class DownloaderParameters { diff --git a/src/NuGet.Downloader/Entities/DownloaderResult.cs b/src/NeoGet/Tools/Downloader/Entities/DownloaderResult.cs similarity index 68% rename from src/NuGet.Downloader/Entities/DownloaderResult.cs rename to src/NeoGet/Tools/Downloader/Entities/DownloaderResult.cs index 73bda3a..ddbaa0b 100644 --- a/src/NuGet.Downloader/Entities/DownloaderResult.cs +++ b/src/NeoGet/Tools/Downloader/Entities/DownloaderResult.cs @@ -1,6 +1,6 @@ -using NuGet.Shared.Entities; +using NeoGet.Entities; -namespace NuGet.Downloader.Entities +namespace NeoGet.Tools.Downloader.Entities { public class DownloaderResult { diff --git a/src/NuGet.Downloader/NuGetDownloader.cs b/src/NeoGet/Tools/Downloader/NuGetDownloader.cs similarity index 88% rename from src/NuGet.Downloader/NuGetDownloader.cs rename to src/NeoGet/Tools/Downloader/NuGetDownloader.cs index 5888464..ffea02c 100644 --- a/src/NuGet.Downloader/NuGetDownloader.cs +++ b/src/NeoGet/Tools/Downloader/NuGetDownloader.cs @@ -1,18 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Helpers; +using NeoGet.Tools.Downloader.Entities; using NuGet.Common; -using NuGet.Downloader.Entities; using NuGet.Packaging.Core; -using NuGet.Shared.Entities; -using NuGet.Shared.Helpers; using Uno.Extensions; -namespace NuGet.Downloader +namespace NeoGet.Tools.Downloader { public class NuGetDownloader { @@ -122,13 +122,15 @@ private async Task> GetPackagesWithDependencies( foreach(var package in packages) { try - { + { var packageDependencies = await source.GetDependencies(ct, package); - _log.LogInformation($"Found {packageDependencies.Length} dependencies for {package} in {source.Url}"); + var flatDependencies = packageDependencies.SelectMany(g => g.Value).ToArray(); + + _log.LogInformation($"Found {flatDependencies.Length} dependencies for {package} in {source.Url}"); //TODO: make the version used parametrable (Use MaxVersion or MinVersion) - packagesToDonwload.AddRange(packageDependencies.Select(d => new PackageIdentity(d.Id, d.VersionRange.MinVersion))); + packagesToDonwload.AddRange(flatDependencies.Select(d => new PackageIdentity(d.Id, d.VersionRange.MinVersion))); knownPackages.Add(package); } @@ -143,7 +145,7 @@ private async Task> GetPackagesWithDependencies( //Take all the dependencies found var unknownPackages = new HashSet(packagesToDonwload); //Remove the packages that we already know are missing - unknownPackages.ExceptWith(missingPackages); + unknownPackages.ExceptWith(missingPackages); //Remove the packages we already have dependencies for unknownPackages.ExceptWith(knownPackages); diff --git a/src/NuGet.Updater/Entities/LogDisplayOptions.cs b/src/NeoGet/Tools/Updater/Entities/LogDisplayOptions.cs similarity index 90% rename from src/NuGet.Updater/Entities/LogDisplayOptions.cs rename to src/NeoGet/Tools/Updater/Entities/LogDisplayOptions.cs index 5b8b430..6624d3e 100644 --- a/src/NuGet.Updater/Entities/LogDisplayOptions.cs +++ b/src/NeoGet/Tools/Updater/Entities/LogDisplayOptions.cs @@ -1,4 +1,4 @@ -namespace NuGet.Updater.Entities +namespace NeoGet.Tools.Updater.Entities { internal struct LogDisplayOptions { diff --git a/src/NuGet.Updater/Entities/UpdateResult.cs b/src/NeoGet/Tools/Updater/Entities/UpdateResult.cs similarity index 89% rename from src/NuGet.Updater/Entities/UpdateResult.cs rename to src/NeoGet/Tools/Updater/Entities/UpdateResult.cs index b270f90..a36e6c0 100644 --- a/src/NuGet.Updater/Entities/UpdateResult.cs +++ b/src/NeoGet/Tools/Updater/Entities/UpdateResult.cs @@ -1,7 +1,6 @@ using System; -using NuGet.Versioning; -namespace NuGet.Updater.Entities +namespace NeoGet.Tools.Updater.Entities { public class UpdateResult : IEquatable { diff --git a/src/NuGet.Updater/Entities/UpdaterPackage.cs b/src/NeoGet/Tools/Updater/Entities/UpdaterPackage.cs similarity index 83% rename from src/NuGet.Updater/Entities/UpdaterPackage.cs rename to src/NeoGet/Tools/Updater/Entities/UpdaterPackage.cs index 7ab9931..3557869 100644 --- a/src/NuGet.Updater/Entities/UpdaterPackage.cs +++ b/src/NeoGet/Tools/Updater/Entities/UpdaterPackage.cs @@ -1,6 +1,6 @@ -using NuGet.Shared.Entities; +using NeoGet.Entities; -namespace NuGet.Updater.Entities +namespace NeoGet.Tools.Updater.Entities { public class UpdaterPackage { diff --git a/src/NuGet.Updater/Entities/UpdaterParameters.cs b/src/NeoGet/Tools/Updater/Entities/UpdaterParameters.cs similarity index 91% rename from src/NuGet.Updater/Entities/UpdaterParameters.cs rename to src/NeoGet/Tools/Updater/Entities/UpdaterParameters.cs index 52f75d7..a5c536f 100644 --- a/src/NuGet.Updater/Entities/UpdaterParameters.cs +++ b/src/NeoGet/Tools/Updater/Entities/UpdaterParameters.cs @@ -1,8 +1,12 @@ -using System.Collections.Generic; -using NuGet.Shared.Entities; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NeoGet.Contracts; +using NeoGet.Entities; using NuGet.Versioning; -namespace NuGet.Updater.Entities +namespace NeoGet.Tools.Updater.Entities { public class UpdaterParameters { diff --git a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs b/src/NeoGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs similarity index 89% rename from src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs rename to src/NeoGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs index f58eb77..ce65d48 100644 --- a/src/NuGet.Updater/Extensions/UpdateOperationExtensions.cs +++ b/src/NeoGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs @@ -1,8 +1,8 @@ -using NuGet.Shared.Extensions; -using NuGet.Updater.Entities; -using NuGet.Updater.Log; +using NeoGet.Extensions; +using NeoGet.Tools.Updater.Entities; +using NeoGet.Tools.Updater.Log; -namespace NuGet.Updater.Extensions +namespace NeoGet.Tools.Updater.Extensions { public static class UpdateOperationExtensions { @@ -16,7 +16,7 @@ public static class UpdateOperationExtensions { PackageId = operation.PackageId, OriginalVersion = operation.PreviousVersion.OriginalVersion, - UpdatedVersion = operation.UpdatedVersion.OriginalVersion, + UpdatedVersion = operation.UpdatedVersion?.OriginalVersion, }; public static bool ShouldProceed(this UpdateOperation operation) diff --git a/src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs b/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs similarity index 100% rename from src/NuGet.Updater/Extensions/UpdaterParametersExtension.cs rename to src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs diff --git a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs b/src/NeoGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs similarity index 97% rename from src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs rename to src/NeoGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index 38cf3d4..f10f610 100644 --- a/src/NuGet.Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NeoGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; using System.Xml; -using NuGet.Shared.Extensions; -using NuGet.Updater.Log; +using NeoGet.Extensions; +using NeoGet.Tools.Updater.Log; using Uno.Extensions; #if WINDOWS_UWP @@ -12,7 +12,7 @@ using XmlDocument = System.Xml.XmlDocument; #endif -namespace NuGet.Updater.Extensions +namespace NeoGet.Tools.Updater.Extensions { public static class XmlDocumentExtensions { diff --git a/src/NuGet.Updater/Log/UpdateOperation.cs b/src/NeoGet/Tools/Updater/Log/UpdateOperation.cs similarity index 96% rename from src/NuGet.Updater/Log/UpdateOperation.cs rename to src/NeoGet/Tools/Updater/Log/UpdateOperation.cs index dae8a3e..b5a33fb 100644 --- a/src/NuGet.Updater/Log/UpdateOperation.cs +++ b/src/NeoGet/Tools/Updater/Log/UpdateOperation.cs @@ -1,9 +1,9 @@ using System; +using NeoGet.Entities; using NuGet.Packaging.Core; -using NuGet.Shared.Entities; using NuGet.Versioning; -namespace NuGet.Updater.Log +namespace NeoGet.Tools.Updater.Log { public class UpdateOperation { diff --git a/src/NuGet.Updater/Log/UpdaterLogger.cs b/src/NeoGet/Tools/Updater/Log/UpdaterLogger.cs similarity index 95% rename from src/NuGet.Updater/Log/UpdaterLogger.cs rename to src/NeoGet/Tools/Updater/Log/UpdaterLogger.cs index 725a0df..61e6876 100644 --- a/src/NuGet.Updater/Log/UpdaterLogger.cs +++ b/src/NeoGet/Tools/Updater/Log/UpdaterLogger.cs @@ -3,14 +3,14 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using NeoGet.Extensions; +using NeoGet.Helpers; +using NeoGet.Tools.Updater.Entities; +using NeoGet.Tools.Updater.Extensions; using NuGet.Common; -using NuGet.Shared.Helpers; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; -using NuGet.Updater.Helpers; using NuGet.Versioning; -namespace NuGet.Updater.Log +namespace NeoGet.Tools.Updater.Log { public class UpdaterLogger : ILogger, IEqualityComparer { @@ -31,7 +31,7 @@ public UpdaterLogger(TextWriter writer, TextWriter summaryWriter = null) public void Clear() => _updateOperations.Clear(); - internal IEnumerable GetUpdates() => _updateOperations; + public IEnumerable GetUpdates() => _updateOperations; public void Write(string message) => _writer.WriteLine(message); @@ -187,7 +187,7 @@ private string GetPreviousVersionText(UpdateOperation operation, bool includeUrl private string GetVersionText(NuGetVersion version, string packageId, Uri feedUri, bool includeUrl) => MarkdownHelper.Link( version.OriginalVersion, - includeUrl ? PackageHelper.GetUrl(packageId, version, feedUri) : null + includeUrl ? packageId.GetPackageUrl(version, feedUri) : null ); #region ILogger diff --git a/src/NuGet.Updater/NuGetUpdater.cs b/src/NeoGet/Tools/Updater/NuGetUpdater.cs similarity index 91% rename from src/NuGet.Updater/NuGetUpdater.cs rename to src/NeoGet/Tools/Updater/NuGetUpdater.cs index 3556ad1..5b893c5 100644 --- a/src/NuGet.Updater/NuGetUpdater.cs +++ b/src/NeoGet/Tools/Updater/NuGetUpdater.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NuGet.Shared.Entities; -using NuGet.Shared.Extensions; -using NuGet.Shared.Helpers; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; -using NuGet.Updater.Log; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Extensions; +using NeoGet.Helpers; +using NeoGet.Tools.Updater.Entities; +using NeoGet.Tools.Updater.Extensions; +using NeoGet.Tools.Updater.Log; using Uno.Extensions; #if WINDOWS_UWP @@ -20,7 +21,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NuGet.Updater.Tests")] -namespace NuGet.Updater +namespace NeoGet.Tools.Updater { public class NuGetUpdater { @@ -44,7 +45,7 @@ public NuGetUpdater(UpdaterParameters parameters, TextWriter logWriter, TextWrit { } - internal NuGetUpdater(UpdaterParameters parameters, UpdaterLogger log) + public NuGetUpdater(UpdaterParameters parameters, UpdaterLogger log) { _parameters = parameters.Validate(); _log = log; @@ -126,7 +127,7 @@ internal async Task GetPackages(CancellationToken ct) continue; } - packages.Add(new UpdaterPackage(reference, await reference.GetLatestVersion(ct, packages, _parameters))); + packages.Add(new UpdaterPackage(reference, await _parameters.GetLatestVersion(ct, packages, reference))); } return packages.ToArray(); diff --git a/src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj b/src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj deleted file mode 100644 index 8ae4c5b..0000000 --- a/src/NuGet.Downloader.Tests/NuGet.Downloader.Tests.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netcoreapp3.0 - - false - - - - - - - - - - diff --git a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj index a35f2c5..8dfe426 100644 --- a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj +++ b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/NuGet.Downloader.Tool/Program.cs b/src/NuGet.Downloader.Tool/Program.cs index 9b27d38..06c4e81 100644 --- a/src/NuGet.Downloader.Tool/Program.cs +++ b/src/NuGet.Downloader.Tool/Program.cs @@ -3,13 +3,14 @@ using System.Threading; using System.Threading.Tasks; using Mono.Options; -using NuGet.Downloader.Entities; -using NuGet.Shared.Entities; +using NeoGet.Entities; +using NeoGet.Tools.Downloader; +using NeoGet.Tools.Downloader.Entities; using Uno.Extensions; namespace NuGet.Downloader.Tool { - public class Program + public static class Program { public static async Task Main(string[] args) { diff --git a/src/NuGet.Shared/Helpers/StringHelper.cs b/src/NuGet.Shared/Helpers/StringHelper.cs deleted file mode 100644 index 568e712..0000000 --- a/src/NuGet.Shared/Helpers/StringHelper.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Uno.Extensions; - -namespace NuGet.Shared.Helpers -{ - public static class StringHelper - { - internal static string Enumeration(IEnumerable values) - { - if(values.None()) - { - return string.Empty; - } - else if(values.Count() == 1) - { - return values.First(); - } - else if(values.Count() == 2) - { - return string.Join(" and ", values); - } - else - { - return $"{string.Join(", ", EnumerableExtensions.SkipLast(values, 1))} and {values.Last()}"; - } - } - } -} diff --git a/src/NuGet.Shared/NuGet.Shared.projitems b/src/NuGet.Shared/NuGet.Shared.projitems deleted file mode 100644 index 3efa2dc..0000000 --- a/src/NuGet.Shared/NuGet.Shared.projitems +++ /dev/null @@ -1,35 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 3890cca3-b7cf-450b-a6cd-4a4106b0008c - - - NuGet.Shared - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NuGet.Updater.Tests/PackageReferenceTests.cs b/src/NuGet.Updater.Tests/PackageReferenceTests.cs deleted file mode 100644 index 5b270b6..0000000 --- a/src/NuGet.Updater.Tests/PackageReferenceTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using NuGet.Shared.Entities; -using NuGet.Updater.Entities; -using NuGet.Updater.Extensions; -using NuGet.Updater.Tests.Entities; -using NuGet.Versioning; - -namespace NuGet.Updater.Tests -{ - [TestClass] - public class PackageReferenceTests - { - private static readonly Uri TestFeedUri = new Uri("http://localhost"); - - private static readonly Dictionary TestPackages = new Dictionary - { - {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, - {"Uno.UI", new[] { "2.1.39", "2.2.0", "2.3.0-dev.44", "2.3.0-dev.48", "2.3.0-dev.58" } }, - }; - - private static readonly TestPackageFeed TestFeed = new TestPackageFeed(TestFeedUri, TestPackages); - - [TestMethod] - public async Task GivenPackageWithMatchingVersion_VersionIsFound() - { - var parameters = new UpdaterParameters - { - TargetVersions = { "beta" }, - Feeds = { TestFeed }, - }; - - var packageVersion = "1.0-beta.1"; - var packageId = "nventive.NuGet.Updater"; - - var reference = new PackageReference(packageId, packageVersion); - - var version = await reference.GetLatestVersion(CancellationToken.None, parameters); - - Assert.IsNotNull(version); - Assert.AreEqual(version.Version.OriginalVersion, packageVersion); - } - - [TestMethod] - public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() - { - var parameters = new UpdaterParameters - { - TargetVersions = { "stable" }, - }; - - var packageVersion = "1.0-beta.1"; - var packageId = "nventive.NuGet.Updater"; - - var reference = new PackageReference(packageId, packageVersion); - - var version = await reference.GetLatestVersion(CancellationToken.None, parameters); - - Assert.IsNull(version); - } - - [TestMethod] - public async Task GivenManualUpdates_AndVersionNotInFeed_ManualVersionIsFound() - { - var reference = new PackageReference("nventive.NuGet.Updater", "1.0"); - - var parameters = new UpdaterParameters - { - TargetVersions = { "stable" }, - Feeds = { TestFeed }, - VersionOverrides = - { - { reference.Identity.Id, (true, new VersionRange(reference.Identity.Version, true, reference.Identity.Version, true)) }, - }, - }; - - var version = await reference.GetLatestVersion(CancellationToken.None, parameters); - - Assert.AreEqual(version.Version, reference.Identity.Version); - } - - [TestMethod] - public async Task GivenRangeOverrides_CorrectVersionsAreResolved() - { - var reference = new PackageReference("Uno.UI", "2.1.39"); - - var parameters = new UpdaterParameters - { - TargetVersions = { "dev", "stable" }, - Feeds = { TestFeed }, - VersionOverrides = - { - { reference.Identity.Id, (false, VersionRange.Parse("(,2.3.0-dev.48]")) }, - }, - }; - - var version = await reference.GetLatestVersion(CancellationToken.None, parameters); - - Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.48"), version.Version); - - parameters.VersionOverrides["Uno.UI"] = (false, VersionRange.Parse("(,2.3.0-dev.48)")); - - version = await reference.GetLatestVersion(CancellationToken.None, parameters); - - Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.44"), version.Version); - } - - [TestMethod] - public async Task GivenRangeOverrides_CorrectVersionsAreResolved_AndTargetVersionIsHonored() - { - var reference = new PackageReference("Uno.UI", "2.1.39"); - - var parameters = new UpdaterParameters - { - TargetVersions = { "stable" }, - Feeds = { TestFeed }, - VersionOverrides = - { - { reference.Identity.Id, (false, VersionRange.Parse("(,2.3.0-dev.48]")) }, - }, - }; - - var version = await reference.GetLatestVersion(CancellationToken.None, parameters); - - Assert.AreEqual(NuGetVersion.Parse("2.2.0"), version.Version); - } - } -} diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index 2b2a8e8..86ea6f1 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -25,7 +25,6 @@ - @@ -41,10 +40,15 @@ + + + + + diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index da63c0b..df48a5e 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -4,15 +4,17 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using NeoGet.Helpers; +using NeoGet.Log; +using NeoGet.Tools.Updater; +using NeoGet.Tools.Updater.Arguments; +using NeoGet.Tools.Updater.Entities; using Newtonsoft.Json; -using NuGet.Shared.Helpers; -using NuGet.Shared.Log; -using NuGet.Updater.Entities; -using NuGet.Updater.Tool.Arguments; +using Uno.Extensions; namespace NuGet.Updater.Tool { - public class Program + public static class Program { public static async Task Main(string[] args) { @@ -43,7 +45,8 @@ public static async Task Main(string[] args) } catch(Exception ex) { - Console.WriteLine($"Error: {ex.Message}"); + Console.Error.WriteLine($"Failed to update nuget packages: {ex.Message}"); + Console.Error.WriteLine($"{ex}"); } } @@ -51,7 +54,7 @@ public static async Task Main(string[] args) private static void Save(IEnumerable result, string path) { - if(path == null || path == "") + if(path.IsNullOrEmpty()) { return; } diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index 5be5713..ccdf654 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29021.104 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater", "Nuget.Updater\NuGet.Updater.csproj", "{29277270-8EFE-41A3-8CD8-D54EBF514C41}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tool", "NuGet.Updater.Tool\NuGet.Updater.Tool.csproj", "{3DAA81DB-00A6-4A84-8180-19A588398F52}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0547178F-3639-4797-940F-265958B7FECA}" @@ -23,35 +21,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tests", "NuGet.Updater.Tests\NuGet.Updater.Tests.csproj", "{D240ABFD-6A48-4E4B-A946-9EBF9840FADA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader", "NuGet.Downloader\NuGet.Downloader.csproj", "{912018BD-D988-4C90-B4EC-BA1C171207C3}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NuGet.Shared", "NuGet.Shared\NuGet.Shared.shproj", "{3890CCA3-B7CF-450B-A6CD-4A4106B0008C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoGet.Tests", "NeoGet.Tests\NeoGet.Tests.csproj", "{D240ABFD-6A48-4E4B-A946-9EBF9840FADA}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet.Updater", "NuGet.Updater", "{54F00313-D565-4D02-9A4B-D2FFF0190934}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tool", "NuGet.Downloader.Tool\NuGet.Downloader.Tool.csproj", "{17979C76-865A-4983-977B-E242BCE1B038}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet.Downloader", "NuGet.Downloader", "{58AE6856-BDF1-4676-8FF3-39B95B7762C1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoGet", "NeoGet\NeoGet.csproj", "{5FBA174D-C841-4968-A8E0-3BA97B839123}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tool", "NuGet.Downloader.Tool\NuGet.Downloader.Tool.csproj", "{17979C76-865A-4983-977B-E242BCE1B038}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{7B2F2CBF-9EC9-4C18-A471-D36862EE23D9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tests", "NuGet.Downloader.Tests\NuGet.Downloader.Tests.csproj", "{4CCA4711-1333-4D4F-91A4-B6E4263D7244}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NeoGet.Tools.Shared", "NeoGet.Tools.Shared\NeoGet.Tools.Shared.shproj", "{4C395D24-0028-47EF-B07E-5566344E78DE}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution - NuGet.Shared\NuGet.Shared.projitems*{29277270-8efe-41a3-8cd8-d54ebf514c41}*SharedItemsImports = 5 - NuGet.Shared\NuGet.Shared.projitems*{3890cca3-b7cf-450b-a6cd-4a4106b0008c}*SharedItemsImports = 13 - NuGet.Shared\NuGet.Shared.projitems*{912018bd-d988-4c90-b4ec-ba1c171207c3}*SharedItemsImports = 5 + NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{3daa81db-00a6-4a84-8180-19a588398f52}*SharedItemsImports = 5 + NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{4c395d24-0028-47ef-b07e-5566344e78de}*SharedItemsImports = 13 + NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{d240abfd-6a48-4e4b-a946-9ebf9840fada}*SharedItemsImports = 5 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29277270-8EFE-41A3-8CD8-D54EBF514C41}.Release|Any CPU.Build.0 = Release|Any CPU {3DAA81DB-00A6-4A84-8180-19A588398F52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3DAA81DB-00A6-4A84-8180-19A588398F52}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DAA81DB-00A6-4A84-8180-19A588398F52}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -60,29 +50,21 @@ Global {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Debug|Any CPU.Build.0 = Debug|Any CPU {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Release|Any CPU.ActiveCfg = Release|Any CPU {D240ABFD-6A48-4E4B-A946-9EBF9840FADA}.Release|Any CPU.Build.0 = Release|Any CPU - {912018BD-D988-4C90-B4EC-BA1C171207C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {912018BD-D988-4C90-B4EC-BA1C171207C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {912018BD-D988-4C90-B4EC-BA1C171207C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {912018BD-D988-4C90-B4EC-BA1C171207C3}.Release|Any CPU.Build.0 = Release|Any CPU {17979C76-865A-4983-977B-E242BCE1B038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17979C76-865A-4983-977B-E242BCE1B038}.Debug|Any CPU.Build.0 = Debug|Any CPU {17979C76-865A-4983-977B-E242BCE1B038}.Release|Any CPU.ActiveCfg = Release|Any CPU {17979C76-865A-4983-977B-E242BCE1B038}.Release|Any CPU.Build.0 = Release|Any CPU - {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4CCA4711-1333-4D4F-91A4-B6E4263D7244}.Release|Any CPU.Build.0 = Release|Any CPU + {5FBA174D-C841-4968-A8E0-3BA97B839123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FBA174D-C841-4968-A8E0-3BA97B839123}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FBA174D-C841-4968-A8E0-3BA97B839123}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FBA174D-C841-4968-A8E0-3BA97B839123}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {29277270-8EFE-41A3-8CD8-D54EBF514C41} = {54F00313-D565-4D02-9A4B-D2FFF0190934} - {3DAA81DB-00A6-4A84-8180-19A588398F52} = {54F00313-D565-4D02-9A4B-D2FFF0190934} - {D240ABFD-6A48-4E4B-A946-9EBF9840FADA} = {54F00313-D565-4D02-9A4B-D2FFF0190934} - {912018BD-D988-4C90-B4EC-BA1C171207C3} = {58AE6856-BDF1-4676-8FF3-39B95B7762C1} - {17979C76-865A-4983-977B-E242BCE1B038} = {58AE6856-BDF1-4676-8FF3-39B95B7762C1} - {4CCA4711-1333-4D4F-91A4-B6E4263D7244} = {58AE6856-BDF1-4676-8FF3-39B95B7762C1} + {3DAA81DB-00A6-4A84-8180-19A588398F52} = {7B2F2CBF-9EC9-4C18-A471-D36862EE23D9} + {17979C76-865A-4983-977B-E242BCE1B038} = {7B2F2CBF-9EC9-4C18-A471-D36862EE23D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1C528FB3-4BD1-4D56-ABEE-A20F6367040F} diff --git a/src/NuGet.Updater/Extensions/FeedVersionExtensions.cs b/src/NuGet.Updater/Extensions/FeedVersionExtensions.cs deleted file mode 100644 index 7ff149c..0000000 --- a/src/NuGet.Updater/Extensions/FeedVersionExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Shared.Entities; -using NuGet.Updater.Entities; - -namespace NuGet.Updater.Extensions -{ - public static class FeedVersionExtensions - { - /// - /// Gets the latest version for the given reference by looking up first in a list of known packages. - /// Useful in the cases where refernces to multiple versions of the same packages are found. - /// - public static async Task GetLatestVersion( - this PackageReference reference, - CancellationToken ct, - IEnumerable knownPackages, - UpdaterParameters parameters - ) - { - var knownVersion = knownPackages.FirstOrDefault(p => p.PackageId == reference.Identity.Id)?.Version; - - if(knownVersion == null) - { - knownVersion = await reference.GetLatestVersion(ct, parameters); - } - - return knownVersion; - } - } -} diff --git a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs b/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs deleted file mode 100644 index b3c0738..0000000 --- a/src/NuGet.Updater/Extensions/PackageReferenceExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NuGet.Common; -using NuGet.Shared.Entities; -using NuGet.Shared.Extensions; -using NuGet.Updater.Entities; -using Uno.Extensions; - -#if WINDOWS_UWP -using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; -#else -using XmlDocument = System.Xml.XmlDocument; -#endif - -namespace NuGet.Updater.Extensions -{ - public static class PackageReferenceExtensions - { - /// - /// Opens the XML files where package references were found. - /// - /// - /// - /// - public static async Task> OpenFiles( - this IEnumerable references, - CancellationToken ct - ) - { - var files = references - .SelectMany(r => r.Files) - .SelectMany(g => g.Value) - .Distinct(); - - var documents = new Dictionary(); - - foreach(var file in files) - { - documents.Add(file, await file.LoadDocument(ct)); - } - - return documents; - } - - public static async Task GetLatestVersion( - this PackageReference reference, - CancellationToken ct, - UpdaterParameters parameters - ) - { - if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion) && manualVersion.forceVersion) - { - PackageFeed.Logger.LogInformation($"Overriding version for {reference.Identity.Id}"); - return new FeedVersion(manualVersion.range.MinVersion); - } - - var availableVersions = await Task.WhenAll(parameters - .Feeds - .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor)) - ); - - var versionsPerTarget = availableVersions - .SelectMany(x => x) - .Where(v => manualVersion.range?.Satisfies(v.Version) ?? true) - .OrderByDescending(v => v) - .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) - .Where(g => g.Key.HasValue()); - - return versionsPerTarget - .Select(g => g.FirstOrDefault()) - .OrderByDescending(v => v.Version) - .FirstOrDefault(); - } - } -} diff --git a/src/NuGet.Updater/NuGet.Updater.csproj b/src/NuGet.Updater/NuGet.Updater.csproj deleted file mode 100644 index 539a2e4..0000000 --- a/src/NuGet.Updater/NuGet.Updater.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - netstandard20;net472;uap10.0.17763 - true - 7.2 - - - - - nventive.NuGet.Updater - nventive - nventive - Nuget Updater allows to automatically update the NuGet packages in a solution at build time - Apache-2.0 - icon.png - https://github.com/nventive/NuGet.Updater - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - True - - - - - - - - true - lib/uap10.0.17763 - - - - - - \ No newline at end of file From 92d9b655263c7f1b06772aaf081161388559bc19 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 27 Feb 2020 16:31:02 -0500 Subject: [PATCH 163/201] Added missing methods This is done in a separate commit so that this file is correctly tracked as a rename. Doing this change and renaming causes git to lose track of the file --- .../Extensions/UpdaterParametersExtension.cs | 69 ++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs b/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs index 33c7ef5..0d4a6f4 100644 --- a/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; -using NuGet.Shared.Entities; -using NuGet.Shared.Extensions; -using NuGet.Shared.Helpers; -using NuGet.Updater.Entities; +using System.Threading; +using System.Threading.Tasks; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Extensions; +using NeoGet.Helpers; +using NeoGet.Tools.Updater.Entities; using Uno.Extensions; -namespace NuGet.Updater.Extensions +namespace NeoGet.Tools.Updater.Extensions { - internal static class UpdaterParametersExtension + public static class UpdaterParametersExtension { internal static IEnumerable GetSummary(this UpdaterParameters parameters) { @@ -39,12 +42,12 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Using {MarkdownHelper.CodeBlocksEnumeration(parameters.TargetVersions)} versions {(parameters.Strict ? "(exact match)" : "")}"; - if (parameters.IsDowngradeAllowed) + if(parameters.IsDowngradeAllowed) { yield return $"- Downgrading packages if a lower version is found"; } - if (parameters.PackagesToUpdate?.Any() ?? false) + if(parameters.PackagesToUpdate?.Any() ?? false) { yield return $"- Updating only {MarkdownHelper.CodeBlocksEnumeration(parameters.PackagesToUpdate)}"; } @@ -59,5 +62,55 @@ public static UpdaterParameters Validate(this UpdaterParameters parameters) return parameters; } + + /// + /// Gets the latest version for the given reference by looking up first in a list of known packages. + /// Useful in the cases where refernces to multiple versions of the same packages are found. + /// + public static async Task GetLatestVersion( + this UpdaterParameters parameters, + CancellationToken ct, + IEnumerable knownPackages, + PackageReference reference + ) + { + var knownVersion = knownPackages.FirstOrDefault(p => p.PackageId == reference.Identity.Id)?.Version; + + if(knownVersion == null) + { + knownVersion = await parameters.GetLatestVersion(ct, reference); + } + + return knownVersion; + } + + public static async Task GetLatestVersion( + this UpdaterParameters parameters, + CancellationToken ct, + PackageReference reference + ) + { + if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion)) + { + PackageFeed.Logger.LogInformation($"Overriding version for {reference.Identity.Id}"); + return new FeedVersion(manualVersion); + } + + var availableVersions = await Task.WhenAll(parameters + .Feeds + .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor)) + ); + + var versionsPerTarget = availableVersions + .SelectMany(x => x) + .OrderByDescending(v => v) + .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) + .Where(g => g.Key.HasValue()); + + return versionsPerTarget + .Select(g => g.FirstOrDefault()) + .OrderByDescending(v => v.Version) + .FirstOrDefault(); + } } } From f0f649f3d44c9cf25ea12a656f2bbf266ec92f97 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 27 Feb 2020 17:54:03 -0500 Subject: [PATCH 164/201] Added a tool to retrieve a Nuget hierarchy --- .../Extensions/PackageDependencyExtensions.cs | 9 ++ .../Tools/Downloader/NuGetDownloader.cs | 83 ++------------ .../Hierarchy/Entities/PackageHierarchy.cs | 17 +++ .../Entities/PackageHierarchyItem.cs | 33 ++++++ .../Extensions/PackageHierarchyExtensions.cs | 108 ++++++++++++++++++ .../PackageHierarchyItemExtensions.cs | 49 ++++++++ src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs | 97 ++++++++++++++++ .../Extensions/UpdaterParametersExtension.cs | 5 +- src/NuGet.Updater.Tool/Program.cs | 7 ++ 9 files changed, 330 insertions(+), 78 deletions(-) create mode 100644 src/NeoGet/Extensions/PackageDependencyExtensions.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs create mode 100644 src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs diff --git a/src/NeoGet/Extensions/PackageDependencyExtensions.cs b/src/NeoGet/Extensions/PackageDependencyExtensions.cs new file mode 100644 index 0000000..521b098 --- /dev/null +++ b/src/NeoGet/Extensions/PackageDependencyExtensions.cs @@ -0,0 +1,9 @@ +using NuGet.Packaging.Core; + +namespace NeoGet.Extensions +{ + public static class PackageDependencyExtensions + { + public static PackageIdentity GetIdentity(this PackageDependency dependency) => new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion); + } +} diff --git a/src/NeoGet/Tools/Downloader/NuGetDownloader.cs b/src/NeoGet/Tools/Downloader/NuGetDownloader.cs index ffea02c..cfa5f0d 100644 --- a/src/NeoGet/Tools/Downloader/NuGetDownloader.cs +++ b/src/NeoGet/Tools/Downloader/NuGetDownloader.cs @@ -6,8 +6,9 @@ using System.Threading.Tasks; using NeoGet.Contracts; using NeoGet.Entities; -using NeoGet.Helpers; using NeoGet.Tools.Downloader.Entities; +using NeoGet.Tools.Hierarchy; +using NeoGet.Tools.Hierarchy.Extensions; using NuGet.Common; using NuGet.Packaging.Core; using Uno.Extensions; @@ -57,23 +58,11 @@ private async Task DownloadPackages( string outputPath ) { - var downloadedPackages = new List(); - Directory.CreateDirectory(outputPath); - foreach(var package in packages) - { - var localPackage = await sourceFeed.DownloadPackage(ct, package, outputPath); - - if(localPackage == null) - { - throw new PackageNotFoundException(package, sourceFeed.Url); //Shouldn't happen - } - - downloadedPackages.Add(localPackage); - } + var downloadedPackages = await Task.WhenAll(packages.Select(package => sourceFeed.DownloadPackage(ct, package, outputPath))); - return downloadedPackages.ToArray(); + return downloadedPackages.Trim().ToArray(); } private async Task PushPackages(CancellationToken ct, IEnumerable packages, IPackageFeed targetFeed) @@ -98,69 +87,11 @@ private async Task PushPackages(CancellationToken ct, IEnumerabl private async Task> GetPackagesToDownload(CancellationToken ct, string solutionPath, IPackageFeed source) { - var references = await SolutionHelper.GetPackageReferences(ct, solutionPath, FileType.All, _log); - - var identities = new HashSet(references.Select(r => r.Identity)); - - return await GetPackagesWithDependencies(ct, identities, source); - } - - private async Task> GetPackagesWithDependencies( - CancellationToken ct, - ISet packages, - IPackageFeed source, - ISet knownPackages = null, - ISet missingPackages = null - ) - { - knownPackages = knownPackages ?? new HashSet(); - missingPackages = missingPackages ?? new HashSet(); - - //Assume we will have to download the packages passed - var packagesToDonwload = new HashSet(packages); - - foreach(var package in packages) - { - try - { - var packageDependencies = await source.GetDependencies(ct, package); - - var flatDependencies = packageDependencies.SelectMany(g => g.Value).ToArray(); - - _log.LogInformation($"Found {flatDependencies.Length} dependencies for {package} in {source.Url}"); - - //TODO: make the version used parametrable (Use MaxVersion or MinVersion) - packagesToDonwload.AddRange(flatDependencies.Select(d => new PackageIdentity(d.Id, d.VersionRange.MinVersion))); - - knownPackages.Add(package); - } - catch(PackageNotFoundException ex) - { - _log.LogInformation(ex.Message); - - missingPackages.Add(package); - } - } - - //Take all the dependencies found - var unknownPackages = new HashSet(packagesToDonwload); - //Remove the packages that we already know are missing - unknownPackages.ExceptWith(missingPackages); - //Remove the packages we already have dependencies for - unknownPackages.ExceptWith(knownPackages); - - if(unknownPackages.Any()) - { - //Get the dependencies for the rest - var subDependencies = await GetPackagesWithDependencies(ct, unknownPackages, source, knownPackages, missingPackages); - //Add those to the packages to download - packagesToDonwload.UnionWith(subDependencies); - } + var hierachy = new NuGetHierarchy(solutionPath, new[] { source }, _log); - //Remove the packages that we know are missing - packagesToDonwload.ExceptWith(missingPackages); + var result = await hierachy.RunAsync(ct); - return packagesToDonwload; + return result.GetAllIdentities(); } } } diff --git a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs new file mode 100644 index 0000000..fad884a --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace NeoGet.Tools.Hierarchy.Entities +{ + public class PackageHierarchy + { + public PackageHierarchy(string source, IEnumerable packages) + { + Source = source; + Packages = new List(packages); + } + + public string Source { get; set; } + + public List Packages { get; set; } + } +} diff --git a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs new file mode 100644 index 0000000..7380e8b --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using NeoGet.Extensions; +using NuGet.Frameworks; +using NuGet.Packaging.Core; + +namespace NeoGet.Tools.Hierarchy.Entities +{ + public class PackageHierarchyItem + { + public PackageHierarchyItem(PackageIdentity identity) + { + Identity = identity; + } + + public PackageHierarchyItem(PackageIdentity identity, IDictionary dependencies) + : this(identity) + { + Dependencies = new Dictionary(dependencies); + } + + public PackageHierarchyItem(PackageIdentity identity, IDictionary dependencies) + : this(identity, dependencies.ToDictionary(g => g.Key, g => g.Value.Select(d => new PackageHierarchyItem(d.GetIdentity())).ToArray())) + { + } + + public PackageIdentity Identity { get; set; } + + public Dictionary Dependencies { get; set; } + + public override string ToString() => Identity.ToString(); + } +} diff --git a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs new file mode 100644 index 0000000..234b53b --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NeoGet.Tools.Hierarchy.Entities; +using NuGet.Frameworks; + +namespace NeoGet.Tools.Hierarchy.Extensions +{ + public static class PackageHierarchyExtensions + { + public static IEnumerable GetSummary(this PackageHierarchy hierarchy) + { + yield return hierarchy.Source; + + foreach(var line in GetSummary(hierarchy.Packages, p => p.GetSummary())) + { + yield return line; + } + } + + /// + /// Gets a summary of the hierarchy for the specific item, optionally limited to a specific framework. + /// + /// The item for which to generate a summary. + /// The framework to limit the hierarchy to. + private static IEnumerable GetSummary( + this PackageHierarchyItem item, + NuGetFramework framework = null + ) + { + var dependencies = item.GetMatchingDependencies(framework); + + yield return (dependencies.Any() ? "─┬" : "──") + item.Identity; + + if(dependencies.Any()) + { + //We only want to display the target framework if; + //- we're at the root (framework should not a value) + //- the dependencies found target the same framework as the current one (it could be a matching framework, like NetStandard 1.0 can be used by a NetStandard 2.0 package) + var displayFramework = framework == null || dependencies.First().Key != framework; + + foreach(var line in dependencies.GetSummary(d => GetSummary(d.Key, d.Value, displayFramework), displayFramework)) + { + yield return line; + } + } + } + + /// + /// Gets a summary of the packages present for the given framework. + /// + /// The target framework. + /// The packages + /// Indicates whether to display the target framework name. + /// + private static IEnumerable GetSummary(NuGetFramework framework, PackageHierarchyItem[] packages, bool displayFramework = false) + { + if(displayFramework) + { + yield return "[" + framework.DotNetFrameworkName + "]"; + } + + foreach(var line in packages.GetSummary(p => p.GetSummary(framework))) + { + yield return line; + } + } + + /// + /// A generic version for the summary construction. + /// Simplifies the inclusion of the "tree" characters. + /// + /// The items to display. + /// The method used to retrieve a item's summary. + /// Indicates whether to include the prefix in each item summary. + /// A tree-like summary for the given items. + private static IEnumerable GetSummary( + this ICollection items, + Func> itemSummaryFactory, + bool includePrefix = true + ) + { + for(var i = 1; i <= items.Count; i++) + { + var item = items.ElementAt(i - 1); + var isLast = i == items.Count; + + var isFirst = true; + + foreach(var line in itemSummaryFactory(item)) + { + var prefix = ""; + + if(includePrefix) + { + prefix = isFirst + ? (isLast ? " └" : " ├") + : (isLast ? " " : " │"); + } + + isFirst = false; + + yield return prefix + line; + } + } + } + } +} diff --git a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs new file mode 100644 index 0000000..84db170 --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NeoGet.Tools.Hierarchy.Entities; +using NuGet.Frameworks; +using NuGet.Packaging.Core; + +namespace NeoGet.Tools.Hierarchy.Extensions +{ + public static class PackageHierarchyItemExtensions + { + public static IEnumerable GetAllIdentities(this PackageHierarchy hierarchy) + => hierarchy?.Packages?.SelectMany(i => i.GetAllIdentities()).Distinct() ?? Array.Empty(); + + private static IEnumerable GetAllIdentities(this PackageHierarchyItem hierarchyItem) + => new[] { hierarchyItem.Identity } + .Concat(hierarchyItem.Dependencies?.SelectMany(p => p.Value.SelectMany(i => i.GetAllIdentities())) ?? Array.Empty()); + + public static IEnumerable GetDependenciesIdentities(this PackageHierarchyItem hierarchyItem) + => hierarchyItem.Dependencies?.SelectMany(d => d.Value).Select(i => i.Identity).Distinct() ?? Array.Empty(); + + public static Dictionary GetMatchingDependencies(this PackageHierarchyItem item, NuGetFramework framework) + { + var dependencies = new Dictionary(); + + if(item.Dependencies == null) + { + return dependencies; + } + else if(framework == null) + { + dependencies = item.Dependencies; + } + else + { + var nearestFramework = new FrameworkReducer().GetNearest(framework, item.Dependencies.Keys); + + dependencies = item.Dependencies + .Where(d => d.Key == nearestFramework) + .Take(1) + .ToDictionary(p => p.Key, p => p.Value); + } + + return dependencies + .Where(p => p.Value?.Any() ?? false) + .ToDictionary(p => p.Key, p => p.Value); + } + } +} diff --git a/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs b/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs new file mode 100644 index 0000000..99fb110 --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Helpers; +using NeoGet.Tools.Hierarchy.Entities; +using NeoGet.Tools.Hierarchy.Extensions; +using NuGet.Common; +using NuGet.Packaging; +using NuGet.Packaging.Core; + +namespace NeoGet.Tools.Hierarchy +{ + public class NuGetHierarchy + { + private readonly ILogger _log; + private readonly string _target; + private readonly IEnumerable _sources; + + public NuGetHierarchy(string target, IEnumerable sources, ILogger log) + { + _target = target; + _sources = sources; + _log = log; + } + + public async Task RunAsync(CancellationToken ct) + { + var references = await SolutionHelper.GetPackageReferences(ct, _target, FileType.All, _log); + var identities = new HashSet(references.Select(r => r.Identity)); + + var packages = await GetPackagesWithDependencies(ct, identities); + + return new PackageHierarchy(_target, packages); + } + + private async Task> GetPackagesWithDependencies( + CancellationToken ct, + IEnumerable packages, + IEnumerable knownPackages = null + ) + { + var resolvedPackages = await Task.WhenAll(packages.Select(p => GetHierarchy(ct, p))); + + knownPackages = resolvedPackages.Concat(knownPackages ?? Array.Empty()); + + var packagesToRetrieve = resolvedPackages + .SelectMany(p => p.GetDependenciesIdentities()) //Get resolved dependencies + .Except(knownPackages.Select(i => i.Identity)) //Remove the packages we already have dependencies for or not + .ToArray(); + + if(packagesToRetrieve.Any()) + { + //Get the packages still needed + var subDependencies = await GetPackagesWithDependencies(ct, packagesToRetrieve, knownPackages); + + foreach(var item in resolvedPackages.Where(i => i.Dependencies != null).SelectMany(i => i.Dependencies.Values.SelectMany(x => x))) + { + item.Dependencies = subDependencies.FirstOrDefault(i => i.Identity == item.Identity)?.Dependencies; + } + } + + return resolvedPackages; + } + + private async Task GetHierarchy(CancellationToken ct, PackageIdentity package) + { + PackageHierarchyItem hierarchy = null; + + foreach(var source in _sources) + { + try + { + var dependencies = await source.GetDependencies(ct, package); + + hierarchy = new PackageHierarchyItem(package, dependencies); + + _log.LogInformation($"Found {hierarchy.Dependencies.Count} dependencies for {package}"); + } + catch(PackageNotFoundException ex) + { + _log.LogInformation(ex.Message); + } + } + + if(hierarchy == null) + { + hierarchy = new PackageHierarchyItem(package); + } + + return hierarchy; + } + } +} diff --git a/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs b/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs index 0d4a6f4..61a2230 100644 --- a/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs @@ -90,10 +90,10 @@ public static async Task GetLatestVersion( PackageReference reference ) { - if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion)) + if(parameters.VersionOverrides.TryGetValue(reference.Identity.Id, out var manualVersion) && manualVersion.forceVersion) { PackageFeed.Logger.LogInformation($"Overriding version for {reference.Identity.Id}"); - return new FeedVersion(manualVersion); + return new FeedVersion(manualVersion.range.MinVersion); } var availableVersions = await Task.WhenAll(parameters @@ -103,6 +103,7 @@ PackageReference reference var versionsPerTarget = availableVersions .SelectMany(x => x) + .Where(v => manualVersion.range?.Satisfies(v.Version) ?? true) .OrderByDescending(v => v) .GroupBy(version => parameters.TargetVersions.FirstOrDefault(t => version.IsMatchingVersion(t, parameters.Strict))) .Where(g => g.Key.HasValue()); diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NuGet.Updater.Tool/Program.cs index df48a5e..18ead8a 100644 --- a/src/NuGet.Updater.Tool/Program.cs +++ b/src/NuGet.Updater.Tool/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -35,12 +36,18 @@ public static async Task Main(string[] args) } else { + var stopwatch = Stopwatch.StartNew(); + context.Parameters.SolutionRoot ??= Environment.CurrentDirectory; var updater = new NuGetUpdater(context.Parameters, context.IsSilent ? null : Console.Out, GetSummaryWriter(context.SummaryFile)); var result = await updater.UpdatePackages(CancellationToken.None); Save(result, context.ResultFile); + + stopwatch.Stop(); + + Console.WriteLine($"Operation completed in {stopwatch.Elapsed}"); } } catch(Exception ex) From 462a88198b2b6b4d83b05a8d2fa08f0f795b3dea Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 12 Mar 2020 16:21:08 -0400 Subject: [PATCH 165/201] Added tool for Nuget.hierarchy --- src/NeoGet/Entities/PackageReferenceHolder.cs | 23 ++++++ src/NeoGet/Extensions/DictionaryExtensions.cs | 18 +++++ .../Extensions/PackageReferenceExtensions.cs | 21 +++++ src/NeoGet/Extensions/StringExtensions.cs | 2 +- .../Hierarchy/Entities/PackageHierarchy.cs | 17 ---- .../Entities/PackageHierarchyItem.cs | 7 +- .../Entities/ProjectPackageHierarchy.cs | 19 +++++ .../Entities/ReversePackageReference.cs | 12 +++ .../Entities/SolutionPackageHierarchy.cs | 19 +++++ .../Extensions/PackageHierarchyExtensions.cs | 64 ++++++++++++++- .../PackageHierarchyItemExtensions.cs | 9 ++- src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs | 22 ++++- .../NuGet.Hierarchy.Tool.csproj | 13 +++ src/NuGet.Hierarchy.Tool/Program.cs | 80 +++++++++++++++++++ src/NuGet.Updater.sln | 7 ++ 15 files changed, 305 insertions(+), 28 deletions(-) create mode 100644 src/NeoGet/Entities/PackageReferenceHolder.cs create mode 100644 src/NeoGet/Extensions/PackageReferenceExtensions.cs delete mode 100644 src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Entities/ReversePackageReference.cs create mode 100644 src/NeoGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs create mode 100644 src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj create mode 100644 src/NuGet.Hierarchy.Tool/Program.cs diff --git a/src/NeoGet/Entities/PackageReferenceHolder.cs b/src/NeoGet/Entities/PackageReferenceHolder.cs new file mode 100644 index 0000000..28d50a4 --- /dev/null +++ b/src/NeoGet/Entities/PackageReferenceHolder.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.IO; +using NuGet.Packaging.Core; + +namespace NeoGet.Entities +{ + public class PackageReferenceHolder + { + public PackageReferenceHolder(string path, IEnumerable packages) + { + Path = path; + Packages = new List(packages); + + Name = System.IO.Path.GetFileName(path); + } + + public string Path { get; set; } + + public string Name { get; } + + public List Packages { get; set; } + } +} diff --git a/src/NeoGet/Extensions/DictionaryExtensions.cs b/src/NeoGet/Extensions/DictionaryExtensions.cs index 784cdf4..d5f8330 100644 --- a/src/NeoGet/Extensions/DictionaryExtensions.cs +++ b/src/NeoGet/Extensions/DictionaryExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Uno; +using Uno.Extensions.ValueType; namespace NeoGet.Extensions { @@ -13,6 +15,22 @@ params TKey[] keys .Where(g => keys.Contains(g.Key)) .ToDictionary(g => g.Key, g => g.Value); + public static void AddOrUpdate( + this IDictionary dictionary, + TKey key, + Func update + ) + { + if(dictionary.TryGetValue(key, out var existing)) + { + dictionary[key] = update(existing); + } + else + { + dictionary.Add(key, update(default)); + } + } + #if !WINDOWS_UWP public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value) { diff --git a/src/NeoGet/Extensions/PackageReferenceExtensions.cs b/src/NeoGet/Extensions/PackageReferenceExtensions.cs new file mode 100644 index 0000000..e1cfd54 --- /dev/null +++ b/src/NeoGet/Extensions/PackageReferenceExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NeoGet.Entities; + +namespace NeoGet.Extensions +{ + public static class PackageReferenceExtensions + { + public static IEnumerable GetReferenceHolders(this IEnumerable references) + => references + .SelectMany(r => r + .Files + .SelectMany(p => p.Value) + .Select(f => new { r.Identity, File = f }) + ) + .GroupBy(a => a.File) + .Select(g => new PackageReferenceHolder(g.Key, g.Select(a => a.Identity))); + } +} diff --git a/src/NeoGet/Extensions/StringExtensions.cs b/src/NeoGet/Extensions/StringExtensions.cs index 985a770..74ef735 100644 --- a/src/NeoGet/Extensions/StringExtensions.cs +++ b/src/NeoGet/Extensions/StringExtensions.cs @@ -7,7 +7,7 @@ namespace NeoGet.Extensions { - internal static class StringExtensions + public static class StringExtensions { private const string LegacyAzureArtifactsFeedUrlPattern = @"https:\/\/(?'account'[^.]*).*_packaging\/(?'feed'[^\/]*)"; private const string AzureArtifactsFeedUrlPattern = @"https:\/\/pkgs\.dev.azure.com\/(?'account'[^\/]*).*_packaging\/(?'feed'[^\/]*)"; diff --git a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs deleted file mode 100644 index fad884a..0000000 --- a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchy.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; - -namespace NeoGet.Tools.Hierarchy.Entities -{ - public class PackageHierarchy - { - public PackageHierarchy(string source, IEnumerable packages) - { - Source = source; - Packages = new List(packages); - } - - public string Source { get; set; } - - public List Packages { get; set; } - } -} diff --git a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs index 7380e8b..7dc80f3 100644 --- a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs +++ b/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using NeoGet.Extensions; using NuGet.Frameworks; @@ -6,7 +7,7 @@ namespace NeoGet.Tools.Hierarchy.Entities { - public class PackageHierarchyItem + public class PackageHierarchyItem : IEquatable { public PackageHierarchyItem(PackageIdentity identity) { @@ -28,6 +29,8 @@ public PackageHierarchyItem(PackageIdentity identity, IDictionary Dependencies { get; set; } + public bool Equals(PackageHierarchyItem other) => other == null ? false : other.Identity == Identity; + public override string ToString() => Identity.ToString(); } } diff --git a/src/NeoGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs b/src/NeoGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs new file mode 100644 index 0000000..804b1b5 --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace NeoGet.Tools.Hierarchy.Entities +{ + public class ProjectPackageHierarchy + { + public ProjectPackageHierarchy(string name, IEnumerable packages) + { + Name = name; + Packages = new List(packages); + } + + public string Name { get; set; } + + public List Packages { get; set; } + + public override string ToString() => Name; + } +} diff --git a/src/NeoGet/Tools/Hierarchy/Entities/ReversePackageReference.cs b/src/NeoGet/Tools/Hierarchy/Entities/ReversePackageReference.cs new file mode 100644 index 0000000..beb7b8d --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Entities/ReversePackageReference.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using NuGet.Packaging.Core; + +namespace NeoGet.Tools.Hierarchy.Entities +{ + public class ReversePackageReference + { + public PackageIdentity Identity { get; set; } + + public List ReferencedBy { get; set; } + } +} diff --git a/src/NeoGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs b/src/NeoGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs new file mode 100644 index 0000000..495dbc6 --- /dev/null +++ b/src/NeoGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace NeoGet.Tools.Hierarchy.Entities +{ + public class SolutionPackageHierarchy + { + public SolutionPackageHierarchy(string name) + { + Name = name; + Projects = new List(); + } + + public string Name { get; set; } + + public List Projects { get; } + + public override string ToString() => Name; + } +} diff --git a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs index 234b53b..9e8d948 100644 --- a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs +++ b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs @@ -1,16 +1,76 @@ using System; using System.Collections.Generic; using System.Linq; +using NeoGet.Extensions; using NeoGet.Tools.Hierarchy.Entities; using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; namespace NeoGet.Tools.Hierarchy.Extensions { public static class PackageHierarchyExtensions { - public static IEnumerable GetSummary(this PackageHierarchy hierarchy) + public static IDictionary GetReversePackageReferences(this SolutionPackageHierarchy solution) { - yield return hierarchy.Source; + var references = new Dictionary(); + + foreach(var project in solution.Projects) + { + references.Add(project.Name, project.GetReversePackageReferences().ToArray()); + } + + return references; + } + + private static IEnumerable GetReversePackageReferences(this ProjectPackageHierarchy project) + { + var references = new List>(); + var rootPackages = project.Packages.Select(i => i.Identity); + + foreach(var item in project.Packages) + { + references.AddRange(item.GetParentReferences(rootPackages)); + } + + return references + .GroupBy(p => p.Key, p => p.Value) + .Select(p => new ReversePackageReference { Identity = p.Key, ReferencedBy = p.SelectMany(x => x).Distinct().ToList() }); + } + + private static IEnumerable> GetParentReferences(this PackageHierarchyItem item, IEnumerable packages) + { + var references = new List>(); + + if(item.Dependencies != null) + { + foreach(var d in item.Dependencies.SelectMany(p => p.Value).Distinct()) + { + if(packages.Contains(d.Identity)) + { + references.Add(new KeyValuePair(d.Identity, item.Identity)); + } + } + } + + return references + .GroupBy(p => p.Key, p => p.Value) + .Select(g => new KeyValuePair(g.Key, g.ToArray())); + } + + public static IEnumerable GetSummary(this SolutionPackageHierarchy hierarchy) + { + yield return hierarchy.Name; + + foreach(var line in GetSummary(hierarchy.Projects, p => p.GetSummary())) + { + yield return line; + } + } + + private static IEnumerable GetSummary(this ProjectPackageHierarchy hierarchy) + { + yield return $"({hierarchy.Name})"; foreach(var line in GetSummary(hierarchy.Packages, p => p.GetSummary())) { diff --git a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs index 84db170..0ef5ba0 100644 --- a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs +++ b/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs @@ -9,17 +9,20 @@ namespace NeoGet.Tools.Hierarchy.Extensions { public static class PackageHierarchyItemExtensions { - public static IEnumerable GetAllIdentities(this PackageHierarchy hierarchy) + public static IEnumerable GetAllIdentities(this SolutionPackageHierarchy hierarchy) + => hierarchy?.Projects?.SelectMany(i => i.GetAllIdentities()).Distinct() ?? Array.Empty(); + + public static IEnumerable GetAllIdentities(this ProjectPackageHierarchy hierarchy) => hierarchy?.Packages?.SelectMany(i => i.GetAllIdentities()).Distinct() ?? Array.Empty(); private static IEnumerable GetAllIdentities(this PackageHierarchyItem hierarchyItem) => new[] { hierarchyItem.Identity } - .Concat(hierarchyItem.Dependencies?.SelectMany(p => p.Value.SelectMany(i => i.GetAllIdentities())) ?? Array.Empty()); + .Concat(hierarchyItem.GetDependenciesIdentities()); public static IEnumerable GetDependenciesIdentities(this PackageHierarchyItem hierarchyItem) => hierarchyItem.Dependencies?.SelectMany(d => d.Value).Select(i => i.Identity).Distinct() ?? Array.Empty(); - public static Dictionary GetMatchingDependencies(this PackageHierarchyItem item, NuGetFramework framework) + public static Dictionary GetMatchingDependencies(this PackageHierarchyItem item, NuGetFramework framework = null) { var dependencies = new Dictionary(); diff --git a/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs b/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs index 99fb110..d541cd9 100644 --- a/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs +++ b/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs @@ -5,12 +5,14 @@ using System.Threading.Tasks; using NeoGet.Contracts; using NeoGet.Entities; +using NeoGet.Extensions; using NeoGet.Helpers; using NeoGet.Tools.Hierarchy.Entities; using NeoGet.Tools.Hierarchy.Extensions; using NuGet.Common; using NuGet.Packaging; using NuGet.Packaging.Core; +using PackageReference = NeoGet.Entities.PackageReference; namespace NeoGet.Tools.Hierarchy { @@ -27,14 +29,28 @@ public NuGetHierarchy(string target, IEnumerable sources, ILogger _log = log; } - public async Task RunAsync(CancellationToken ct) + public async Task RunAsync(CancellationToken ct) { var references = await SolutionHelper.GetPackageReferences(ct, _target, FileType.All, _log); var identities = new HashSet(references.Select(r => r.Identity)); - var packages = await GetPackagesWithDependencies(ct, identities); + var hierarchy = await GetPackagesWithDependencies(ct, identities); - return new PackageHierarchy(_target, packages); + return GetSolutionPackageHierarchy(references, hierarchy); + } + + private SolutionPackageHierarchy GetSolutionPackageHierarchy(IEnumerable references, IEnumerable hierarchy) + { + var solutionHierarchy = new SolutionPackageHierarchy(_target); + + var solutionItems = references.GetReferenceHolders(); + + foreach(var item in solutionItems) + { + solutionHierarchy.Projects.Add(new ProjectPackageHierarchy(item.Name, hierarchy.Where(i => item.Packages.Contains(i.Identity)))); + } + + return solutionHierarchy; } private async Task> GetPackagesWithDependencies( diff --git a/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj b/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj new file mode 100644 index 0000000..38a6235 --- /dev/null +++ b/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + diff --git a/src/NuGet.Hierarchy.Tool/Program.cs b/src/NuGet.Hierarchy.Tool/Program.cs new file mode 100644 index 0000000..c6d88ad --- /dev/null +++ b/src/NuGet.Hierarchy.Tool/Program.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Mono.Options; +using NeoGet.Contracts; +using NeoGet.Entities; +using NeoGet.Extensions; +using NeoGet.Tools.Hierarchy; +using NeoGet.Tools.Hierarchy.Extensions; + +namespace NuGet.Hierarchy.Tool +{ + public static class Program + { + public static async Task Main(string[] args) + { + try + { + var isHelp = false; + var target = default(string); + var outputFile = default(string); + var sources = new List(); + + var options = new OptionSet + { + { "help|h", "Displays this help screen", s => isHelp = true }, + { "solution|s=", "Path to the {solution}", s => target = s }, + { "outputfile|o=", "Path to a {file} where to write the tree", s => outputFile = s }, + { "sourceFeed=|sf=", "The NuGet feed from where to download the packages; a private feed can be specified with the format {url|accessToken}", s => sources.Add(PackageFeed.FromString(s)) }, + }; + + options.Parse(args); + + if(isHelp) + { + Console.WriteLine("NuGet Hierarchy is a tool allowing the view the hierarchy of the NuGet packages found in a solution"); + Console.WriteLine(); + options.WriteOptionDescriptions(Console.Out); + } + + var tool = new NuGetHierarchy(target, sources, ConsoleLogger.Instance); + + var result = await tool.RunAsync(CancellationToken.None); + + var summary = result.GetSummary(); + + File.WriteAllLines(outputFile, summary); + + var reverseReferences = result.GetReversePackageReferences(); + + var lines = new List(); + + Console.WriteLine(); + Console.WriteLine("Unecessary package references"); + + foreach(var r in reverseReferences) + { + lines.Add(r.Key); + Console.WriteLine(r.Key); + foreach(var p in r.Value) + { + var l = $"\t- [{p.Identity}] is referenced by {p.ReferencedBy.Select(i => "[" + i + "]").GetEnumeration()}"; + lines.Add(l); + Console.WriteLine(l); + } + } + + File.WriteAllLines(Path.Combine(Path.GetDirectoryName(outputFile), "unecessary.txt"), lines); + } + catch(Exception ex) + { + Console.Error.WriteLine($"Failed to download nuget packages: {ex.Message}"); + Console.Error.WriteLine($"{ex}"); + } + } + } +} diff --git a/src/NuGet.Updater.sln b/src/NuGet.Updater.sln index ccdf654..083a5b4 100644 --- a/src/NuGet.Updater.sln +++ b/src/NuGet.Updater.sln @@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{7B2F2CBF EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NeoGet.Tools.Shared", "NeoGet.Tools.Shared\NeoGet.Tools.Shared.shproj", "{4C395D24-0028-47EF-B07E-5566344E78DE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Hierarchy.Tool", "NuGet.Hierarchy.Tool\NuGet.Hierarchy.Tool.csproj", "{464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{3daa81db-00a6-4a84-8180-19a588398f52}*SharedItemsImports = 5 @@ -58,6 +60,10 @@ Global {5FBA174D-C841-4968-A8E0-3BA97B839123}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FBA174D-C841-4968-A8E0-3BA97B839123}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FBA174D-C841-4968-A8E0-3BA97B839123}.Release|Any CPU.Build.0 = Release|Any CPU + {464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,6 +71,7 @@ Global GlobalSection(NestedProjects) = preSolution {3DAA81DB-00A6-4A84-8180-19A588398F52} = {7B2F2CBF-9EC9-4C18-A471-D36862EE23D9} {17979C76-865A-4983-977B-E242BCE1B038} = {7B2F2CBF-9EC9-4C18-A471-D36862EE23D9} + {464BC350-3EE3-4D03-B34F-BDC53D6E0FAE} = {7B2F2CBF-9EC9-4C18-A471-D36862EE23D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1C528FB3-4BD1-4D56-ABEE-A20F6367040F} From dd70cb9059b2815816f11ba140738a38238f1c13 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 10:48:19 -0500 Subject: [PATCH 166/201] Updated target to netcore 3.1 --- src/NeoGet.Tests/NeoGet.Tests.csproj | 2 +- .../NuGet.Downloader.Tool.csproj | 2 +- src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj | 2 +- src/NuGet.Hierarchy.Tool/Program.cs | 11 ++++++----- src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/NeoGet.Tests/NeoGet.Tests.csproj b/src/NeoGet.Tests/NeoGet.Tests.csproj index db2b256..8bcec2f 100644 --- a/src/NeoGet.Tests/NeoGet.Tests.csproj +++ b/src/NeoGet.Tests/NeoGet.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + netcoreapp3.1 false diff --git a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj index 8dfe426..8891b67 100644 --- a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj +++ b/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.2 + netcoreapp3.1 diff --git a/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj b/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj index 38a6235..c0db808 100644 --- a/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj +++ b/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.2 + netcoreapp3.1 diff --git a/src/NuGet.Hierarchy.Tool/Program.cs b/src/NuGet.Hierarchy.Tool/Program.cs index c6d88ad..4c7d622 100644 --- a/src/NuGet.Hierarchy.Tool/Program.cs +++ b/src/NuGet.Hierarchy.Tool/Program.cs @@ -45,10 +45,6 @@ public static async Task Main(string[] args) var result = await tool.RunAsync(CancellationToken.None); - var summary = result.GetSummary(); - - File.WriteAllLines(outputFile, summary); - var reverseReferences = result.GetReversePackageReferences(); var lines = new List(); @@ -68,7 +64,12 @@ public static async Task Main(string[] args) } } - File.WriteAllLines(Path.Combine(Path.GetDirectoryName(outputFile), "unecessary.txt"), lines); + if(outputFile != null) + { + var summary = result.GetSummary(); + File.WriteAllLines(outputFile, summary); + File.WriteAllLines(Path.Combine(Path.GetDirectoryName(outputFile), "unecessary.txt"), lines); + } } catch(Exception ex) { diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj index 86ea6f1..e3db53b 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.2 + netcoreapp3.1 true true nugetupdater From 2f3b616c63dcc9546adf7e4a799116c80be63a8a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 11:18:57 -0500 Subject: [PATCH 167/201] Renamed files to NvGet --- .../ConsoleArgsParserTests.cs | 0 .../Constants.cs | 0 .../Entities/TestPackageFeed.cs | 0 .../NvGet.Tests.csproj} | 4 ++-- .../Resources/version_overrides.json | 0 .../Tools/Updater/NuGetUpdaterTests.cs | 0 .../Tools/Updater/PackageReferenceTests.cs | 0 .../Tools/Updater/UpdaterParametersTests.cs | 0 .../NvGet.Tools.Downloader.csproj} | 2 +- .../Program.cs | 0 .../README.md | 0 .../NvGet.Tools.Hierarchy.csproj} | 2 +- .../Program.cs | 0 .../NvGet.Tools.Shared.projitems} | 0 .../NvGet.Tools.Shared.shproj} | 2 +- .../Updater/Arguments/ConsoleArgError.cs | 0 .../Updater/Arguments/ConsoleArgErrorType.cs | 0 .../ConsoleArgsContext.Properties.cs | 0 .../Updater/Arguments/ConsoleArgsContext.cs | 0 .../NvGet.Tools.Updater.csproj} | 4 ++-- .../Program.cs | 0 .../Readme.md | 0 src/{NuGet.Updater.sln => NvGet.sln} | 20 +++++++++---------- src/{NeoGet => NvGet}/Contracts/FileType.cs | 0 .../Contracts/IPackageFeed.cs | 0 src/{NeoGet => NvGet}/Entities/FeedVersion.cs | 0 .../Entities/LocalPackage.cs | 0 src/{NeoGet => NvGet}/Entities/PackageFeed.cs | 0 .../Entities/PackageNotFoundException.cs | 0 .../Entities/PackageReference.cs | 0 .../Entities/PackageReferenceHolder.cs | 0 .../Extensions/DictionaryExtensions.cs | 0 .../Extensions/FeedVersionExtensions.cs | 0 .../Extensions/FileTypeExtensions.cs | 0 .../Extensions/NuGetResourceExtensions.cs | 0 .../Extensions/NuGetVersionExtensions.cs | 0 .../Extensions/PackageDependencyExtensions.cs | 0 .../Extensions/PackageReferenceExtensions.cs | 0 .../PackageSearchMetadataExtensions.cs | 0 .../Extensions/PackageSourceExtensions.cs | 0 .../Extensions/StringExtensions.cs | 0 .../Extensions/XmlDocumentExtensions.cs | 0 .../Helpers/FileHelper.Net.cs | 0 .../Helpers/FileHelper.UAP.cs | 0 .../Helpers/MarkdownHelper.cs | 0 .../Helpers/SolutionHelper.cs | 0 src/{NeoGet => NvGet}/Log/ConsoleLogger.cs | 0 src/{NeoGet => NvGet}/Log/SimpleTextWriter.cs | 0 .../NeoGet.csproj => NvGet/NvGet.csproj} | 0 .../Entities/DownloaderParameters.cs | 0 .../Downloader/Entities/DownloaderResult.cs | 0 .../Tools/Downloader/NuGetDownloader.cs | 0 .../Entities/PackageHierarchyItem.cs | 0 .../Entities/ProjectPackageHierarchy.cs | 0 .../Entities/ReversePackageReference.cs | 0 .../Entities/SolutionPackageHierarchy.cs | 0 .../Extensions/PackageHierarchyExtensions.cs | 0 .../PackageHierarchyItemExtensions.cs | 0 .../Tools/Hierarchy/NuGetHierarchy.cs | 0 .../Updater/Entities/LogDisplayOptions.cs | 0 .../Tools/Updater/Entities/UpdateResult.cs | 0 .../Tools/Updater/Entities/UpdaterPackage.cs | 0 .../Updater/Entities/UpdaterParameters.cs | 0 .../Extensions/UpdateOperationExtensions.cs | 0 .../Extensions/UpdaterParametersExtension.cs | 0 .../Extensions/XmlDocumentExtensions.cs | 0 .../Tools/Updater/Log/UpdateOperation.cs | 0 .../Tools/Updater/Log/UpdaterLogger.cs | 0 .../Tools/Updater/NuGetUpdater.cs | 0 69 files changed, 17 insertions(+), 17 deletions(-) rename src/{NeoGet.Tests => NvGet.Tests}/ConsoleArgsParserTests.cs (100%) rename src/{NeoGet.Tests => NvGet.Tests}/Constants.cs (100%) rename src/{NeoGet.Tests => NvGet.Tests}/Entities/TestPackageFeed.cs (100%) rename src/{NeoGet.Tests/NeoGet.Tests.csproj => NvGet.Tests/NvGet.Tests.csproj} (81%) rename src/{NeoGet.Tests => NvGet.Tests}/Resources/version_overrides.json (100%) rename src/{NeoGet.Tests => NvGet.Tests}/Tools/Updater/NuGetUpdaterTests.cs (100%) rename src/{NeoGet.Tests => NvGet.Tests}/Tools/Updater/PackageReferenceTests.cs (100%) rename src/{NeoGet.Tests => NvGet.Tests}/Tools/Updater/UpdaterParametersTests.cs (100%) rename src/{NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj => NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj} (93%) rename src/{NuGet.Downloader.Tool => NvGet.Tools.Downloader}/Program.cs (100%) rename src/{NuGet.Downloader.Tool => NvGet.Tools.Downloader}/README.md (100%) rename src/{NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj => NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj} (83%) rename src/{NuGet.Hierarchy.Tool => NvGet.Tools.Hierarchy}/Program.cs (100%) rename src/{NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems => NvGet.Tools.Shared/NvGet.Tools.Shared.projitems} (100%) rename src/{NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj => NvGet.Tools.Shared/NvGet.Tools.Shared.shproj} (93%) rename src/{NeoGet.Tools.Shared => NvGet.Tools.Shared}/Updater/Arguments/ConsoleArgError.cs (100%) rename src/{NeoGet.Tools.Shared => NvGet.Tools.Shared}/Updater/Arguments/ConsoleArgErrorType.cs (100%) rename src/{NeoGet.Tools.Shared => NvGet.Tools.Shared}/Updater/Arguments/ConsoleArgsContext.Properties.cs (100%) rename src/{NeoGet.Tools.Shared => NvGet.Tools.Shared}/Updater/Arguments/ConsoleArgsContext.cs (100%) rename src/{NuGet.Updater.Tool/NuGet.Updater.Tool.csproj => NvGet.Tools.Updater/NvGet.Tools.Updater.csproj} (92%) rename src/{NuGet.Updater.Tool => NvGet.Tools.Updater}/Program.cs (100%) rename src/{NuGet.Updater.Tool => NvGet.Tools.Updater}/Readme.md (100%) rename src/{NuGet.Updater.sln => NvGet.sln} (73%) rename src/{NeoGet => NvGet}/Contracts/FileType.cs (100%) rename src/{NeoGet => NvGet}/Contracts/IPackageFeed.cs (100%) rename src/{NeoGet => NvGet}/Entities/FeedVersion.cs (100%) rename src/{NeoGet => NvGet}/Entities/LocalPackage.cs (100%) rename src/{NeoGet => NvGet}/Entities/PackageFeed.cs (100%) rename src/{NeoGet => NvGet}/Entities/PackageNotFoundException.cs (100%) rename src/{NeoGet => NvGet}/Entities/PackageReference.cs (100%) rename src/{NeoGet => NvGet}/Entities/PackageReferenceHolder.cs (100%) rename src/{NeoGet => NvGet}/Extensions/DictionaryExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/FeedVersionExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/FileTypeExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/NuGetResourceExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/NuGetVersionExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/PackageDependencyExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/PackageReferenceExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/PackageSearchMetadataExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/PackageSourceExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/StringExtensions.cs (100%) rename src/{NeoGet => NvGet}/Extensions/XmlDocumentExtensions.cs (100%) rename src/{NeoGet => NvGet}/Helpers/FileHelper.Net.cs (100%) rename src/{NeoGet => NvGet}/Helpers/FileHelper.UAP.cs (100%) rename src/{NeoGet => NvGet}/Helpers/MarkdownHelper.cs (100%) rename src/{NeoGet => NvGet}/Helpers/SolutionHelper.cs (100%) rename src/{NeoGet => NvGet}/Log/ConsoleLogger.cs (100%) rename src/{NeoGet => NvGet}/Log/SimpleTextWriter.cs (100%) rename src/{NeoGet/NeoGet.csproj => NvGet/NvGet.csproj} (100%) rename src/{NeoGet => NvGet}/Tools/Downloader/Entities/DownloaderParameters.cs (100%) rename src/{NeoGet => NvGet}/Tools/Downloader/Entities/DownloaderResult.cs (100%) rename src/{NeoGet => NvGet}/Tools/Downloader/NuGetDownloader.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/Entities/PackageHierarchyItem.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/Entities/ReversePackageReference.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs (100%) rename src/{NeoGet => NvGet}/Tools/Hierarchy/NuGetHierarchy.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Entities/LogDisplayOptions.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Entities/UpdateResult.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Entities/UpdaterPackage.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Entities/UpdaterParameters.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Extensions/UpdateOperationExtensions.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Extensions/UpdaterParametersExtension.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Extensions/XmlDocumentExtensions.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Log/UpdateOperation.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/Log/UpdaterLogger.cs (100%) rename src/{NeoGet => NvGet}/Tools/Updater/NuGetUpdater.cs (100%) diff --git a/src/NeoGet.Tests/ConsoleArgsParserTests.cs b/src/NvGet.Tests/ConsoleArgsParserTests.cs similarity index 100% rename from src/NeoGet.Tests/ConsoleArgsParserTests.cs rename to src/NvGet.Tests/ConsoleArgsParserTests.cs diff --git a/src/NeoGet.Tests/Constants.cs b/src/NvGet.Tests/Constants.cs similarity index 100% rename from src/NeoGet.Tests/Constants.cs rename to src/NvGet.Tests/Constants.cs diff --git a/src/NeoGet.Tests/Entities/TestPackageFeed.cs b/src/NvGet.Tests/Entities/TestPackageFeed.cs similarity index 100% rename from src/NeoGet.Tests/Entities/TestPackageFeed.cs rename to src/NvGet.Tests/Entities/TestPackageFeed.cs diff --git a/src/NeoGet.Tests/NeoGet.Tests.csproj b/src/NvGet.Tests/NvGet.Tests.csproj similarity index 81% rename from src/NeoGet.Tests/NeoGet.Tests.csproj rename to src/NvGet.Tests/NvGet.Tests.csproj index 8bcec2f..ca16cf2 100644 --- a/src/NeoGet.Tests/NeoGet.Tests.csproj +++ b/src/NvGet.Tests/NvGet.Tests.csproj @@ -13,7 +13,7 @@ - + @@ -22,6 +22,6 @@ - + diff --git a/src/NeoGet.Tests/Resources/version_overrides.json b/src/NvGet.Tests/Resources/version_overrides.json similarity index 100% rename from src/NeoGet.Tests/Resources/version_overrides.json rename to src/NvGet.Tests/Resources/version_overrides.json diff --git a/src/NeoGet.Tests/Tools/Updater/NuGetUpdaterTests.cs b/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs similarity index 100% rename from src/NeoGet.Tests/Tools/Updater/NuGetUpdaterTests.cs rename to src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs diff --git a/src/NeoGet.Tests/Tools/Updater/PackageReferenceTests.cs b/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs similarity index 100% rename from src/NeoGet.Tests/Tools/Updater/PackageReferenceTests.cs rename to src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs diff --git a/src/NeoGet.Tests/Tools/Updater/UpdaterParametersTests.cs b/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs similarity index 100% rename from src/NeoGet.Tests/Tools/Updater/UpdaterParametersTests.cs rename to src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs diff --git a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj similarity index 93% rename from src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj rename to src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj index 8891b67..921eec4 100644 --- a/src/NuGet.Downloader.Tool/NuGet.Downloader.Tool.csproj +++ b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/NuGet.Downloader.Tool/Program.cs b/src/NvGet.Tools.Downloader/Program.cs similarity index 100% rename from src/NuGet.Downloader.Tool/Program.cs rename to src/NvGet.Tools.Downloader/Program.cs diff --git a/src/NuGet.Downloader.Tool/README.md b/src/NvGet.Tools.Downloader/README.md similarity index 100% rename from src/NuGet.Downloader.Tool/README.md rename to src/NvGet.Tools.Downloader/README.md diff --git a/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj similarity index 83% rename from src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj rename to src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj index c0db808..d86d008 100644 --- a/src/NuGet.Hierarchy.Tool/NuGet.Hierarchy.Tool.csproj +++ b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj @@ -8,6 +8,6 @@ - + diff --git a/src/NuGet.Hierarchy.Tool/Program.cs b/src/NvGet.Tools.Hierarchy/Program.cs similarity index 100% rename from src/NuGet.Hierarchy.Tool/Program.cs rename to src/NvGet.Tools.Hierarchy/Program.cs diff --git a/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems b/src/NvGet.Tools.Shared/NvGet.Tools.Shared.projitems similarity index 100% rename from src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.projitems rename to src/NvGet.Tools.Shared/NvGet.Tools.Shared.projitems diff --git a/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj b/src/NvGet.Tools.Shared/NvGet.Tools.Shared.shproj similarity index 93% rename from src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj rename to src/NvGet.Tools.Shared/NvGet.Tools.Shared.shproj index bf48bb6..607a48b 100644 --- a/src/NeoGet.Tools.Shared/NeoGet.Tools.Shared.shproj +++ b/src/NvGet.Tools.Shared/NvGet.Tools.Shared.shproj @@ -8,6 +8,6 @@ - + diff --git a/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs b/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs similarity index 100% rename from src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs rename to src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs diff --git a/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs b/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs similarity index 100% rename from src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs rename to src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs diff --git a/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs b/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs similarity index 100% rename from src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs rename to src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs diff --git a/src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs b/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs similarity index 100% rename from src/NeoGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs rename to src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs diff --git a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj similarity index 92% rename from src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj rename to src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index e3db53b..350fcfa 100644 --- a/src/NuGet.Updater.Tool/NuGet.Updater.Tool.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -41,7 +41,7 @@ - + @@ -50,5 +50,5 @@ - + diff --git a/src/NuGet.Updater.Tool/Program.cs b/src/NvGet.Tools.Updater/Program.cs similarity index 100% rename from src/NuGet.Updater.Tool/Program.cs rename to src/NvGet.Tools.Updater/Program.cs diff --git a/src/NuGet.Updater.Tool/Readme.md b/src/NvGet.Tools.Updater/Readme.md similarity index 100% rename from src/NuGet.Updater.Tool/Readme.md rename to src/NvGet.Tools.Updater/Readme.md diff --git a/src/NuGet.Updater.sln b/src/NvGet.sln similarity index 73% rename from src/NuGet.Updater.sln rename to src/NvGet.sln index 083a5b4..d4f540b 100644 --- a/src/NuGet.Updater.sln +++ b/src/NvGet.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29021.104 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Updater.Tool", "NuGet.Updater.Tool\NuGet.Updater.Tool.csproj", "{3DAA81DB-00A6-4A84-8180-19A588398F52}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0547178F-3639-4797-940F-265958B7FECA}" ProjectSection(SolutionItems) = preProject ..\.azure-pipelines.yml = ..\.azure-pipelines.yml @@ -21,23 +19,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoGet.Tests", "NeoGet.Tests\NeoGet.Tests.csproj", "{D240ABFD-6A48-4E4B-A946-9EBF9840FADA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvGet.Tools.Updater", "NvGet.Tools.Updater\NvGet.Tools.Updater.csproj", "{3DAA81DB-00A6-4A84-8180-19A588398F52}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvGet.Tests", "NvGet.Tests\NvGet.Tests.csproj", "{D240ABFD-6A48-4E4B-A946-9EBF9840FADA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Downloader.Tool", "NuGet.Downloader.Tool\NuGet.Downloader.Tool.csproj", "{17979C76-865A-4983-977B-E242BCE1B038}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvGet.Tools.Downloader", "NvGet.Tools.Downloader\NvGet.Tools.Downloader.csproj", "{17979C76-865A-4983-977B-E242BCE1B038}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeoGet", "NeoGet\NeoGet.csproj", "{5FBA174D-C841-4968-A8E0-3BA97B839123}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvGet", "NvGet\NvGet.csproj", "{5FBA174D-C841-4968-A8E0-3BA97B839123}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{7B2F2CBF-9EC9-4C18-A471-D36862EE23D9}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NeoGet.Tools.Shared", "NeoGet.Tools.Shared\NeoGet.Tools.Shared.shproj", "{4C395D24-0028-47EF-B07E-5566344E78DE}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "NvGet.Tools.Shared", "NvGet.Tools.Shared\NvGet.Tools.Shared.shproj", "{4C395D24-0028-47EF-B07E-5566344E78DE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGet.Hierarchy.Tool", "NuGet.Hierarchy.Tool\NuGet.Hierarchy.Tool.csproj", "{464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvGet.Tools.Hierarchy", "NvGet.Tools.Hierarchy\NvGet.Tools.Hierarchy.csproj", "{464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution - NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{3daa81db-00a6-4a84-8180-19a588398f52}*SharedItemsImports = 5 - NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{4c395d24-0028-47ef-b07e-5566344e78de}*SharedItemsImports = 13 - NeoGet.Tools.Shared\NeoGet.Tools.Shared.projitems*{d240abfd-6a48-4e4b-a946-9ebf9840fada}*SharedItemsImports = 5 + NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{3daa81db-00a6-4a84-8180-19a588398f52}*SharedItemsImports = 5 + NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{4c395d24-0028-47ef-b07e-5566344e78de}*SharedItemsImports = 13 + NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{d240abfd-6a48-4e4b-a946-9ebf9840fada}*SharedItemsImports = 5 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/NeoGet/Contracts/FileType.cs b/src/NvGet/Contracts/FileType.cs similarity index 100% rename from src/NeoGet/Contracts/FileType.cs rename to src/NvGet/Contracts/FileType.cs diff --git a/src/NeoGet/Contracts/IPackageFeed.cs b/src/NvGet/Contracts/IPackageFeed.cs similarity index 100% rename from src/NeoGet/Contracts/IPackageFeed.cs rename to src/NvGet/Contracts/IPackageFeed.cs diff --git a/src/NeoGet/Entities/FeedVersion.cs b/src/NvGet/Entities/FeedVersion.cs similarity index 100% rename from src/NeoGet/Entities/FeedVersion.cs rename to src/NvGet/Entities/FeedVersion.cs diff --git a/src/NeoGet/Entities/LocalPackage.cs b/src/NvGet/Entities/LocalPackage.cs similarity index 100% rename from src/NeoGet/Entities/LocalPackage.cs rename to src/NvGet/Entities/LocalPackage.cs diff --git a/src/NeoGet/Entities/PackageFeed.cs b/src/NvGet/Entities/PackageFeed.cs similarity index 100% rename from src/NeoGet/Entities/PackageFeed.cs rename to src/NvGet/Entities/PackageFeed.cs diff --git a/src/NeoGet/Entities/PackageNotFoundException.cs b/src/NvGet/Entities/PackageNotFoundException.cs similarity index 100% rename from src/NeoGet/Entities/PackageNotFoundException.cs rename to src/NvGet/Entities/PackageNotFoundException.cs diff --git a/src/NeoGet/Entities/PackageReference.cs b/src/NvGet/Entities/PackageReference.cs similarity index 100% rename from src/NeoGet/Entities/PackageReference.cs rename to src/NvGet/Entities/PackageReference.cs diff --git a/src/NeoGet/Entities/PackageReferenceHolder.cs b/src/NvGet/Entities/PackageReferenceHolder.cs similarity index 100% rename from src/NeoGet/Entities/PackageReferenceHolder.cs rename to src/NvGet/Entities/PackageReferenceHolder.cs diff --git a/src/NeoGet/Extensions/DictionaryExtensions.cs b/src/NvGet/Extensions/DictionaryExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/DictionaryExtensions.cs rename to src/NvGet/Extensions/DictionaryExtensions.cs diff --git a/src/NeoGet/Extensions/FeedVersionExtensions.cs b/src/NvGet/Extensions/FeedVersionExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/FeedVersionExtensions.cs rename to src/NvGet/Extensions/FeedVersionExtensions.cs diff --git a/src/NeoGet/Extensions/FileTypeExtensions.cs b/src/NvGet/Extensions/FileTypeExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/FileTypeExtensions.cs rename to src/NvGet/Extensions/FileTypeExtensions.cs diff --git a/src/NeoGet/Extensions/NuGetResourceExtensions.cs b/src/NvGet/Extensions/NuGetResourceExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/NuGetResourceExtensions.cs rename to src/NvGet/Extensions/NuGetResourceExtensions.cs diff --git a/src/NeoGet/Extensions/NuGetVersionExtensions.cs b/src/NvGet/Extensions/NuGetVersionExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/NuGetVersionExtensions.cs rename to src/NvGet/Extensions/NuGetVersionExtensions.cs diff --git a/src/NeoGet/Extensions/PackageDependencyExtensions.cs b/src/NvGet/Extensions/PackageDependencyExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/PackageDependencyExtensions.cs rename to src/NvGet/Extensions/PackageDependencyExtensions.cs diff --git a/src/NeoGet/Extensions/PackageReferenceExtensions.cs b/src/NvGet/Extensions/PackageReferenceExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/PackageReferenceExtensions.cs rename to src/NvGet/Extensions/PackageReferenceExtensions.cs diff --git a/src/NeoGet/Extensions/PackageSearchMetadataExtensions.cs b/src/NvGet/Extensions/PackageSearchMetadataExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/PackageSearchMetadataExtensions.cs rename to src/NvGet/Extensions/PackageSearchMetadataExtensions.cs diff --git a/src/NeoGet/Extensions/PackageSourceExtensions.cs b/src/NvGet/Extensions/PackageSourceExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/PackageSourceExtensions.cs rename to src/NvGet/Extensions/PackageSourceExtensions.cs diff --git a/src/NeoGet/Extensions/StringExtensions.cs b/src/NvGet/Extensions/StringExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/StringExtensions.cs rename to src/NvGet/Extensions/StringExtensions.cs diff --git a/src/NeoGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs similarity index 100% rename from src/NeoGet/Extensions/XmlDocumentExtensions.cs rename to src/NvGet/Extensions/XmlDocumentExtensions.cs diff --git a/src/NeoGet/Helpers/FileHelper.Net.cs b/src/NvGet/Helpers/FileHelper.Net.cs similarity index 100% rename from src/NeoGet/Helpers/FileHelper.Net.cs rename to src/NvGet/Helpers/FileHelper.Net.cs diff --git a/src/NeoGet/Helpers/FileHelper.UAP.cs b/src/NvGet/Helpers/FileHelper.UAP.cs similarity index 100% rename from src/NeoGet/Helpers/FileHelper.UAP.cs rename to src/NvGet/Helpers/FileHelper.UAP.cs diff --git a/src/NeoGet/Helpers/MarkdownHelper.cs b/src/NvGet/Helpers/MarkdownHelper.cs similarity index 100% rename from src/NeoGet/Helpers/MarkdownHelper.cs rename to src/NvGet/Helpers/MarkdownHelper.cs diff --git a/src/NeoGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs similarity index 100% rename from src/NeoGet/Helpers/SolutionHelper.cs rename to src/NvGet/Helpers/SolutionHelper.cs diff --git a/src/NeoGet/Log/ConsoleLogger.cs b/src/NvGet/Log/ConsoleLogger.cs similarity index 100% rename from src/NeoGet/Log/ConsoleLogger.cs rename to src/NvGet/Log/ConsoleLogger.cs diff --git a/src/NeoGet/Log/SimpleTextWriter.cs b/src/NvGet/Log/SimpleTextWriter.cs similarity index 100% rename from src/NeoGet/Log/SimpleTextWriter.cs rename to src/NvGet/Log/SimpleTextWriter.cs diff --git a/src/NeoGet/NeoGet.csproj b/src/NvGet/NvGet.csproj similarity index 100% rename from src/NeoGet/NeoGet.csproj rename to src/NvGet/NvGet.csproj diff --git a/src/NeoGet/Tools/Downloader/Entities/DownloaderParameters.cs b/src/NvGet/Tools/Downloader/Entities/DownloaderParameters.cs similarity index 100% rename from src/NeoGet/Tools/Downloader/Entities/DownloaderParameters.cs rename to src/NvGet/Tools/Downloader/Entities/DownloaderParameters.cs diff --git a/src/NeoGet/Tools/Downloader/Entities/DownloaderResult.cs b/src/NvGet/Tools/Downloader/Entities/DownloaderResult.cs similarity index 100% rename from src/NeoGet/Tools/Downloader/Entities/DownloaderResult.cs rename to src/NvGet/Tools/Downloader/Entities/DownloaderResult.cs diff --git a/src/NeoGet/Tools/Downloader/NuGetDownloader.cs b/src/NvGet/Tools/Downloader/NuGetDownloader.cs similarity index 100% rename from src/NeoGet/Tools/Downloader/NuGetDownloader.cs rename to src/NvGet/Tools/Downloader/NuGetDownloader.cs diff --git a/src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs b/src/NvGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs rename to src/NvGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs diff --git a/src/NeoGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs b/src/NvGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs rename to src/NvGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs diff --git a/src/NeoGet/Tools/Hierarchy/Entities/ReversePackageReference.cs b/src/NvGet/Tools/Hierarchy/Entities/ReversePackageReference.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/Entities/ReversePackageReference.cs rename to src/NvGet/Tools/Hierarchy/Entities/ReversePackageReference.cs diff --git a/src/NeoGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs b/src/NvGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs rename to src/NvGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs diff --git a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs b/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs rename to src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs diff --git a/src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs b/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs rename to src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs diff --git a/src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs b/src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs similarity index 100% rename from src/NeoGet/Tools/Hierarchy/NuGetHierarchy.cs rename to src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs diff --git a/src/NeoGet/Tools/Updater/Entities/LogDisplayOptions.cs b/src/NvGet/Tools/Updater/Entities/LogDisplayOptions.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Entities/LogDisplayOptions.cs rename to src/NvGet/Tools/Updater/Entities/LogDisplayOptions.cs diff --git a/src/NeoGet/Tools/Updater/Entities/UpdateResult.cs b/src/NvGet/Tools/Updater/Entities/UpdateResult.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Entities/UpdateResult.cs rename to src/NvGet/Tools/Updater/Entities/UpdateResult.cs diff --git a/src/NeoGet/Tools/Updater/Entities/UpdaterPackage.cs b/src/NvGet/Tools/Updater/Entities/UpdaterPackage.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Entities/UpdaterPackage.cs rename to src/NvGet/Tools/Updater/Entities/UpdaterPackage.cs diff --git a/src/NeoGet/Tools/Updater/Entities/UpdaterParameters.cs b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Entities/UpdaterParameters.cs rename to src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs diff --git a/src/NeoGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs b/src/NvGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs rename to src/NvGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs diff --git a/src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs b/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs rename to src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs diff --git a/src/NeoGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs rename to src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs diff --git a/src/NeoGet/Tools/Updater/Log/UpdateOperation.cs b/src/NvGet/Tools/Updater/Log/UpdateOperation.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Log/UpdateOperation.cs rename to src/NvGet/Tools/Updater/Log/UpdateOperation.cs diff --git a/src/NeoGet/Tools/Updater/Log/UpdaterLogger.cs b/src/NvGet/Tools/Updater/Log/UpdaterLogger.cs similarity index 100% rename from src/NeoGet/Tools/Updater/Log/UpdaterLogger.cs rename to src/NvGet/Tools/Updater/Log/UpdaterLogger.cs diff --git a/src/NeoGet/Tools/Updater/NuGetUpdater.cs b/src/NvGet/Tools/Updater/NuGetUpdater.cs similarity index 100% rename from src/NeoGet/Tools/Updater/NuGetUpdater.cs rename to src/NvGet/Tools/Updater/NuGetUpdater.cs From 9432176612d186146a17e03c1cbecee33cb9accd Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 11:20:29 -0500 Subject: [PATCH 168/201] Updated namespaces to NvGet --- src/NvGet.Tests/Constants.cs | 4 ++-- src/NvGet.Tests/Entities/TestPackageFeed.cs | 6 +++--- .../{ => Tools}/ConsoleArgsParserTests.cs | 8 ++++---- .../Tools/Updater/NuGetUpdaterTests.cs | 12 ++++++------ .../Tools/Updater/PackageReferenceTests.cs | 8 ++++---- .../Tools/Updater/UpdaterParametersTests.cs | 8 ++++---- src/NvGet.Tools.Downloader/Program.cs | 7 +++---- src/NvGet.Tools.Hierarchy/Program.cs | 17 +++++++++++------ .../{Updater => }/Arguments/ConsoleArgError.cs | 2 +- .../Arguments/ConsoleArgErrorType.cs | 2 +- .../Arguments/ConsoleArgsContext.Properties.cs | 4 ++-- .../Arguments/ConsoleArgsContext.cs | 8 ++++---- .../NvGet.Tools.Shared.projitems | 10 +++++----- .../NvGet.Tools.Updater.csproj | 4 ++-- src/NvGet.Tools.Updater/Program.cs | 11 +++++------ src/NvGet/Contracts/FileType.cs | 2 +- src/NvGet/Contracts/IPackageFeed.cs | 4 ++-- src/NvGet/Entities/FeedVersion.cs | 2 +- src/NvGet/Entities/LocalPackage.cs | 2 +- src/NvGet/Entities/PackageFeed.cs | 6 +++--- src/NvGet/Entities/PackageNotFoundException.cs | 2 +- src/NvGet/Entities/PackageReference.cs | 4 ++-- src/NvGet/Entities/PackageReferenceHolder.cs | 2 +- src/NvGet/Extensions/DictionaryExtensions.cs | 2 +- src/NvGet/Extensions/FeedVersionExtensions.cs | 4 ++-- src/NvGet/Extensions/FileTypeExtensions.cs | 6 +++--- src/NvGet/Extensions/NuGetResourceExtensions.cs | 4 ++-- src/NvGet/Extensions/NuGetVersionExtensions.cs | 2 +- .../Extensions/PackageDependencyExtensions.cs | 2 +- .../Extensions/PackageReferenceExtensions.cs | 4 ++-- .../PackageSearchMetadataExtensions.cs | 2 +- src/NvGet/Extensions/StringExtensions.cs | 2 +- src/NvGet/Extensions/XmlDocumentExtensions.cs | 4 ++-- src/NvGet/Helpers/FileHelper.Net.cs | 2 +- src/NvGet/Helpers/FileHelper.UAP.cs | 2 +- src/NvGet/Helpers/MarkdownHelper.cs | 4 ++-- src/NvGet/Helpers/SolutionHelper.cs | 8 ++++---- src/NvGet/Log/ConsoleLogger.cs | 2 +- src/NvGet/Log/SimpleTextWriter.cs | 2 +- .../Downloader/Entities/DownloaderParameters.cs | 4 ++-- .../Downloader/Entities/DownloaderResult.cs | 4 ++-- src/NvGet/Tools/Downloader/NuGetDownloader.cs | 12 ++++++------ .../Hierarchy/Entities/PackageHierarchyItem.cs | 4 ++-- .../Entities/ProjectPackageHierarchy.cs | 2 +- .../Entities/ReversePackageReference.cs | 2 +- .../Entities/SolutionPackageHierarchy.cs | 2 +- .../Extensions/PackageHierarchyExtensions.cs | 6 +++--- .../PackageHierarchyItemExtensions.cs | 4 ++-- src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs | 16 ++++++++-------- .../Tools/Updater/Entities/LogDisplayOptions.cs | 2 +- .../Tools/Updater/Entities/UpdateResult.cs | 2 +- .../Tools/Updater/Entities/UpdaterPackage.cs | 4 ++-- .../Tools/Updater/Entities/UpdaterParameters.cs | 6 +++--- .../Extensions/UpdateOperationExtensions.cs | 8 ++++---- .../Extensions/UpdaterParametersExtension.cs | 12 ++++++------ .../Updater/Extensions/XmlDocumentExtensions.cs | 6 +++--- src/NvGet/Tools/Updater/Log/UpdateOperation.cs | 4 ++-- src/NvGet/Tools/Updater/Log/UpdaterLogger.cs | 10 +++++----- src/NvGet/Tools/Updater/NuGetUpdater.cs | 16 ++++++++-------- 59 files changed, 158 insertions(+), 155 deletions(-) rename src/NvGet.Tests/{ => Tools}/ConsoleArgsParserTests.cs (98%) rename src/NvGet.Tools.Shared/{Updater => }/Arguments/ConsoleArgError.cs (94%) rename src/NvGet.Tools.Shared/{Updater => }/Arguments/ConsoleArgErrorType.cs (71%) rename src/NvGet.Tools.Shared/{Updater => }/Arguments/ConsoleArgsContext.Properties.cs (85%) rename src/NvGet.Tools.Shared/{Updater => }/Arguments/ConsoleArgsContext.cs (97%) diff --git a/src/NvGet.Tests/Constants.cs b/src/NvGet.Tests/Constants.cs index 9e966ec..fa755bd 100644 --- a/src/NvGet.Tests/Constants.cs +++ b/src/NvGet.Tests/Constants.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Text; -using NeoGet.Tools.Tests.Entities; +using NvGet.Tools.Tests.Entities; -namespace NeoGet.Tests +namespace NvGet.Tests { public static class Constants { diff --git a/src/NvGet.Tests/Entities/TestPackageFeed.cs b/src/NvGet.Tests/Entities/TestPackageFeed.cs index 3f71500..0fadde5 100644 --- a/src/NvGet.Tests/Entities/TestPackageFeed.cs +++ b/src/NvGet.Tests/Entities/TestPackageFeed.cs @@ -6,10 +6,10 @@ using System.Threading.Tasks; using NuGet.Frameworks; using NuGet.Packaging.Core; -using NeoGet.Contracts; -using NeoGet.Entities; +using NvGet.Contracts; +using NvGet.Entities; -namespace NeoGet.Tools.Tests.Entities +namespace NvGet.Tools.Tests.Entities { public class TestPackageFeed : IPackageFeed { diff --git a/src/NvGet.Tests/ConsoleArgsParserTests.cs b/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs similarity index 98% rename from src/NvGet.Tests/ConsoleArgsParserTests.cs rename to src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs index 0b04810..c030d9a 100644 --- a/src/NvGet.Tests/ConsoleArgsParserTests.cs +++ b/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs @@ -5,11 +5,11 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NeoGet.Entities; -using NeoGet.Tools.Updater.Arguments; -using NeoGet.Tools.Updater.Entities; +using NvGet.Entities; +using NvGet.Tools.Arguments; +using NvGet.Tools.Updater.Entities; -namespace NuGet.Updater.Tests +namespace NvGet.Tests.Tools { [TestClass] public class ConsoleArgsParserTests diff --git a/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs b/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs index 0a08567..d34812e 100644 --- a/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs +++ b/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs @@ -3,14 +3,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NeoGet.Contracts; -using NeoGet.Tools.Tests.Entities; -using NeoGet.Tools.Updater; -using NeoGet.Tools.Updater.Entities; -using NeoGet.Tools.Updater.Log; +using NvGet.Contracts; +using NvGet.Tools.Tests.Entities; +using NvGet.Tools.Updater; +using NvGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Log; using Uno.Extensions; -namespace NeoGet.Tests.Tools +namespace NvGet.Tests.Tools { [TestClass] public class NuGetUpdaterTests diff --git a/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs b/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs index 8a8c4e9..fc36035 100644 --- a/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs +++ b/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs @@ -1,12 +1,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NeoGet.Entities; -using NeoGet.Tools.Updater.Entities; -using NeoGet.Tools.Updater.Extensions; +using NvGet.Entities; +using NvGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Extensions; using NuGet.Versioning; -namespace NeoGet.Tests +namespace NvGet.Tests { [TestClass] public class PackageReferenceTests diff --git a/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs b/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs index 3c1782c..13fd469 100644 --- a/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs +++ b/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs @@ -1,12 +1,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using NeoGet.Entities; -using NeoGet.Tools.Updater.Entities; -using NeoGet.Tools.Updater.Extensions; +using NvGet.Entities; +using NvGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Extensions; using NuGet.Versioning; -namespace NeoGet.Tests.Tools.Updater +namespace NvGet.Tests.Tools.Updater { [TestClass] public class UpdaterParametersTests diff --git a/src/NvGet.Tools.Downloader/Program.cs b/src/NvGet.Tools.Downloader/Program.cs index 06c4e81..f16f65e 100644 --- a/src/NvGet.Tools.Downloader/Program.cs +++ b/src/NvGet.Tools.Downloader/Program.cs @@ -3,12 +3,11 @@ using System.Threading; using System.Threading.Tasks; using Mono.Options; -using NeoGet.Entities; -using NeoGet.Tools.Downloader; -using NeoGet.Tools.Downloader.Entities; +using NvGet.Entities; +using NvGet.Tools.Downloader.Entities; using Uno.Extensions; -namespace NuGet.Downloader.Tool +namespace NvGet.Tools.Downloader { public static class Program { diff --git a/src/NvGet.Tools.Hierarchy/Program.cs b/src/NvGet.Tools.Hierarchy/Program.cs index 4c7d622..d210b71 100644 --- a/src/NvGet.Tools.Hierarchy/Program.cs +++ b/src/NvGet.Tools.Hierarchy/Program.cs @@ -5,13 +5,13 @@ using System.Threading; using System.Threading.Tasks; using Mono.Options; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Extensions; -using NeoGet.Tools.Hierarchy; -using NeoGet.Tools.Hierarchy.Extensions; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Extensions; +using NvGet.Tools.Hierarchy.Extensions; +using Uno.Extensions; -namespace NuGet.Hierarchy.Tool +namespace NvGet.Tools.Hierarchy.Tool { public static class Program { @@ -41,6 +41,11 @@ public static async Task Main(string[] args) options.WriteOptionDescriptions(Console.Out); } + if(sources.Empty()) + { + sources.Add(PackageFeed.NuGetOrg); + } + var tool = new NuGetHierarchy(target, sources, ConsoleLogger.Instance); var result = await tool.RunAsync(CancellationToken.None); diff --git a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgError.cs similarity index 94% rename from src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs rename to src/NvGet.Tools.Shared/Arguments/ConsoleArgError.cs index 4332743..441b78d 100644 --- a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgError.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgError.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace NeoGet.Tools.Updater.Arguments +namespace NvGet.Tools.Arguments { public class ConsoleArgError { diff --git a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgErrorType.cs similarity index 71% rename from src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs rename to src/NvGet.Tools.Shared/Arguments/ConsoleArgErrorType.cs index 0ae0acb..eaa7fba 100644 --- a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgErrorType.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgErrorType.cs @@ -1,4 +1,4 @@ -namespace NeoGet.Tools.Updater.Arguments +namespace NvGet.Tools.Arguments { public enum ConsoleArgErrorType { diff --git a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.Properties.cs similarity index 85% rename from src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs rename to src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.Properties.cs index bd5251a..9b3d400 100644 --- a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.Properties.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.Properties.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; -using NeoGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Entities; -namespace NeoGet.Tools.Updater.Arguments +namespace NvGet.Tools.Arguments { public partial class ConsoleArgsContext { diff --git a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs similarity index 97% rename from src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs rename to src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs index 6b92367..6274c71 100644 --- a/src/NvGet.Tools.Shared/Updater/Arguments/ConsoleArgsContext.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs @@ -6,14 +6,14 @@ using System.Text; using System.Threading.Tasks; using Mono.Options; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Tools.Updater.Entities; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Tools.Updater.Entities; using Newtonsoft.Json; using NuGet.Versioning; using Uno.Extensions; -namespace NeoGet.Tools.Updater.Arguments +namespace NvGet.Tools.Arguments { public partial class ConsoleArgsContext { diff --git a/src/NvGet.Tools.Shared/NvGet.Tools.Shared.projitems b/src/NvGet.Tools.Shared/NvGet.Tools.Shared.projitems index b472589..fcc71da 100644 --- a/src/NvGet.Tools.Shared/NvGet.Tools.Shared.projitems +++ b/src/NvGet.Tools.Shared/NvGet.Tools.Shared.projitems @@ -6,12 +6,12 @@ 4c395d24-0028-47ef-b07e-5566344e78de - NeoGet.Tools + NvGet.Tools - - - - + + + + \ No newline at end of file diff --git a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index 350fcfa..fffd7e8 100644 --- a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -36,12 +36,12 @@ - <_Parameter1>NuGet.Updater.Tests + <_Parameter1>NvGet.Tests - + diff --git a/src/NvGet.Tools.Updater/Program.cs b/src/NvGet.Tools.Updater/Program.cs index 18ead8a..ddc2402 100644 --- a/src/NvGet.Tools.Updater/Program.cs +++ b/src/NvGet.Tools.Updater/Program.cs @@ -5,15 +5,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NeoGet.Helpers; -using NeoGet.Log; -using NeoGet.Tools.Updater; -using NeoGet.Tools.Updater.Arguments; -using NeoGet.Tools.Updater.Entities; using Newtonsoft.Json; +using NvGet.Helpers; +using NvGet.Log; +using NvGet.Tools.Arguments; +using NvGet.Tools.Updater.Entities; using Uno.Extensions; -namespace NuGet.Updater.Tool +namespace NvGet.Tools.Updater { public static class Program { diff --git a/src/NvGet/Contracts/FileType.cs b/src/NvGet/Contracts/FileType.cs index e9023b8..90bead9 100644 --- a/src/NvGet/Contracts/FileType.cs +++ b/src/NvGet/Contracts/FileType.cs @@ -1,6 +1,6 @@ using System; -namespace NeoGet.Contracts +namespace NvGet.Contracts { /// /// The type of file containing a reference. diff --git a/src/NvGet/Contracts/IPackageFeed.cs b/src/NvGet/Contracts/IPackageFeed.cs index 3e97cd3..dfeb953 100644 --- a/src/NvGet/Contracts/IPackageFeed.cs +++ b/src/NvGet/Contracts/IPackageFeed.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using NeoGet.Entities; +using NvGet.Entities; using NuGet.Frameworks; using NuGet.Packaging.Core; -namespace NeoGet.Contracts +namespace NvGet.Contracts { public interface IPackageFeed { diff --git a/src/NvGet/Entities/FeedVersion.cs b/src/NvGet/Entities/FeedVersion.cs index 6480ca2..3327a5c 100644 --- a/src/NvGet/Entities/FeedVersion.cs +++ b/src/NvGet/Entities/FeedVersion.cs @@ -1,7 +1,7 @@ using System; using NuGet.Versioning; -namespace NeoGet.Entities +namespace NvGet.Entities { public class FeedVersion : IComparable { diff --git a/src/NvGet/Entities/LocalPackage.cs b/src/NvGet/Entities/LocalPackage.cs index 247ced0..0de043a 100644 --- a/src/NvGet/Entities/LocalPackage.cs +++ b/src/NvGet/Entities/LocalPackage.cs @@ -1,6 +1,6 @@ using NuGet.Packaging.Core; -namespace NeoGet.Entities +namespace NvGet.Entities { public class LocalPackage { diff --git a/src/NvGet/Entities/PackageFeed.cs b/src/NvGet/Entities/PackageFeed.cs index f30ef40..f0c0260 100644 --- a/src/NvGet/Entities/PackageFeed.cs +++ b/src/NvGet/Entities/PackageFeed.cs @@ -10,12 +10,12 @@ using NuGet.Frameworks; using NuGet.Packaging.Core; using NuGet.Protocol.Core.Types; -using NeoGet.Contracts; -using NeoGet.Extensions; +using NvGet.Contracts; +using NvGet.Extensions; using Uno.Extensions; using NuGet.Shared.Extensions; -namespace NeoGet.Entities +namespace NvGet.Entities { public class PackageFeed : IPackageFeed { diff --git a/src/NvGet/Entities/PackageNotFoundException.cs b/src/NvGet/Entities/PackageNotFoundException.cs index e152680..b088cdf 100644 --- a/src/NvGet/Entities/PackageNotFoundException.cs +++ b/src/NvGet/Entities/PackageNotFoundException.cs @@ -1,7 +1,7 @@ using System; using NuGet.Packaging.Core; -namespace NeoGet.Entities +namespace NvGet.Entities { [Serializable] public class PackageNotFoundException : Exception diff --git a/src/NvGet/Entities/PackageReference.cs b/src/NvGet/Entities/PackageReference.cs index f1d8f4c..51b5833 100644 --- a/src/NvGet/Entities/PackageReference.cs +++ b/src/NvGet/Entities/PackageReference.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using NuGet.Packaging.Core; -using NeoGet.Contracts; +using NvGet.Contracts; using NuGet.Versioning; -namespace NeoGet.Entities +namespace NvGet.Entities { public class PackageReference { diff --git a/src/NvGet/Entities/PackageReferenceHolder.cs b/src/NvGet/Entities/PackageReferenceHolder.cs index 28d50a4..dec973f 100644 --- a/src/NvGet/Entities/PackageReferenceHolder.cs +++ b/src/NvGet/Entities/PackageReferenceHolder.cs @@ -2,7 +2,7 @@ using System.IO; using NuGet.Packaging.Core; -namespace NeoGet.Entities +namespace NvGet.Entities { public class PackageReferenceHolder { diff --git a/src/NvGet/Extensions/DictionaryExtensions.cs b/src/NvGet/Extensions/DictionaryExtensions.cs index d5f8330..f44d5ae 100644 --- a/src/NvGet/Extensions/DictionaryExtensions.cs +++ b/src/NvGet/Extensions/DictionaryExtensions.cs @@ -4,7 +4,7 @@ using Uno; using Uno.Extensions.ValueType; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class DictionaryExtensions { diff --git a/src/NvGet/Extensions/FeedVersionExtensions.cs b/src/NvGet/Extensions/FeedVersionExtensions.cs index fbdb176..6c2c92f 100644 --- a/src/NvGet/Extensions/FeedVersionExtensions.cs +++ b/src/NvGet/Extensions/FeedVersionExtensions.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using NeoGet.Entities; +using NvGet.Entities; using Uno.Extensions; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class FeedVersionExtensions { diff --git a/src/NvGet/Extensions/FileTypeExtensions.cs b/src/NvGet/Extensions/FileTypeExtensions.cs index 4260bce..2151c62 100644 --- a/src/NvGet/Extensions/FileTypeExtensions.cs +++ b/src/NvGet/Extensions/FileTypeExtensions.cs @@ -1,8 +1,8 @@ using System.Linq; -using NeoGet.Contracts; -using NeoGet.Entities; +using NvGet.Contracts; +using NvGet.Entities; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class FileTypeExtensions { diff --git a/src/NvGet/Extensions/NuGetResourceExtensions.cs b/src/NvGet/Extensions/NuGetResourceExtensions.cs index 42f9644..a1940ab 100644 --- a/src/NvGet/Extensions/NuGetResourceExtensions.cs +++ b/src/NvGet/Extensions/NuGetResourceExtensions.cs @@ -6,9 +6,9 @@ using NuGet.Packaging.Core; using NuGet.Protocol; using NuGet.Protocol.Core.Types; -using NeoGet.Entities; +using NvGet.Entities; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class NuGetResourceExtensions { diff --git a/src/NvGet/Extensions/NuGetVersionExtensions.cs b/src/NvGet/Extensions/NuGetVersionExtensions.cs index a402e34..e8ee441 100644 --- a/src/NvGet/Extensions/NuGetVersionExtensions.cs +++ b/src/NvGet/Extensions/NuGetVersionExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using NuGet.Versioning; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class NuGetVersionExtensions { diff --git a/src/NvGet/Extensions/PackageDependencyExtensions.cs b/src/NvGet/Extensions/PackageDependencyExtensions.cs index 521b098..c3fdff8 100644 --- a/src/NvGet/Extensions/PackageDependencyExtensions.cs +++ b/src/NvGet/Extensions/PackageDependencyExtensions.cs @@ -1,6 +1,6 @@ using NuGet.Packaging.Core; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class PackageDependencyExtensions { diff --git a/src/NvGet/Extensions/PackageReferenceExtensions.cs b/src/NvGet/Extensions/PackageReferenceExtensions.cs index e1cfd54..416a673 100644 --- a/src/NvGet/Extensions/PackageReferenceExtensions.cs +++ b/src/NvGet/Extensions/PackageReferenceExtensions.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using NeoGet.Entities; +using NvGet.Entities; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class PackageReferenceExtensions { diff --git a/src/NvGet/Extensions/PackageSearchMetadataExtensions.cs b/src/NvGet/Extensions/PackageSearchMetadataExtensions.cs index 34691bf..0195327 100644 --- a/src/NvGet/Extensions/PackageSearchMetadataExtensions.cs +++ b/src/NvGet/Extensions/PackageSearchMetadataExtensions.cs @@ -2,7 +2,7 @@ using System.Linq; using NuGet.Protocol.Core.Types; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class PackageSearchMetadataExtensions { diff --git a/src/NvGet/Extensions/StringExtensions.cs b/src/NvGet/Extensions/StringExtensions.cs index 74ef735..69e7387 100644 --- a/src/NvGet/Extensions/StringExtensions.cs +++ b/src/NvGet/Extensions/StringExtensions.cs @@ -5,7 +5,7 @@ using NuGet.Versioning; using Uno.Extensions; -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class StringExtensions { diff --git a/src/NvGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs index 321fb62..88494cb 100644 --- a/src/NvGet/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Extensions/XmlDocumentExtensions.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NeoGet.Entities; +using NvGet.Entities; using NuGet.Packaging.Core; using NuGet.Versioning; using Uno.Extensions; @@ -21,7 +21,7 @@ using XmlNode = System.Xml.XmlNode; #endif -namespace NeoGet.Extensions +namespace NvGet.Extensions { public static class XmlDocumentExtensions { diff --git a/src/NvGet/Helpers/FileHelper.Net.cs b/src/NvGet/Helpers/FileHelper.Net.cs index 1d2c1f2..ad39b0b 100644 --- a/src/NvGet/Helpers/FileHelper.Net.cs +++ b/src/NvGet/Helpers/FileHelper.Net.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Uno.Extensions; -namespace NeoGet.Helpers +namespace NvGet.Helpers { public static class FileHelper { diff --git a/src/NvGet/Helpers/FileHelper.UAP.cs b/src/NvGet/Helpers/FileHelper.UAP.cs index e87a558..29eeaed 100644 --- a/src/NvGet/Helpers/FileHelper.UAP.cs +++ b/src/NvGet/Helpers/FileHelper.UAP.cs @@ -6,7 +6,7 @@ using Windows.Storage; using Windows.Storage.Search; -namespace NeoGet.Helpers +namespace NvGet.Helpers { /// /// UAP-Specific methods. diff --git a/src/NvGet/Helpers/MarkdownHelper.cs b/src/NvGet/Helpers/MarkdownHelper.cs index 3020b10..2d3600b 100644 --- a/src/NvGet/Helpers/MarkdownHelper.cs +++ b/src/NvGet/Helpers/MarkdownHelper.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using NeoGet.Extensions; +using NvGet.Extensions; using Uno.Extensions; -namespace NeoGet.Helpers +namespace NvGet.Helpers { public static class MarkdownHelper { diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 3f18b75..45ba7f0 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -7,13 +7,13 @@ using System.Threading.Tasks; using NuGet.Common; using NuGet.Packaging.Core; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Extensions; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Extensions; using NuGet.Versioning; using Uno.Extensions; -namespace NeoGet.Helpers +namespace NvGet.Helpers { /// /// Shared solution helper methods. diff --git a/src/NvGet/Log/ConsoleLogger.cs b/src/NvGet/Log/ConsoleLogger.cs index 3a5fc53..506d5a6 100644 --- a/src/NvGet/Log/ConsoleLogger.cs +++ b/src/NvGet/Log/ConsoleLogger.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using NuGet.Common; -namespace NeoGet.Entities +namespace NvGet.Entities { public class ConsoleLogger : ILogger { diff --git a/src/NvGet/Log/SimpleTextWriter.cs b/src/NvGet/Log/SimpleTextWriter.cs index 03f280d..51b9efe 100644 --- a/src/NvGet/Log/SimpleTextWriter.cs +++ b/src/NvGet/Log/SimpleTextWriter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text; -namespace NeoGet.Log +namespace NvGet.Log { public class SimpleTextWriter : TextWriter { diff --git a/src/NvGet/Tools/Downloader/Entities/DownloaderParameters.cs b/src/NvGet/Tools/Downloader/Entities/DownloaderParameters.cs index c3010c8..b2f14c8 100644 --- a/src/NvGet/Tools/Downloader/Entities/DownloaderParameters.cs +++ b/src/NvGet/Tools/Downloader/Entities/DownloaderParameters.cs @@ -1,6 +1,6 @@ -using NeoGet.Contracts; +using NvGet.Contracts; -namespace NeoGet.Tools.Downloader.Entities +namespace NvGet.Tools.Downloader.Entities { public class DownloaderParameters { diff --git a/src/NvGet/Tools/Downloader/Entities/DownloaderResult.cs b/src/NvGet/Tools/Downloader/Entities/DownloaderResult.cs index ddbaa0b..ad051eb 100644 --- a/src/NvGet/Tools/Downloader/Entities/DownloaderResult.cs +++ b/src/NvGet/Tools/Downloader/Entities/DownloaderResult.cs @@ -1,6 +1,6 @@ -using NeoGet.Entities; +using NvGet.Entities; -namespace NeoGet.Tools.Downloader.Entities +namespace NvGet.Tools.Downloader.Entities { public class DownloaderResult { diff --git a/src/NvGet/Tools/Downloader/NuGetDownloader.cs b/src/NvGet/Tools/Downloader/NuGetDownloader.cs index cfa5f0d..bbf4026 100644 --- a/src/NvGet/Tools/Downloader/NuGetDownloader.cs +++ b/src/NvGet/Tools/Downloader/NuGetDownloader.cs @@ -4,16 +4,16 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Tools.Downloader.Entities; -using NeoGet.Tools.Hierarchy; -using NeoGet.Tools.Hierarchy.Extensions; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Tools.Downloader.Entities; +using NvGet.Tools.Hierarchy; +using NvGet.Tools.Hierarchy.Extensions; using NuGet.Common; using NuGet.Packaging.Core; using Uno.Extensions; -namespace NeoGet.Tools.Downloader +namespace NvGet.Tools.Downloader { public class NuGetDownloader { diff --git a/src/NvGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs b/src/NvGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs index 7dc80f3..9323c51 100644 --- a/src/NvGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs +++ b/src/NvGet/Tools/Hierarchy/Entities/PackageHierarchyItem.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using NeoGet.Extensions; +using NvGet.Extensions; using NuGet.Frameworks; using NuGet.Packaging.Core; -namespace NeoGet.Tools.Hierarchy.Entities +namespace NvGet.Tools.Hierarchy.Entities { public class PackageHierarchyItem : IEquatable { diff --git a/src/NvGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs b/src/NvGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs index 804b1b5..50d7e37 100644 --- a/src/NvGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs +++ b/src/NvGet/Tools/Hierarchy/Entities/ProjectPackageHierarchy.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NeoGet.Tools.Hierarchy.Entities +namespace NvGet.Tools.Hierarchy.Entities { public class ProjectPackageHierarchy { diff --git a/src/NvGet/Tools/Hierarchy/Entities/ReversePackageReference.cs b/src/NvGet/Tools/Hierarchy/Entities/ReversePackageReference.cs index beb7b8d..41e7770 100644 --- a/src/NvGet/Tools/Hierarchy/Entities/ReversePackageReference.cs +++ b/src/NvGet/Tools/Hierarchy/Entities/ReversePackageReference.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using NuGet.Packaging.Core; -namespace NeoGet.Tools.Hierarchy.Entities +namespace NvGet.Tools.Hierarchy.Entities { public class ReversePackageReference { diff --git a/src/NvGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs b/src/NvGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs index 495dbc6..5c0eec1 100644 --- a/src/NvGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs +++ b/src/NvGet/Tools/Hierarchy/Entities/SolutionPackageHierarchy.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NeoGet.Tools.Hierarchy.Entities +namespace NvGet.Tools.Hierarchy.Entities { public class SolutionPackageHierarchy { diff --git a/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs b/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs index 9e8d948..7328ae6 100644 --- a/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs +++ b/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyExtensions.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using NeoGet.Extensions; -using NeoGet.Tools.Hierarchy.Entities; +using NvGet.Extensions; +using NvGet.Tools.Hierarchy.Entities; using NuGet.Frameworks; using NuGet.Packaging; using NuGet.Packaging.Core; -namespace NeoGet.Tools.Hierarchy.Extensions +namespace NvGet.Tools.Hierarchy.Extensions { public static class PackageHierarchyExtensions { diff --git a/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs b/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs index 0ef5ba0..b0b2ca6 100644 --- a/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs +++ b/src/NvGet/Tools/Hierarchy/Extensions/PackageHierarchyItemExtensions.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using NeoGet.Tools.Hierarchy.Entities; +using NvGet.Tools.Hierarchy.Entities; using NuGet.Frameworks; using NuGet.Packaging.Core; -namespace NeoGet.Tools.Hierarchy.Extensions +namespace NvGet.Tools.Hierarchy.Extensions { public static class PackageHierarchyItemExtensions { diff --git a/src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs b/src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs index d541cd9..607c47f 100644 --- a/src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs +++ b/src/NvGet/Tools/Hierarchy/NuGetHierarchy.cs @@ -3,18 +3,18 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Extensions; -using NeoGet.Helpers; -using NeoGet.Tools.Hierarchy.Entities; -using NeoGet.Tools.Hierarchy.Extensions; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Extensions; +using NvGet.Helpers; +using NvGet.Tools.Hierarchy.Entities; +using NvGet.Tools.Hierarchy.Extensions; using NuGet.Common; using NuGet.Packaging; using NuGet.Packaging.Core; -using PackageReference = NeoGet.Entities.PackageReference; +using PackageReference = NvGet.Entities.PackageReference; -namespace NeoGet.Tools.Hierarchy +namespace NvGet.Tools.Hierarchy { public class NuGetHierarchy { diff --git a/src/NvGet/Tools/Updater/Entities/LogDisplayOptions.cs b/src/NvGet/Tools/Updater/Entities/LogDisplayOptions.cs index 6624d3e..0e1a741 100644 --- a/src/NvGet/Tools/Updater/Entities/LogDisplayOptions.cs +++ b/src/NvGet/Tools/Updater/Entities/LogDisplayOptions.cs @@ -1,4 +1,4 @@ -namespace NeoGet.Tools.Updater.Entities +namespace NvGet.Tools.Updater.Entities { internal struct LogDisplayOptions { diff --git a/src/NvGet/Tools/Updater/Entities/UpdateResult.cs b/src/NvGet/Tools/Updater/Entities/UpdateResult.cs index a36e6c0..04baa02 100644 --- a/src/NvGet/Tools/Updater/Entities/UpdateResult.cs +++ b/src/NvGet/Tools/Updater/Entities/UpdateResult.cs @@ -1,6 +1,6 @@ using System; -namespace NeoGet.Tools.Updater.Entities +namespace NvGet.Tools.Updater.Entities { public class UpdateResult : IEquatable { diff --git a/src/NvGet/Tools/Updater/Entities/UpdaterPackage.cs b/src/NvGet/Tools/Updater/Entities/UpdaterPackage.cs index 3557869..a03e8fb 100644 --- a/src/NvGet/Tools/Updater/Entities/UpdaterPackage.cs +++ b/src/NvGet/Tools/Updater/Entities/UpdaterPackage.cs @@ -1,6 +1,6 @@ -using NeoGet.Entities; +using NvGet.Entities; -namespace NeoGet.Tools.Updater.Entities +namespace NvGet.Tools.Updater.Entities { public class UpdaterPackage { diff --git a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs index a5c536f..af098cc 100644 --- a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs +++ b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using NeoGet.Contracts; -using NeoGet.Entities; +using NvGet.Contracts; +using NvGet.Entities; using NuGet.Versioning; -namespace NeoGet.Tools.Updater.Entities +namespace NvGet.Tools.Updater.Entities { public class UpdaterParameters { diff --git a/src/NvGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs b/src/NvGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs index ce65d48..a3351cb 100644 --- a/src/NvGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/UpdateOperationExtensions.cs @@ -1,8 +1,8 @@ -using NeoGet.Extensions; -using NeoGet.Tools.Updater.Entities; -using NeoGet.Tools.Updater.Log; +using NvGet.Extensions; +using NvGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Log; -namespace NeoGet.Tools.Updater.Extensions +namespace NvGet.Tools.Updater.Extensions { public static class UpdateOperationExtensions { diff --git a/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs b/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs index 61a2230..84f9eae 100644 --- a/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs @@ -3,14 +3,14 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Extensions; -using NeoGet.Helpers; -using NeoGet.Tools.Updater.Entities; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Extensions; +using NvGet.Helpers; +using NvGet.Tools.Updater.Entities; using Uno.Extensions; -namespace NeoGet.Tools.Updater.Extensions +namespace NvGet.Tools.Updater.Extensions { public static class UpdaterParametersExtension { diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index f10f610..4e3556b 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; using System.Xml; -using NeoGet.Extensions; -using NeoGet.Tools.Updater.Log; +using NvGet.Extensions; +using NvGet.Tools.Updater.Log; using Uno.Extensions; #if WINDOWS_UWP @@ -12,7 +12,7 @@ using XmlDocument = System.Xml.XmlDocument; #endif -namespace NeoGet.Tools.Updater.Extensions +namespace NvGet.Tools.Updater.Extensions { public static class XmlDocumentExtensions { diff --git a/src/NvGet/Tools/Updater/Log/UpdateOperation.cs b/src/NvGet/Tools/Updater/Log/UpdateOperation.cs index b5a33fb..0fc9a66 100644 --- a/src/NvGet/Tools/Updater/Log/UpdateOperation.cs +++ b/src/NvGet/Tools/Updater/Log/UpdateOperation.cs @@ -1,9 +1,9 @@ using System; -using NeoGet.Entities; +using NvGet.Entities; using NuGet.Packaging.Core; using NuGet.Versioning; -namespace NeoGet.Tools.Updater.Log +namespace NvGet.Tools.Updater.Log { public class UpdateOperation { diff --git a/src/NvGet/Tools/Updater/Log/UpdaterLogger.cs b/src/NvGet/Tools/Updater/Log/UpdaterLogger.cs index 61e6876..36030a9 100644 --- a/src/NvGet/Tools/Updater/Log/UpdaterLogger.cs +++ b/src/NvGet/Tools/Updater/Log/UpdaterLogger.cs @@ -3,14 +3,14 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using NeoGet.Extensions; -using NeoGet.Helpers; -using NeoGet.Tools.Updater.Entities; -using NeoGet.Tools.Updater.Extensions; +using NvGet.Extensions; +using NvGet.Helpers; +using NvGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Extensions; using NuGet.Common; using NuGet.Versioning; -namespace NeoGet.Tools.Updater.Log +namespace NvGet.Tools.Updater.Log { public class UpdaterLogger : ILogger, IEqualityComparer { diff --git a/src/NvGet/Tools/Updater/NuGetUpdater.cs b/src/NvGet/Tools/Updater/NuGetUpdater.cs index 5b893c5..13855cc 100644 --- a/src/NvGet/Tools/Updater/NuGetUpdater.cs +++ b/src/NvGet/Tools/Updater/NuGetUpdater.cs @@ -4,13 +4,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using NeoGet.Contracts; -using NeoGet.Entities; -using NeoGet.Extensions; -using NeoGet.Helpers; -using NeoGet.Tools.Updater.Entities; -using NeoGet.Tools.Updater.Extensions; -using NeoGet.Tools.Updater.Log; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Extensions; +using NvGet.Helpers; +using NvGet.Tools.Updater.Entities; +using NvGet.Tools.Updater.Extensions; +using NvGet.Tools.Updater.Log; using Uno.Extensions; #if WINDOWS_UWP @@ -21,7 +21,7 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NuGet.Updater.Tests")] -namespace NeoGet.Tools.Updater +namespace NvGet.Tools.Updater { public class NuGetUpdater { From 11ce01cd600577526123097b46ebb983fd54f18a Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 11:54:03 -0500 Subject: [PATCH 169/201] Updated packages --- src/Directory.Build.targets | 9 +++++ src/NvGet.Tests/NvGet.Tests.csproj | 8 ++--- .../NvGet.Tools.Downloader.csproj | 22 +++++++----- .../NvGet.Tools.Hierarchy.csproj | 36 ++++++++++++++++--- .../NvGet.Tools.Updater.csproj | 18 ++++------ .../Extensions/NuGetResourceExtensions.cs | 15 ++++++-- src/NvGet/NvGet.csproj | 16 +++------ 7 files changed, 80 insertions(+), 44 deletions(-) create mode 100644 src/Directory.Build.targets diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..a0823c4 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/NvGet.Tests/NvGet.Tests.csproj b/src/NvGet.Tests/NvGet.Tests.csproj index ca16cf2..68bc5c1 100644 --- a/src/NvGet.Tests/NvGet.Tests.csproj +++ b/src/NvGet.Tests/NvGet.Tests.csproj @@ -6,10 +6,10 @@ - - - - + + + + diff --git a/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj index 921eec4..2654a25 100644 --- a/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj +++ b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj @@ -1,8 +1,13 @@ - - Exe + + Exe netcoreapp3.1 + true + true + nugetdownloader + latest + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage @@ -22,13 +27,12 @@ - - - - - - - + + + + + + diff --git a/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj index d86d008..ca17ab6 100644 --- a/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj +++ b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj @@ -1,13 +1,39 @@  - - Exe + + Exe netcoreapp3.1 - + true + true + nugethierarchy + latest + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + + + + + nventive.NuGet.Hierarchy.Tool + NuGet Hierarchy Tool + NuGet Hierarchy allows you to view the dependency tree of a solution + nventive + nventive + Apache-2.0 + icon.png + https://github.com/nventive/NuGet.Updater + + + + + True + + + + - + + - + diff --git a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index fffd7e8..0cc0ca9 100644 --- a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -22,11 +22,6 @@ https://github.com/nventive/NuGet.Updater - - - - - True @@ -41,14 +36,13 @@ - + + + + + + - - - - - - diff --git a/src/NvGet/Extensions/NuGetResourceExtensions.cs b/src/NvGet/Extensions/NuGetResourceExtensions.cs index a1940ab..47e275d 100644 --- a/src/NvGet/Extensions/NuGetResourceExtensions.cs +++ b/src/NvGet/Extensions/NuGetResourceExtensions.cs @@ -60,7 +60,18 @@ public static async Task PushPackage( ILogger log ) { - await resource.Push(package.Path, null, 100, true, s => s, s => s, true, log); + await resource.Push( + package.Path, + symbolSource: null, + timeoutInSecond: 100, + disableBuffering: true, + getApiKey: s => s, + getSymbolApiKey: s => s, + noServiceEndpoint: true, + skipDuplicate: true, + symbolPackageUpdateResource: default, + log + ); return true; } @@ -69,7 +80,7 @@ internal static TResource GetResource(this PackageSource source) where TResource : class, INuGetResource { var repositoryProvider = new SourceRepositoryProvider( - Settings.LoadDefaultSettings(null), + new PackageSourceProvider(Settings.LoadDefaultSettings(null)), Repository.Provider.GetCoreV3() ); diff --git a/src/NvGet/NvGet.csproj b/src/NvGet/NvGet.csproj index d338c86..aa812cd 100644 --- a/src/NvGet/NvGet.csproj +++ b/src/NvGet/NvGet.csproj @@ -1,4 +1,4 @@ - + netstandard20;net472 $(NoWarn);CA1068;NU1701 @@ -18,17 +18,9 @@ - - - - - - - - - - - + + + From 2614d13a88fba560fd3df9ba8ea0439ef3f3fc51 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 12:16:02 -0500 Subject: [PATCH 170/201] Updated documentation --- CHANGELOG.md | 2 ++ README.md | 8 ++++++-- src/NvGet.Tools.Downloader/README.md | 2 +- src/NvGet.Tools.Hierarchy/README.md | 15 +++++++++++++++ src/NvGet.Tools.Updater/Readme.md | 2 +- 5 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/NvGet.Tools.Hierarchy/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 28a0808..bded984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added +- Added tool allowing to display package dependencies ### Changed +- Changed code structure to be able to share code between tools ### Fixed - Fixed issue where the UWP implementation was not properly checking for files' existence. ### Removed diff --git a/README.md b/README.md index 03a1b14..fd7a8ad 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,15 @@ NuGet packages manipulations made easy. ## NuGet.Updater -Documentation about the NuGet.Updater can be found [here](src/NuGet.Updater.Tool/Readme.md) +Documentation about the NuGet.Updater can be found [here](src/NvGet.Tools.Updater/Readme.md) ## NuGet.Downloader -Documentation about the NuGet.Downloader can be found [here](src/NuGet.Downloader.Tool/README.md) +Documentation about the NuGet.Downloader can be found [here](src/NvGet.Tools.Downloader/README.md) + +## NuGet.Hierarchy + +Documentation about the NuGet.Hierarchy can be found [here](src/NvGet.Tools.Hierarchy/README.md) ## Changelog diff --git a/src/NvGet.Tools.Downloader/README.md b/src/NvGet.Tools.Downloader/README.md index a525a66..b9aa59a 100644 --- a/src/NvGet.Tools.Downloader/README.md +++ b/src/NvGet.Tools.Downloader/README.md @@ -10,7 +10,7 @@ The NuGet Downloader can be installed as a standalone .Net Core tool using the f Help can be found with : `nugetdownloader --help` -The NuGet downloader library can also be installed as a NuGet package in a UWP or .Net Standard application. +The NuGet Downloader can also be used as part of the NvGet library ## Sample commands diff --git a/src/NvGet.Tools.Hierarchy/README.md b/src/NvGet.Tools.Hierarchy/README.md new file mode 100644 index 0000000..a4acb68 --- /dev/null +++ b/src/NvGet.Tools.Hierarchy/README.md @@ -0,0 +1,15 @@ +## About + +NuGet.Hierarcy allows to display the full hierarchy of the NuGet packages for a solution. + +## Getting Started + +The NuGet Hierarcy can be installed as a standalone .Net Core tool using the following command: +`dotnet tool install -g nventive.NuGet.Hierarchy.Tool` + +Help can be found with : +`nugethierarchy --help` + +The NuGet Hierachy can also be used as part of the NvGet library. + +## Sample commands diff --git a/src/NvGet.Tools.Updater/Readme.md b/src/NvGet.Tools.Updater/Readme.md index dbf937c..1b6c915 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/src/NvGet.Tools.Updater/Readme.md @@ -10,7 +10,7 @@ The NuGet Updater can be installed as a standalone .Net Core tool using the foll Help can be found with : `nugetupdater --help` -The NuGet updater library can also be installed as a NuGet package in a UWP or .Net Standard application. +The NuGet Updater can also be used as part of the NvGet library ## Sample commands From 2b110b23a704e01d3264dbf624ebdc1fab2add97 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 12:40:59 -0500 Subject: [PATCH 171/201] Updated build definition --- .azure-pipelines.yml | 83 +++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index fba4020..ace2885 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -14,35 +14,54 @@ pr: - develop variables: -- name: PackageOutputPath - value: '$(Build.ArtifactStagingDirectory)' -- name: GeneratePackageOnBuild - value: true - -steps: -- task: GitVersion@5 - displayName: 'Calculate version' - -- task: NuGetToolInstaller@1 - displayName: 'Install NuGet 5.8.0' - inputs: - versionSpec: 5.8.0 - -- task: NuGetCommand@2 - displayName: 'Restore NuGet packages' - inputs: - command: restore - restoreSolution: src\Nuget.Updater.sln - includeNuGetOrg: true - -- task: MSBuild@1 - displayName: 'Build solution in Release | Any CPU' - inputs: - solution: src\Nuget.Updater.sln - configuration: Release - platform: 'Any CPU' - msbuildArguments: /p:PackageVersion=$(GitVersion.NuGetVersion) - -- publish: $(PackageOutputPath) - name: Packages - displayName: 'Publish NuGet packages' + NuGetArtifactName: Packages + NuGetToolVersion: 5.8.0 + +stages: +- stage: Build + jobs: + - job: Windows_Build + steps: + - task: GitVersion@5 + displayName: 'Calculate version' + + - task: MSBuild@1 + displayName: 'Build solution in Release | Any CPU' + inputs: + solution: src\NvGet.sln + configuration: Release + platform: 'Any CPU' + msbuildArguments: > + /p:PackageVersion=$(GitVersion.NuGetVersion) + /p:PackageOutputPath=$(Build.ArtifactStagingDirectory) + /p:GeneratePackageOnBuild=true + /r + + - publish: $(Build.ArtifactStagingDirectory) + artifact: $(NuGetArtifactName) + displayName: 'Publish NuGet packages' + +- stage: Release + condition: ne(variables['Build.Reason'], 'PullRequest') + jobs: + - deployment: NuGet + environment: NuGet + strategy: + runOnce: + deploy: + steps: + - download: current + artifact: $(NuGetArtifactName) + + - task: NuGetToolInstaller@1 + displayName: 'Use NuGet $(NuGetToolVersion)' + inputs: + versionSpec: $(NuGetToolVersion) + + - task: NuGetCommand@2 + displayName: 'NuGet push' + inputs: + command: push + nuGetFeedType: external + publishFeedCredentials: 'NuGet.org - nventive' + \ No newline at end of file From 730d61b56db51949ab6f530c7d2a2de17a5172e6 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 13:19:41 -0500 Subject: [PATCH 172/201] Fixed NuGet package release --- .azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index ace2885..3001150 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -64,4 +64,5 @@ stages: command: push nuGetFeedType: external publishFeedCredentials: 'NuGet.org - nventive' - \ No newline at end of file + packagesToPush: $(Pipeline.Workspace)/$(NuGetArtifactName)/*.nupkg + From 73a0c8980f2da7c3d4ae1af5717d774912b47d38 Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 14:25:43 -0500 Subject: [PATCH 173/201] Added alternate separator for the package sources --- src/NvGet/Extensions/PackageSourceExtensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/NvGet/Extensions/PackageSourceExtensions.cs b/src/NvGet/Extensions/PackageSourceExtensions.cs index 2ec3f8b..4085b0b 100644 --- a/src/NvGet/Extensions/PackageSourceExtensions.cs +++ b/src/NvGet/Extensions/PackageSourceExtensions.cs @@ -8,6 +8,7 @@ namespace NuGet.Shared.Extensions public static class PackageSourceExtensions { private const char PackageFeedInputSeparator = '|'; + private const char AlternatePackageFeedInputSeparator = ','; /// /// Transforms a input string into a package feed @@ -19,6 +20,12 @@ public static class PackageSourceExtensions public static PackageSource ToPackageSource(this string input) { var parts = input.Split(PackageFeedInputSeparator); + + //If nothing split, try with the alternate separator + if(parts.Length == 1) + { + parts = input.Split(AlternatePackageFeedInputSeparator); + } var url = parts.ElementAtOrDefault(0); var accessToken = parts.ElementAtOrDefault(1); From 176205377e8f860c4d482d28526244df4bb7c6bd Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Tue, 17 Nov 2020 14:58:39 -0500 Subject: [PATCH 174/201] Ensured source check is not done initially This will ensure the app starts faster and potential 401 errors will bubble up instead of being misinsterpreted as parsing errors --- src/NvGet/Entities/PackageFeed.cs | 24 ++++++++++++------------ src/NvGet/NvGet.csproj | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/NvGet/Entities/PackageFeed.cs b/src/NvGet/Entities/PackageFeed.cs index f0c0260..7eaf6c9 100644 --- a/src/NvGet/Entities/PackageFeed.cs +++ b/src/NvGet/Entities/PackageFeed.cs @@ -25,18 +25,18 @@ public class PackageFeed : IPackageFeed public static PackageFeed FromString(string input) => new PackageFeed(input.ToPackageSource()); - private readonly PackageMetadataResource _packageSearchMetadata; - private readonly DownloadResource _downloadResource; - private readonly PackageUpdateResource _packageUpdateResource; + private readonly Lazy _packageSearchMetadata; + private readonly Lazy _downloadResource; + private readonly Lazy _packageUpdateResource; private PackageFeed(PackageSource source) { Url = source.SourceUri; IsPrivate = source.Credentials != null; - _packageSearchMetadata = source.GetResource(); - _downloadResource = source.GetResource(); - _packageUpdateResource = source.GetResource(); + _packageSearchMetadata = new(() => source.GetResource()); + _downloadResource = new(() => source.GetResource()); + _packageUpdateResource = new(() => source.GetResource()); } public Uri Url { get; } @@ -53,7 +53,7 @@ public async Task GetPackageVersions( logMessage.AppendLine($"Retrieving package {reference.Identity.Id} from {Url}"); - var versions = await _packageSearchMetadata.GetPackageVersions(ct, reference.Identity.Id); + var versions = await _packageSearchMetadata.Value.GetPackageVersions(ct, reference.Identity.Id); logMessage.AppendLine(versions.Length > 0 ? $"Found {versions.Length} versions" : "No versions found"); @@ -76,7 +76,7 @@ public async Task> GetDependenci PackageIdentity packageIdentity ) { - var version = await _packageSearchMetadata.GetPackageVersion(ct, packageIdentity); + var version = await _packageSearchMetadata.Value.GetPackageVersion(ct, packageIdentity); if(version == null) { @@ -94,14 +94,14 @@ public async Task DownloadPackage( string downloadLocation ) { - var version = await _packageSearchMetadata.GetPackageVersion(ct, packageIdentity); + var version = await _packageSearchMetadata.Value.GetPackageVersion(ct, packageIdentity); if (version == null) //Package with this version doesn't exist in the source, skipping. { return null; } - var downloadResult = await _downloadResource.DownloadPackage(ct, packageIdentity, downloadLocation, Logger); + var downloadResult = await _downloadResource.Value.DownloadPackage(ct, packageIdentity, downloadLocation, Logger); var localPackagePath = Path.Combine(downloadLocation, $"{packageIdentity}.nupkg"); @@ -112,7 +112,7 @@ string downloadLocation public async Task PushPackage(CancellationToken ct, LocalPackage package) { - var version = await _packageSearchMetadata.GetPackageVersion(ct, package.Identity); + var version = await _packageSearchMetadata.Value.GetPackageVersion(ct, package.Identity); if(version != null) { @@ -120,7 +120,7 @@ public async Task PushPackage(CancellationToken ct, LocalPackage package) return false; } - return await _packageUpdateResource.PushPackage(ct, package, Logger); + return await _packageUpdateResource.Value.PushPackage(ct, package, Logger); } public override int GetHashCode() => Url.GetHashCode(); diff --git a/src/NvGet/NvGet.csproj b/src/NvGet/NvGet.csproj index aa812cd..275ea8d 100644 --- a/src/NvGet/NvGet.csproj +++ b/src/NvGet/NvGet.csproj @@ -3,7 +3,7 @@ netstandard20;net472 $(NoWarn);CA1068;NU1701 true - 7.2 + 9.0 From 7e4ffc980e999f2422f97d7c6321b4ff4b1ac48e Mon Sep 17 00:00:00 2001 From: Benjamin Cartier Date: Thu, 25 Feb 2021 16:24:37 -0500 Subject: [PATCH 175/201] Bumped target framework to net50 --- src/NvGet.Tests/NvGet.Tests.csproj | 2 +- src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj | 2 +- src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj | 2 +- src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NvGet.Tests/NvGet.Tests.csproj b/src/NvGet.Tests/NvGet.Tests.csproj index 68bc5c1..027a0ae 100644 --- a/src/NvGet.Tests/NvGet.Tests.csproj +++ b/src/NvGet.Tests/NvGet.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 false diff --git a/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj index 2654a25..fbf11f7 100644 --- a/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj +++ b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 true true nugetdownloader diff --git a/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj index ca17ab6..79f708d 100644 --- a/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj +++ b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 true true nugethierarchy diff --git a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index 0cc0ca9..107e1c8 100644 --- a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5.0 true true nugetupdater From 6cd23af4a5a2624c069f729f52639d4c35e0fcf3 Mon Sep 17 00:00:00 2001 From: Carl Mathieu Date: Wed, 19 Oct 2022 17:21:26 -0400 Subject: [PATCH 176/201] chore: bump nuget.protocol because of security issue --- src/NvGet/NvGet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NvGet/NvGet.csproj b/src/NvGet/NvGet.csproj index 275ea8d..d89e5bd 100644 --- a/src/NvGet/NvGet.csproj +++ b/src/NvGet/NvGet.csproj @@ -20,7 +20,7 @@ - + From 3de0eb5c73b091584e2a074777cea6b974bec625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 23 Nov 2022 10:27:27 -0500 Subject: [PATCH 177/201] ci: Enable github actions --- .github/workflows/main.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..26ce28e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: + - develop + - master + + pull_request: + types: [opened, synchronize, reopened] + branches: + - develop + - master + +jobs: + build: + name: Build + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 From b3946e29a3a05f16493d686c0c3f84279c45b98f Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 23 Nov 2022 09:51:13 -0500 Subject: [PATCH 178/201] feat: Add support for Directory.Packages.props --- src/NvGet/Contracts/FileType.cs | 7 ++++++- src/NvGet/Extensions/FileTypeExtensions.cs | 2 ++ src/NvGet/Extensions/XmlDocumentExtensions.cs | 3 ++- src/NvGet/Helpers/SolutionHelper.cs | 10 ++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/NvGet/Contracts/FileType.cs b/src/NvGet/Contracts/FileType.cs index 90bead9..3baa575 100644 --- a/src/NvGet/Contracts/FileType.cs +++ b/src/NvGet/Contracts/FileType.cs @@ -33,9 +33,14 @@ public enum FileType /// DirectoryTargets = 16, + /// + /// Directory.Packages.props files. + /// + CentralPackageManagement = 32, + /// /// All the supported file types. /// - All = Nuspec | Csproj | DirectoryProps | DirectoryTargets, + All = Nuspec | Csproj | DirectoryProps | DirectoryTargets | CentralPackageManagement, } } diff --git a/src/NvGet/Extensions/FileTypeExtensions.cs b/src/NvGet/Extensions/FileTypeExtensions.cs index 2151c62..3265022 100644 --- a/src/NvGet/Extensions/FileTypeExtensions.cs +++ b/src/NvGet/Extensions/FileTypeExtensions.cs @@ -18,6 +18,8 @@ public static string GetDescription(this FileType target) return "Directory.Build.targets"; case FileType.DirectoryTargets: return "Directory.Build.props"; + case FileType.CentralPackageManagement: + return "Directory.Packages.props"; default: return default; } diff --git a/src/NvGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs index 88494cb..173120b 100644 --- a/src/NvGet/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Extensions/XmlDocumentExtensions.cs @@ -36,8 +36,9 @@ public static PackageIdentity[] GetPackageReferences(this XmlDocument document) var packageReferences = document.SelectElements("PackageReference"); var dotnetCliReferences = document.SelectElements("DotNetCliToolReference"); + var packageVersionReferences = document.SelectElements("PackageVersion"); - foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) + foreach(var packageReference in packageReferences.Concat(dotnetCliReferences).Concat(packageVersionReferences)) { var packageId = new[] { "Include", "Update", "Remove" } .Select(packageReference.GetAttribute) diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 45ba7f0..e360a2a 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -59,6 +59,16 @@ ILogger log } } + if(fileType.HasFlag(FileType.CentralPackageManagement)) + { + const FileType currentTarget = FileType.CentralPackageManagement; + + foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) + { + packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + } + } + if(fileType.HasFlag(FileType.Nuspec)) { foreach(var f in await GetNuspecFiles(ct, solutionPath, log)) From 89b10d8a75e98b460007e4fdce8e48bd7672e9ca Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 23 Nov 2022 09:52:18 -0500 Subject: [PATCH 179/201] chore: Add net6.0 and net7.0 support --- .azure-pipelines.yml | 2 +- src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 3001150..9a39a6f 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -1,4 +1,4 @@ -pool: 'Hosted Windows 2019 with VS2019' +pool: 'windows-2022' trigger: batch: 'true' diff --git a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index 107e1c8..b34e691 100644 --- a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -2,12 +2,13 @@ Exe - net5.0 + net5.0;net6.0;net7.0 true true nugetupdater latest $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + major From c054aee004fe6b78c138092ef8cfde72209d118c Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 23 Nov 2022 10:25:14 -0500 Subject: [PATCH 180/201] feat: Add multiple authors support --- src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs | 6 +++--- src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs | 2 +- src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs | 4 ++-- .../Updater/Extensions/UpdaterParametersExtension.cs | 10 +++++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs b/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs index c030d9a..fd3515f 100644 --- a/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs +++ b/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs @@ -96,8 +96,8 @@ public void Given_ContextArgument_ContextPropertyIsSet(string propertyName, stri [DataTestMethod] [DataRow(nameof(UpdaterParameters.SolutionRoot), "--solution=" + NotExistingFilePath, NotExistingFilePath)] [DataRow(nameof(UpdaterParameters.SolutionRoot), "-s=" + NotExistingFilePath, NotExistingFilePath)] - [DataRow(nameof(UpdaterParameters.PackageAuthor), "--packageAuthor=" + SomeText, SomeText)] - [DataRow(nameof(UpdaterParameters.PackageAuthor), "-a=" + SomeText, SomeText)] + [DataRow(nameof(UpdaterParameters.PackageAuthors), "--packageAuthor=" + SomeText, SomeText)] + [DataRow(nameof(UpdaterParameters.PackageAuthors), "-a=" + SomeText, SomeText)] [DataRow(nameof(UpdaterParameters.IsDowngradeAllowed), "--allowDowngrade", true)] [DataRow(nameof(UpdaterParameters.IsDowngradeAllowed), "-d", true)] [DataRow(nameof(UpdaterParameters.Strict), "--strict", true)] @@ -107,7 +107,7 @@ public void Given_UpdaterParametersArgument_UpdaterParametersPropertyIsSet(strin Func propertySelector = propertyName switch { nameof(UpdaterParameters.SolutionRoot) => x => x.SolutionRoot, - nameof(UpdaterParameters.PackageAuthor) => x => x.PackageAuthor, + nameof(UpdaterParameters.PackageAuthors) => x => x.PackageAuthors, nameof(UpdaterParameters.IsDowngradeAllowed) => x => x.IsDowngradeAllowed, nameof(UpdaterParameters.Strict) => x => x.Strict, nameof(UpdaterParameters.IsDryRun) => x => x.IsDryRun, diff --git a/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs index 6274c71..b8c43ea 100644 --- a/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs @@ -44,7 +44,7 @@ internal static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) { "version=|versions=|v=", "The target {version} to use; latest stable is always considered; can be specified multiple times", TrySet(x => context.Parameters.TargetVersions.Add(x)) }, { "ignorePackages=|ignore=|i=", "A specific {package} to ignore; can be specified multiple times", TrySet(x => context.Parameters.PackagesToIgnore.Add(x)) }, { "updatePackages=|update=|u=", "A specific {package} to update; not specifying this will update all packages found; can be specified multiple times", TrySet(x => context.Parameters.PackagesToUpdate.Add(x)) }, - { "packageAuthor=|a=", "The {author} of the packages to update; used for public packages only", TrySet(x => context.Parameters.PackageAuthor = x)}, + { "packageAuthor=|packageAuthors=|a=", "A comma separated list of {authors} of the packages to update; used for public packages only", TrySet(x => context.Parameters.PackageAuthors = x)}, { "outputFile=|of=", "The {path} to a markdown file where the update summary will be written", TrySet(x => context.SummaryFile = x) }, { "allowDowngrade|d", "Whether package downgrade is allowed", TrySet(x => context.Parameters.IsDowngradeAllowed = true)}, { "useNuGetorg|n", "Whether to use packages from NuGet.org", TrySet(_ => context.Parameters.Feeds.Add(PackageFeed.NuGetOrg)) }, diff --git a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs index af098cc..87628a9 100644 --- a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs +++ b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs @@ -51,9 +51,9 @@ public class UpdaterParameters public ICollection PackagesToUpdate { get; } = new List(); /// - /// Gets or sets the name of the author of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. + /// Gets or sets the name of the authors of the packages to update; used with NuGet.org; packages from private feeds are assumed to be required. Multiple authors can be provided with a comma separated list. /// - public string PackageAuthor { get; set; } + public string PackageAuthors { get; set; } /// /// Gets the version range overrides for specific packages. diff --git a/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs b/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs index 84f9eae..96b3048 100644 --- a/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs +++ b/src/NvGet/Tools/Updater/Extensions/UpdaterParametersExtension.cs @@ -35,9 +35,9 @@ internal static IEnumerable GetSummary(this UpdaterParameters parameters yield return $"- Fetching packages from {MarkdownHelper.CodeBlocksEnumeration(parameters.Feeds.Select(s => s.Url.OriginalString))}"; } - if(parameters.PackageAuthor.HasValue()) + if(parameters.PackageAuthors.HasValue()) { - yield return $"- Limiting to public packages authored by {MarkdownHelper.Bold(parameters.PackageAuthor)}"; + yield return $"- Limiting to public packages authored by {MarkdownHelper.Bold(parameters.PackageAuthors)}"; } yield return $"- Using {MarkdownHelper.CodeBlocksEnumeration(parameters.TargetVersions)} versions {(parameters.Strict ? "(exact match)" : "")}"; @@ -96,9 +96,13 @@ PackageReference reference return new FeedVersion(manualVersion.range.MinVersion); } + var authors = parameters.PackageAuthors?.Split(',') is { Length: > 0 } value + ? value + : new string[] { null }; // Invoke GetPackageVersions with a null parameter + var availableVersions = await Task.WhenAll(parameters .Feeds - .Select(f => f.GetPackageVersions(ct, reference, parameters.PackageAuthor)) + .SelectMany(f => authors.Select(author => f.GetPackageVersions(ct, reference, author))) ); var versionsPerTarget = availableVersions From 7810c77982e1be80ffb0f80524e2820632cf1395 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 23 Nov 2022 10:33:10 -0500 Subject: [PATCH 181/201] ci: Move to github actions --- .azure-pipelines.yml | 68 -------------------------------------- .github/workflows/main.yml | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 68 deletions(-) delete mode 100644 .azure-pipelines.yml diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml deleted file mode 100644 index 9a39a6f..0000000 --- a/.azure-pipelines.yml +++ /dev/null @@ -1,68 +0,0 @@ -pool: 'windows-2022' - -trigger: - batch: 'true' - branches: - include: - - master - - develop - -pr: - branches: - include: - - master - - develop - -variables: - NuGetArtifactName: Packages - NuGetToolVersion: 5.8.0 - -stages: -- stage: Build - jobs: - - job: Windows_Build - steps: - - task: GitVersion@5 - displayName: 'Calculate version' - - - task: MSBuild@1 - displayName: 'Build solution in Release | Any CPU' - inputs: - solution: src\NvGet.sln - configuration: Release - platform: 'Any CPU' - msbuildArguments: > - /p:PackageVersion=$(GitVersion.NuGetVersion) - /p:PackageOutputPath=$(Build.ArtifactStagingDirectory) - /p:GeneratePackageOnBuild=true - /r - - - publish: $(Build.ArtifactStagingDirectory) - artifact: $(NuGetArtifactName) - displayName: 'Publish NuGet packages' - -- stage: Release - condition: ne(variables['Build.Reason'], 'PullRequest') - jobs: - - deployment: NuGet - environment: NuGet - strategy: - runOnce: - deploy: - steps: - - download: current - artifact: $(NuGetArtifactName) - - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet $(NuGetToolVersion)' - inputs: - versionSpec: $(NuGetToolVersion) - - - task: NuGetCommand@2 - displayName: 'NuGet push' - inputs: - command: push - nuGetFeedType: external - publishFeedCredentials: 'NuGet.org - nventive' - packagesToPush: $(Pipeline.Workspace)/$(NuGetArtifactName)/*.nupkg - diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26ce28e..1893457 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,3 +21,66 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '5.0.x' + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '7.0.100' + + - name: Setup GitVersion + uses: gittools/actions/gitversion/setup@v0.9.9 + with: + versionSpec: '5.6.x' + + - name: GitVersion + id: gitversion + uses: gittools/actions/gitversion/execute@v0.9.9 + with: + useConfigFile: true + configFilePath: gitversion.yml + + - name: Build - CI + run: | + $adjustedPackageVersion="${{ steps.gitversion.outputs.semVer }}".ToLower(); + dotnet build src/NvGet.sln /p:PackageVersion=$adjustedPackageVersion /p:Version=${{ steps.gitversion.outputs.assemblySemVer }} "/p:PackageOutputPath=$env:GITHUB_WORKSPACE\artifacts" /p:GeneratePackageOnBuild=true + + - name: Run Unit Tests + run: | + cd src + dotnet test + + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: NuGet + path: .\artifacts + + publish: + name: Publish + if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/master')) }} + runs-on: windows-latest + needs: + - build + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Download Artifacts + uses: actions/download-artifact@v2 + with: + name: NuGet + path: artifacts + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + + - name: NuGet Push + run: | + dotnet nuget push artifacts\*.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_ORG_API_KEY }} From 5bb0d80bdce1847c37fcaa5f7870745fd1539c01 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 23 Nov 2022 10:47:55 -0500 Subject: [PATCH 182/201] ci: Adjust solution --- src/NvGet.sln | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/NvGet.sln b/src/NvGet.sln index d4f540b..c785807 100644 --- a/src/NvGet.sln +++ b/src/NvGet.sln @@ -1,11 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29021.104 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33103.201 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0547178F-3639-4797-940F-265958B7FECA}" ProjectSection(SolutionItems) = preProject - ..\.azure-pipelines.yml = ..\.azure-pipelines.yml ..\.editorconfig = ..\.editorconfig ..\.gitignore = ..\.gitignore ..\CHANGELOG.md = ..\CHANGELOG.md @@ -34,11 +33,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvGet.Tools.Hierarchy", "NvGet.Tools.Hierarchy\NvGet.Tools.Hierarchy.csproj", "{464BC350-3EE3-4D03-B34F-BDC53D6E0FAE}" EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{3daa81db-00a6-4a84-8180-19a588398f52}*SharedItemsImports = 5 - NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{4c395d24-0028-47ef-b07e-5566344e78de}*SharedItemsImports = 13 - NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{d240abfd-6a48-4e4b-a946-9ebf9840fada}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -76,4 +70,9 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1C528FB3-4BD1-4D56-ABEE-A20F6367040F} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{3daa81db-00a6-4a84-8180-19a588398f52}*SharedItemsImports = 5 + NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{4c395d24-0028-47ef-b07e-5566344e78de}*SharedItemsImports = 13 + NvGet.Tools.Shared\NvGet.Tools.Shared.projitems*{d240abfd-6a48-4e4b-a946-9ebf9840fada}*SharedItemsImports = 5 + EndGlobalSection EndGlobal From ef237dd412d69b74848b4bccf2a41c31216b6b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 23 Nov 2022 11:42:23 -0500 Subject: [PATCH 183/201] docs: Update for multiple authors updates --- src/NvGet.Tools.Updater/Readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NvGet.Tools.Updater/Readme.md b/src/NvGet.Tools.Updater/Readme.md index 1b6c915..3c1b076 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/src/NvGet.Tools.Updater/Readme.md @@ -43,6 +43,11 @@ nugetupdater -s=MySolution.sln --feed=https://pkgs.dev.azure.com/account/_packag nugetupdater -s=MySolution.sln -n --packageAuthor=nventive --ignore=PackageA -i=PackageB ``` +- Update packages from `nventive` and `microsoft` from NuGet.org, except for `PackageA` and `PackageB` +``` +nugetupdater -s=MySolution.sln -n --packageAuthor=nventive,microsoft --ignore=PackageA -i=PackageB +``` + - Update only `PackageA` and `PackageB` from NuGet.org and a private feed ``` nugetupdater -s=MySolution.sln -n -f=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --update=PackageA -u=PackageB From 4532d4c37ee974cafa357b5b9f2719a74e578107 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 11:38:55 +0000 Subject: [PATCH 184/201] Bump Newtonsoft.Json from 12.0.3 to 13.0.2 in /src/NvGet.Tools.Updater Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 12.0.3 to 13.0.2. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/12.0.3...13.0.2) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index b34e691..88c1c99 100644 --- a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -38,7 +38,7 @@ - + From e9fd2583b7cd071c39257f20419d438980ea6eb0 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Mon, 1 May 2023 00:17:10 +1000 Subject: [PATCH 185/201] fix: Handle CPM files --- src/NvGet/Helpers/SolutionHelper.cs | 2 +- src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs | 3 ++- src/NvGet/Tools/Updater/NuGetUpdater.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index e360a2a..af1adb4 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -171,7 +171,7 @@ private static async Task GetFileReferences(CancellationToke var document = await file.LoadDocument(ct); var references = Array.Empty(); - if(target.HasAnyFlag(FileType.Csproj, FileType.DirectoryProps, FileType.DirectoryTargets)) + if(target.HasAnyFlag(FileType.Csproj, FileType.DirectoryProps, FileType.DirectoryTargets, FileType.CentralPackageManagement)) { references = document.GetPackageReferences(); } diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index 4e3556b..7a5dc43 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -33,8 +33,9 @@ UpdateOperation operation var packageReferences = document.SelectElements("PackageReference", $"[@Include='{packageId}' or @Update='{packageId}']"); var dotnetCliReferences = document.SelectElements("DotNetCliToolReference", $"[@Include='{packageId}' or @Update='{packageId}']"); + var packageVersions = document.SelectElements("PackageVersion", $"[@Include='{packageId}' or @Update='{packageId}']"); - foreach(var packageReference in packageReferences.Concat(dotnetCliReferences)) + foreach(var packageReference in packageReferences.Concat(dotnetCliReferences).Concat(packageVersions)) { var packageVersion = packageReference.GetAttributeOrChild("Version"); diff --git a/src/NvGet/Tools/Updater/NuGetUpdater.cs b/src/NvGet/Tools/Updater/NuGetUpdater.cs index 13855cc..93c9779 100644 --- a/src/NvGet/Tools/Updater/NuGetUpdater.cs +++ b/src/NvGet/Tools/Updater/NuGetUpdater.cs @@ -170,7 +170,7 @@ Dictionary documents { updates = document.UpdateDependencies(currentOperation); } - else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj)) + else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj, FileType.CentralPackageManagement)) { updates = document.UpdatePackageReferences(currentOperation); } From d5c72e74762210f40c549c32efeaa3803c476623 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Mon, 12 Jun 2023 09:27:25 -0400 Subject: [PATCH 186/201] feat: Add support for multipart pre-release tags --- src/NvGet.Tests/Constants.cs | 2 +- .../Tools/ConsoleArgsParserTests.cs | 6 +-- .../Tools/Updater/UpdaterParametersTests.cs | 48 +++++++++++++++++++ src/NvGet/Extensions/FeedVersionExtensions.cs | 19 +++++++- 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/NvGet.Tests/Constants.cs b/src/NvGet.Tests/Constants.cs index fa755bd..e7af79a 100644 --- a/src/NvGet.Tests/Constants.cs +++ b/src/NvGet.Tests/Constants.cs @@ -12,7 +12,7 @@ public static class Constants public static readonly Dictionary TestPackages = new Dictionary { {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, - {"Uno.UI", new[] { "2.1.39", "2.2.0", "2.3.0-dev.44", "2.3.0-dev.48", "2.3.0-dev.58" } }, + {"Uno.UI", new[] { "2.1.39", "2.2.0", "2.3.0-dev.44", "2.3.0-dev.48", "2.3.0-dev.58", "5.0.0-feature.5x.88" } }, }; public static readonly TestPackageFeed TestFeed = new TestPackageFeed(TestFeedUri, TestPackages); diff --git a/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs b/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs index fd3515f..c8e0e2e 100644 --- a/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs +++ b/src/NvGet.Tests/Tools/ConsoleArgsParserTests.cs @@ -19,7 +19,7 @@ public class ConsoleArgsParserTests private const string SomePublicFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json"; private const string SomePrivateFeed = "https://pkgs.dev.azure.com/qwe/_packaging/asd/nuget/v3/index.json|hunter2"; private const string PinnedVersionJsonPath = @"Resources\version_overrides.json"; - + [TestMethod] public void Given_HelpArgument_ContextIsHelp() { @@ -161,10 +161,10 @@ public void Given_UpdaterParametersArgument_ContextCollectionPropertyIsSet(Expre [DeploymentItem(PinnedVersionJsonPath)] public void Given_UpdaterParametersArgument_ContextTargetVersionIsSet() { - var arguments = new[] { "--versionOverrides=" + PinnedVersionJsonPath }; + var arguments = new[] { $"--versionOverrides={PinnedVersionJsonPath}" }; var context = ConsoleArgsContext.Parse(arguments); - Assert.IsFalse(context.HasError); + Assert.IsFalse(context.HasError, context.Errors.FirstOrDefault()?.Exception?.Message); var actualValues = (ICollection)context.Parameters.VersionOverrides; var expectedValues = ConsoleArgsContext.LoadOverrides(PinnedVersionJsonPath); diff --git a/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs b/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs index 13fd469..2fe01e5 100644 --- a/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs +++ b/src/NvGet.Tests/Tools/Updater/UpdaterParametersTests.cs @@ -11,6 +11,54 @@ namespace NvGet.Tests.Tools.Updater [TestClass] public class UpdaterParametersTests { + [TestMethod] + public async Task GivenMultipartTag_CorrectVersionsAreResolved() + { + var reference = new PackageReference("Uno.UI", "2.1.39"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "feature.5x" }, + Feeds = { Constants.TestFeed }, + }; + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(NuGetVersion.Parse("5.0.0-feature.5x.88"), version.Version); + } + + [TestMethod] + public async Task GivenMultiPartTag_NoOverride() + { + var reference = new PackageReference("Uno.UI", "5.0.0-feature.5x.89"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "feature.5x" }, + Feeds = { Constants.TestFeed }, + }; + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(NuGetVersion.Parse("5.0.0-feature.5x.88"), version.Version); + } + + [TestMethod] + public async Task GivenSinglePartTag_NoOverride() + { + var reference = new PackageReference("Uno.UI", "2.1.39"); + + var parameters = new UpdaterParameters + { + TargetVersions = { "dev" }, + Feeds = { Constants.TestFeed }, + }; + + var version = await parameters.GetLatestVersion(CancellationToken.None, reference); + + Assert.AreEqual(NuGetVersion.Parse("2.3.0-dev.58"), version.Version); + } + [TestMethod] public async Task GivenRangeOverrides_CorrectVersionsAreResolved() { diff --git a/src/NvGet/Extensions/FeedVersionExtensions.cs b/src/NvGet/Extensions/FeedVersionExtensions.cs index 6c2c92f..93dfc48 100644 --- a/src/NvGet/Extensions/FeedVersionExtensions.cs +++ b/src/NvGet/Extensions/FeedVersionExtensions.cs @@ -23,9 +23,26 @@ bool isStrict var hasTag = ContainsTag(releaseLabels, tag); - return isStrict + var isMatch = isStrict ? releaseLabels?.Count() == 2 && hasTag // Check strictly for packages with versions "dev.XXXX" : hasTag; // Allow packages with versions "dev.XXXX.XXXX" + + if(isMatch) + { + return true; + } + + // Search for multipart tags, such as `feature.test` + var tagDotCount = tag.Count(c => c == '.') + 1; + + if (!isMatch && tagDotCount > 1) + { + var multiPart = string.Join(".", releaseLabels.Take(tagDotCount)); + + return ContainsTag(new[] { multiPart }, tag); + } + + return false; } private static bool ContainsTag(IEnumerable releaseLabels, string tag) From fb522913c84a6adca322d46072be183b5e21e22e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:47:25 +0000 Subject: [PATCH 187/201] Bump NuGet.Protocol from 5.9.3 to 6.0.5 in /src/NvGet Bumps [NuGet.Protocol](https://github.com/NuGet/NuGet.Client) from 5.9.3 to 6.0.5. - [Release notes](https://github.com/NuGet/NuGet.Client/releases) - [Commits](https://github.com/NuGet/NuGet.Client/commits) --- updated-dependencies: - dependency-name: NuGet.Protocol dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/NvGet/NvGet.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NvGet/NvGet.csproj b/src/NvGet/NvGet.csproj index d89e5bd..1061715 100644 --- a/src/NvGet/NvGet.csproj +++ b/src/NvGet/NvGet.csproj @@ -1,4 +1,4 @@ - + netstandard20;net472 $(NoWarn);CA1068;NU1701 @@ -20,7 +20,7 @@ - + From 662db0ebf697e27e523a58403228ffa843c6e0d2 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 30 Jul 2023 12:06:33 +0300 Subject: [PATCH 188/201] fix: Terminate with non-zero exit code on failure --- src/NvGet.Tools.Updater/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NvGet.Tools.Updater/Program.cs b/src/NvGet.Tools.Updater/Program.cs index ddc2402..9c35ca6 100644 --- a/src/NvGet.Tools.Updater/Program.cs +++ b/src/NvGet.Tools.Updater/Program.cs @@ -53,6 +53,7 @@ public static async Task Main(string[] args) { Console.Error.WriteLine($"Failed to update nuget packages: {ex.Message}"); Console.Error.WriteLine($"{ex}"); + Environment.Exit(-1); } } From 3bbbccfba8508781421d8847ceec8a4da24942f3 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Tue, 1 Aug 2023 17:49:42 +1000 Subject: [PATCH 189/201] feat: Adding support for setting project properties file for updating project properties --- .../Resources/project_properties.json | 4 ++ .../Arguments/ConsoleArgsContext.cs | 38 +++++++++++++++- src/NvGet/Extensions/XmlDocumentExtensions.cs | 29 ++++++++++++ src/NvGet/Helpers/SolutionHelper.cs | 19 ++++---- .../Updater/Entities/UpdaterParameters.cs | 5 +++ .../Extensions/XmlDocumentExtensions.cs | 44 ++++++++++++++++++- src/NvGet/Tools/Updater/NuGetUpdater.cs | 6 ++- 7 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 src/NvGet.Tests/Resources/project_properties.json diff --git a/src/NvGet.Tests/Resources/project_properties.json b/src/NvGet.Tests/Resources/project_properties.json new file mode 100644 index 0000000..ad5af23 --- /dev/null +++ b/src/NvGet.Tests/Resources/project_properties.json @@ -0,0 +1,4 @@ +[ + { "PropertyName": "UnoExtensionsVersion", "PackageId": "Uno.Extensions.Core" }, + { "PropertyName": "UnoVersion", "PackageId": "Uno.WinUI" } +] diff --git a/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs index b8c43ea..b6e30cd 100644 --- a/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs @@ -53,13 +53,14 @@ internal static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) { "dryrun", "Runs the updater but doesn't write the updates to files.", TrySet(_ => context.Parameters.IsDryRun = true) }, { "result|r=", "The path to the file where the update result should be saved.", TrySet(x => context.ResultFile = x) }, { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", TryParseAndSet(LoadOverrides, x => context.Parameters.VersionOverrides.AddRange(x)) }, + { "projectProperties=", "The path to a JSON file that lists pairs of csproj property names and corresponding package Id, so that updater can update project properties as necessary", TryParseAndSet(LoadProjectProperties, x => context.Parameters.ProjectProperties.AddRange(x)) }, }; Action TrySet(Action set) { return value => { - if (context != null) + if(context != null) { try { @@ -142,5 +143,40 @@ async Task> LoadFromStreamAsync() } } } + + internal static IEnumerable<(string PropertyName, string PackageId)> LoadProjectProperties(string inputPathOrUrl) + { + var results = + LoadFromStreamAsync() + .GetAwaiter() + .GetResult(); + return results; + + async Task> LoadFromStreamAsync() + { + var jsonSerializer = JsonSerializer.CreateDefault(); + + if(inputPathOrUrl.StartsWith("http://") || inputPathOrUrl.StartsWith("https://")) + { + using(var httpClient = new HttpClient()) + using(var stream = await httpClient.GetStreamAsync(inputPathOrUrl)) + using(var jsonTextReader = new JsonTextReader(new StreamReader(stream, Encoding.UTF8))) + { + return jsonSerializer.Deserialize>(jsonTextReader); + } + } + else + { + using(var jsonTextReader = new JsonTextReader(File.OpenText(inputPathOrUrl))) + { + return jsonSerializer.Deserialize>(jsonTextReader).Select(x => (x.PropertyName, x.PackageId)); + } + } + } + } } + +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + public record PackageProp(string PropertyName, string PackageId); +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter } diff --git a/src/NvGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs index 173120b..370d198 100644 --- a/src/NvGet/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Extensions/XmlDocumentExtensions.cs @@ -25,6 +25,35 @@ namespace NvGet.Extensions { public static class XmlDocumentExtensions { + /// + /// Retrieves the ProjectProperties that need updating from the given XmlDocument. + /// + /// + /// A Dictionary where the key is the id of a package and the value its version. + public static PackageIdentity[] GetProjectProperties(this XmlDocument document, ICollection<(string PropertyName, string PackageId)> projectProperties) + { + var references = new List(); + + foreach(var prop in projectProperties) + { + var docProp = document.SelectElements(prop.PropertyName).FirstOrDefault(); + if(docProp is null) + { + continue; + } + + var docPropIdentity = CreatePackageIdentity(prop.PackageId, docProp.InnerText); + if(docPropIdentity is not null) + { + references.Add(docPropIdentity); + } + } + + return references + .Trim() + .ToArray(); + } + /// /// Retrieves the PackageReferences from the given XmlDocument. If a package is present multiple time, only the first version will be returned. /// diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index af1adb4..25f622e 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -24,9 +24,12 @@ public static async Task GetPackageReferences( CancellationToken ct, string solutionPath, FileType fileType, - ILogger log + ILogger log, + ICollection<(string PropertyName, string PackageId)>? projectProperties = default ) { + projectProperties??= Array.Empty<(string PropertyName, string PackageId)>(); + log.LogInformation($"Retrieving references from files in {solutionPath}"); var packages = new List(); @@ -35,7 +38,7 @@ ILogger log { foreach(var f in await GetProjectFiles(ct, solutionPath, log)) { - packages.AddRange(await GetFileReferences(ct, f, FileType.Csproj)); + packages.AddRange(await GetFileReferences(ct, f, FileType.Csproj, projectProperties)); } } @@ -45,7 +48,7 @@ ILogger log foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) { - packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + packages.AddRange(await GetFileReferences(ct, file, currentTarget, projectProperties)); } } @@ -55,7 +58,7 @@ ILogger log foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) { - packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + packages.AddRange(await GetFileReferences(ct, file, currentTarget, projectProperties)); } } @@ -65,7 +68,7 @@ ILogger log foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) { - packages.AddRange(await GetFileReferences(ct, file, currentTarget)); + packages.AddRange(await GetFileReferences(ct, file, currentTarget, projectProperties)); } } @@ -73,7 +76,7 @@ ILogger log { foreach(var f in await GetNuspecFiles(ct, solutionPath, log)) { - packages.AddRange(await GetFileReferences(ct, f, FileType.Nuspec)); + packages.AddRange(await GetFileReferences(ct, f, FileType.Nuspec, projectProperties)); } } @@ -161,7 +164,7 @@ private static async Task GetNuspecFiles(CancellationToken ct, string return files; } - private static async Task GetFileReferences(CancellationToken ct, string file, FileType target) + private static async Task GetFileReferences(CancellationToken ct, string file, FileType target, ICollection<(string PropertyName, string PackageId)> projectProperties) { if(file.IsNullOrEmpty()) { @@ -173,7 +176,7 @@ private static async Task GetFileReferences(CancellationToke if(target.HasAnyFlag(FileType.Csproj, FileType.DirectoryProps, FileType.DirectoryTargets, FileType.CentralPackageManagement)) { - references = document.GetPackageReferences(); + references = document.GetPackageReferences().Concat(document.GetProjectProperties(projectProperties)).ToArray(); } else if(target.HasFlag(FileType.Nuspec)) { diff --git a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs index 87628a9..db655bf 100644 --- a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs +++ b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs @@ -60,6 +60,11 @@ public class UpdaterParameters /// public IDictionary VersionOverrides { get; } = new Dictionary(); + /// + /// Gets the csproj properties that should be updated for corresponding package. + /// + public ICollection<(string PropertyName, string PackageId)> ProjectProperties { get; } = new List<(string PropertyNaem, string PackageId)>(); + /// /// Gets or sets a value indicating whether to actually write the updates to the files. /// diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index 7a5dc43..4fd5eaf 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -16,6 +16,46 @@ namespace NvGet.Tools.Updater.Extensions { public static class XmlDocumentExtensions { + /// + /// Runs an on Project Properties contained in a . + /// + /// + /// + /// + public static IEnumerable UpdateProjectProperties( + this XmlDocument document, + UpdateOperation operation, + ICollection<(string PropertyName, string PackageId)> projectProperties) + { + var operations = new List(); + + var packageId = operation.PackageId; + + foreach(var prop in projectProperties.Where(x=>x.PackageId==operation.PackageId)) + { + var docProp = document.SelectElements(prop.PropertyName).FirstOrDefault(); + if(docProp is null) + { + continue; + } + + var packageVersion = docProp.InnerText; + if(packageVersion is { Length: > 0 }) + { + var currentOperation = operation.WithPreviousVersion(packageVersion); + + if(currentOperation.ShouldProceed()) + { + docProp.InnerText = currentOperation.UpdatedVersion.ToString(); + } + + operations.Add(currentOperation); + } + } + + return operations; + } + /// /// Runs an on the PackageReferences contained in a . /// @@ -88,7 +128,7 @@ private static void SetAttributeOrChild(this XmlElement element, string name, st if(node != null) { node.InnerText = value; - } + } } } @@ -113,7 +153,7 @@ UpdateOperation operation if(!versionNodeValue.Contains("{", System.StringComparison.OrdinalIgnoreCase)) { var currentOperation = operation.WithPreviousVersion(versionNodeValue); - + if(currentOperation.ShouldProceed()) { node.SetAttribute("version", currentOperation.UpdatedVersion.ToString()); diff --git a/src/NvGet/Tools/Updater/NuGetUpdater.cs b/src/NvGet/Tools/Updater/NuGetUpdater.cs index 93c9779..ca51565 100644 --- a/src/NvGet/Tools/Updater/NuGetUpdater.cs +++ b/src/NvGet/Tools/Updater/NuGetUpdater.cs @@ -108,7 +108,7 @@ public async Task> UpdatePackages(CancellationToken ct internal async Task GetPackages(CancellationToken ct) { var packages = new List(); - var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget, _log); + var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget, _log, _parameters.ProjectProperties); _log.Write($"Found {references.Length} references"); @@ -123,7 +123,7 @@ internal async Task GetPackages(CancellationToken ct) (_parameters.PackagesToUpdate.Any() && !_parameters.PackagesToUpdate.Contains(reference.Identity.Id)) ) { - _log.Write(new UpdateOperation(reference.Identity, isIgnored: true)); + _log.Write(new UpdateOperation(reference.Identity, isIgnored: true)); continue; } @@ -173,6 +173,8 @@ Dictionary documents else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj, FileType.CentralPackageManagement)) { updates = document.UpdatePackageReferences(currentOperation); + var propertyUpdates = document.UpdateProjectProperties(currentOperation, _parameters.ProjectProperties); + updates = updates.Concat(propertyUpdates); } if(!_parameters.IsDryRun && updates.Any(u => u.ShouldProceed())) From ae225eef5c19e1aebb54927a33c20b2e7571e6aa Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Tue, 1 Aug 2023 23:06:36 +1000 Subject: [PATCH 190/201] chore: tidyup Co-authored-by: Youssef Victor --- src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index 4fd5eaf..acc22a5 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -31,7 +31,7 @@ public static IEnumerable UpdateProjectProperties( var packageId = operation.PackageId; - foreach(var prop in projectProperties.Where(x=>x.PackageId==operation.PackageId)) + foreach(var prop in projectProperties.Where(x=> x.PackageId == packageId)) { var docProp = document.SelectElements(prop.PropertyName).FirstOrDefault(); if(docProp is null) From 9fa48dca29f2dd6066271d323627b67402bcb3a3 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Tue, 1 Aug 2023 23:06:48 +1000 Subject: [PATCH 191/201] chore: tidyup Co-authored-by: Youssef Victor --- src/NvGet/Helpers/SolutionHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 25f622e..58f4464 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -28,7 +28,7 @@ public static async Task GetPackageReferences( ICollection<(string PropertyName, string PackageId)>? projectProperties = default ) { - projectProperties??= Array.Empty<(string PropertyName, string PackageId)>(); + projectProperties ??= Array.Empty<(string PropertyName, string PackageId)>(); log.LogInformation($"Retrieving references from files in {solutionPath}"); From 07261b7aaad7653e5b72406c70c7988e30c1c6d8 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Wed, 2 Aug 2023 00:01:55 +1000 Subject: [PATCH 192/201] docs: Adding project properties example to readme --- src/NvGet.Tools.Updater/Readme.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/NvGet.Tools.Updater/Readme.md b/src/NvGet.Tools.Updater/Readme.md index 3c1b076..8bba363 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/src/NvGet.Tools.Updater/Readme.md @@ -62,7 +62,7 @@ nugetupdater -s=MySolution.sln -n --allowDowngrade ``` nugetupdater -s=MySolution.sln -n -v=dev -v=stable --allowDowngrade --versionOverrides=versions.json ``` -Versions.json example: +versions.json example: ``` [ { @@ -75,3 +75,22 @@ Versions.json example: } ] ``` + +- Update versions tracked in project properties. eg: +```xml +4.9.26 +``` + +``` +nugetupdater -s=MySolution.sln -n -v=dev -v=stable --allowDowngrade --projectProperties=properties.json +``` +properties.json example: +``` +[ + { + "PropertyName": "UnoVersion", + "PackageId": "Uno.UI" + } +] +``` +In this case the `UnoVersion` property will be updated to the latest version of `Uno.UI` found in the solution. From 76a48a0e429ce4bc8286184825a53b4bb3c32c84 Mon Sep 17 00:00:00 2001 From: Nick Randolph Date: Thu, 3 Aug 2023 23:11:24 +1000 Subject: [PATCH 193/201] chore: Renaming projectProperties to updateProperties --- .../Arguments/ConsoleArgsContext.cs | 4 ++-- src/NvGet.Tools.Updater/Readme.md | 2 +- src/NvGet/Extensions/XmlDocumentExtensions.cs | 6 +++--- src/NvGet/Helpers/SolutionHelper.cs | 18 +++++++++--------- .../Updater/Entities/UpdaterParameters.cs | 2 +- .../Extensions/XmlDocumentExtensions.cs | 6 +++--- src/NvGet/Tools/Updater/NuGetUpdater.cs | 4 ++-- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs index b6e30cd..560ca1d 100644 --- a/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs +++ b/src/NvGet.Tools.Shared/Arguments/ConsoleArgsContext.cs @@ -53,7 +53,7 @@ internal static OptionSet CreateOptionsFor(ConsoleArgsContext context = null) { "dryrun", "Runs the updater but doesn't write the updates to files.", TrySet(_ => context.Parameters.IsDryRun = true) }, { "result|r=", "The path to the file where the update result should be saved.", TrySet(x => context.ResultFile = x) }, { "versionOverrides=", "The path to a JSON file to force specifc versions to be used; format should be the same as the result file", TryParseAndSet(LoadOverrides, x => context.Parameters.VersionOverrides.AddRange(x)) }, - { "projectProperties=", "The path to a JSON file that lists pairs of csproj property names and corresponding package Id, so that updater can update project properties as necessary", TryParseAndSet(LoadProjectProperties, x => context.Parameters.ProjectProperties.AddRange(x)) }, + { "updateProperties=", "The path to a JSON file that lists pairs of csproj property names and corresponding package Id, so that updater can update project properties as necessary", TryParseAndSet(LoadUpdateProperties, x => context.Parameters.UpdateProperties.AddRange(x)) }, }; Action TrySet(Action set) @@ -144,7 +144,7 @@ async Task> LoadFromStreamAsync() } } - internal static IEnumerable<(string PropertyName, string PackageId)> LoadProjectProperties(string inputPathOrUrl) + internal static IEnumerable<(string PropertyName, string PackageId)> LoadUpdateProperties(string inputPathOrUrl) { var results = LoadFromStreamAsync() diff --git a/src/NvGet.Tools.Updater/Readme.md b/src/NvGet.Tools.Updater/Readme.md index 8bba363..6fdbd4f 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/src/NvGet.Tools.Updater/Readme.md @@ -82,7 +82,7 @@ versions.json example: ``` ``` -nugetupdater -s=MySolution.sln -n -v=dev -v=stable --allowDowngrade --projectProperties=properties.json +nugetupdater -s=MySolution.sln -n -v=dev -v=stable --allowDowngrade --updateProperties=properties.json ``` properties.json example: ``` diff --git a/src/NvGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs index 370d198..98e323d 100644 --- a/src/NvGet/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Extensions/XmlDocumentExtensions.cs @@ -26,15 +26,15 @@ namespace NvGet.Extensions public static class XmlDocumentExtensions { /// - /// Retrieves the ProjectProperties that need updating from the given XmlDocument. + /// Retrieves the UpdateProperties that need updating from the given XmlDocument. /// /// /// A Dictionary where the key is the id of a package and the value its version. - public static PackageIdentity[] GetProjectProperties(this XmlDocument document, ICollection<(string PropertyName, string PackageId)> projectProperties) + public static PackageIdentity[] GetUpdateProperties(this XmlDocument document, ICollection<(string PropertyName, string PackageId)> updateProperties) { var references = new List(); - foreach(var prop in projectProperties) + foreach(var prop in updateProperties) { var docProp = document.SelectElements(prop.PropertyName).FirstOrDefault(); if(docProp is null) diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 58f4464..23054db 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -25,10 +25,10 @@ public static async Task GetPackageReferences( string solutionPath, FileType fileType, ILogger log, - ICollection<(string PropertyName, string PackageId)>? projectProperties = default + ICollection<(string PropertyName, string PackageId)>? updateProperties = default ) { - projectProperties ??= Array.Empty<(string PropertyName, string PackageId)>(); + updateProperties ??= Array.Empty<(string PropertyName, string PackageId)>(); log.LogInformation($"Retrieving references from files in {solutionPath}"); @@ -38,7 +38,7 @@ public static async Task GetPackageReferences( { foreach(var f in await GetProjectFiles(ct, solutionPath, log)) { - packages.AddRange(await GetFileReferences(ct, f, FileType.Csproj, projectProperties)); + packages.AddRange(await GetFileReferences(ct, f, FileType.Csproj, updateProperties)); } } @@ -48,7 +48,7 @@ public static async Task GetPackageReferences( foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) { - packages.AddRange(await GetFileReferences(ct, file, currentTarget, projectProperties)); + packages.AddRange(await GetFileReferences(ct, file, currentTarget, updateProperties)); } } @@ -58,7 +58,7 @@ public static async Task GetPackageReferences( foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) { - packages.AddRange(await GetFileReferences(ct, file, currentTarget, projectProperties)); + packages.AddRange(await GetFileReferences(ct, file, currentTarget, updateProperties)); } } @@ -68,7 +68,7 @@ public static async Task GetPackageReferences( foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) { - packages.AddRange(await GetFileReferences(ct, file, currentTarget, projectProperties)); + packages.AddRange(await GetFileReferences(ct, file, currentTarget, updateProperties)); } } @@ -76,7 +76,7 @@ public static async Task GetPackageReferences( { foreach(var f in await GetNuspecFiles(ct, solutionPath, log)) { - packages.AddRange(await GetFileReferences(ct, f, FileType.Nuspec, projectProperties)); + packages.AddRange(await GetFileReferences(ct, f, FileType.Nuspec, updateProperties)); } } @@ -164,7 +164,7 @@ private static async Task GetNuspecFiles(CancellationToken ct, string return files; } - private static async Task GetFileReferences(CancellationToken ct, string file, FileType target, ICollection<(string PropertyName, string PackageId)> projectProperties) + private static async Task GetFileReferences(CancellationToken ct, string file, FileType target, ICollection<(string PropertyName, string PackageId)> updateProperties) { if(file.IsNullOrEmpty()) { @@ -176,7 +176,7 @@ private static async Task GetFileReferences(CancellationToke if(target.HasAnyFlag(FileType.Csproj, FileType.DirectoryProps, FileType.DirectoryTargets, FileType.CentralPackageManagement)) { - references = document.GetPackageReferences().Concat(document.GetProjectProperties(projectProperties)).ToArray(); + references = document.GetPackageReferences().Concat(document.GetUpdateProperties(updateProperties)).ToArray(); } else if(target.HasFlag(FileType.Nuspec)) { diff --git a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs index db655bf..40cbe7a 100644 --- a/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs +++ b/src/NvGet/Tools/Updater/Entities/UpdaterParameters.cs @@ -63,7 +63,7 @@ public class UpdaterParameters /// /// Gets the csproj properties that should be updated for corresponding package. /// - public ICollection<(string PropertyName, string PackageId)> ProjectProperties { get; } = new List<(string PropertyNaem, string PackageId)>(); + public ICollection<(string PropertyName, string PackageId)> UpdateProperties { get; } = new List<(string PropertyNaem, string PackageId)>(); /// /// Gets or sets a value indicating whether to actually write the updates to the files. diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index acc22a5..67e5d05 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -22,16 +22,16 @@ public static class XmlDocumentExtensions /// /// /// - public static IEnumerable UpdateProjectProperties( + public static IEnumerable UpdateUpdateProperties( this XmlDocument document, UpdateOperation operation, - ICollection<(string PropertyName, string PackageId)> projectProperties) + ICollection<(string PropertyName, string PackageId)> updateProperties) { var operations = new List(); var packageId = operation.PackageId; - foreach(var prop in projectProperties.Where(x=> x.PackageId == packageId)) + foreach(var prop in updateProperties.Where(x=> x.PackageId == packageId)) { var docProp = document.SelectElements(prop.PropertyName).FirstOrDefault(); if(docProp is null) diff --git a/src/NvGet/Tools/Updater/NuGetUpdater.cs b/src/NvGet/Tools/Updater/NuGetUpdater.cs index ca51565..0cc21e1 100644 --- a/src/NvGet/Tools/Updater/NuGetUpdater.cs +++ b/src/NvGet/Tools/Updater/NuGetUpdater.cs @@ -108,7 +108,7 @@ public async Task> UpdatePackages(CancellationToken ct internal async Task GetPackages(CancellationToken ct) { var packages = new List(); - var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget, _log, _parameters.ProjectProperties); + var references = await SolutionHelper.GetPackageReferences(ct, _parameters.SolutionRoot, _parameters.UpdateTarget, _log, _parameters.UpdateProperties); _log.Write($"Found {references.Length} references"); @@ -173,7 +173,7 @@ Dictionary documents else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj, FileType.CentralPackageManagement)) { updates = document.UpdatePackageReferences(currentOperation); - var propertyUpdates = document.UpdateProjectProperties(currentOperation, _parameters.ProjectProperties); + var propertyUpdates = document.UpdateUpdateProperties(currentOperation, _parameters.UpdateProperties); updates = updates.Concat(propertyUpdates); } From 10a7f1ef8cf7503155859ba674afc58cf9742574 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Tue, 2 Apr 2024 23:27:05 -0400 Subject: [PATCH 194/201] feat: Add support for global.json updates --- src/NvGet/Contracts/FileType.cs | 7 ++- src/NvGet/Extensions/DocumentReference.cs | 30 ++++++++++ src/NvGet/Extensions/FileTypeExtensions.cs | 23 +++----- src/NvGet/Extensions/JsonDocumentReference.cs | 40 ++++++++++++++ src/NvGet/Extensions/XmlDocumentExtensions.cs | 15 +++-- src/NvGet/Extensions/XmlDocumentReference.cs | 40 ++++++++++++++ src/NvGet/Helpers/SolutionHelper.cs | 39 +++++++++++++ .../Extensions/XmlDocumentExtensions.cs | 55 ++++++++++++++----- src/NvGet/Tools/Updater/NuGetUpdater.cs | 17 ++++-- 9 files changed, 228 insertions(+), 38 deletions(-) create mode 100644 src/NvGet/Extensions/DocumentReference.cs create mode 100644 src/NvGet/Extensions/JsonDocumentReference.cs create mode 100644 src/NvGet/Extensions/XmlDocumentReference.cs diff --git a/src/NvGet/Contracts/FileType.cs b/src/NvGet/Contracts/FileType.cs index 3baa575..9cd1db2 100644 --- a/src/NvGet/Contracts/FileType.cs +++ b/src/NvGet/Contracts/FileType.cs @@ -38,9 +38,14 @@ public enum FileType /// CentralPackageManagement = 32, + /// + /// global.json files. + /// + GlobalJson = 64, + /// /// All the supported file types. /// - All = Nuspec | Csproj | DirectoryProps | DirectoryTargets | CentralPackageManagement, + All = Nuspec | Csproj | DirectoryProps | DirectoryTargets | CentralPackageManagement | GlobalJson, } } diff --git a/src/NvGet/Extensions/DocumentReference.cs b/src/NvGet/Extensions/DocumentReference.cs new file mode 100644 index 0000000..e276d34 --- /dev/null +++ b/src/NvGet/Extensions/DocumentReference.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NvGet.Entities; +using NuGet.Packaging.Core; +using NuGet.Versioning; +using Uno.Extensions; + +#if WINDOWS_UWP +using System.Text.RegularExpressions; +using Windows.Data.Xml.Dom; +using Windows.Storage; +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; +using XmlNode = Windows.Data.Xml.Dom.IXmlNode; +#else +using XmlDocument = System.Xml.XmlDocument; +using XmlElement = System.Xml.XmlElement; +using XmlNode = System.Xml.XmlNode; +#endif + +namespace NvGet.Extensions +{ + public abstract class DocumentReference + { + public abstract Task Save(CancellationToken ct, string path); + } +} diff --git a/src/NvGet/Extensions/FileTypeExtensions.cs b/src/NvGet/Extensions/FileTypeExtensions.cs index 3265022..95c39dc 100644 --- a/src/NvGet/Extensions/FileTypeExtensions.cs +++ b/src/NvGet/Extensions/FileTypeExtensions.cs @@ -8,21 +8,16 @@ public static class FileTypeExtensions { public static string GetDescription(this FileType target) { - switch(target) + return target switch { - case FileType.Nuspec: - return ".nuspec"; - case FileType.Csproj: - return ".csproj"; - case FileType.DirectoryProps: - return "Directory.Build.targets"; - case FileType.DirectoryTargets: - return "Directory.Build.props"; - case FileType.CentralPackageManagement: - return "Directory.Packages.props"; - default: - return default; - } + FileType.Nuspec => ".nuspec", + FileType.Csproj => ".csproj", + FileType.DirectoryProps => "Directory.Build.targets", + FileType.DirectoryTargets => "Directory.Build.props", + FileType.CentralPackageManagement => "Directory.Packages.props", + FileType.GlobalJson => "global.json", + _ => default, + }; } public static bool HasAnyFlag(this FileType target, params FileType[] others) diff --git a/src/NvGet/Extensions/JsonDocumentReference.cs b/src/NvGet/Extensions/JsonDocumentReference.cs new file mode 100644 index 0000000..4b3c6c8 --- /dev/null +++ b/src/NvGet/Extensions/JsonDocumentReference.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NvGet.Entities; +using NuGet.Packaging.Core; +using NuGet.Versioning; +using Uno.Extensions; + +#if WINDOWS_UWP +using System.Text.RegularExpressions; +using Windows.Data.Xml.Dom; +using Windows.Storage; +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; +using XmlNode = Windows.Data.Xml.Dom.IXmlNode; +#else +using XmlDocument = System.Xml.XmlDocument; +using XmlElement = System.Xml.XmlElement; +using XmlNode = System.Xml.XmlNode; +#endif + +namespace NvGet.Extensions +{ + public class JsonDocumentReference : DocumentReference + { + public JsonDocumentReference(string contents) + { + Contents = contents; + } + + public string Contents { get; set; } + + public override async Task Save(CancellationToken ct, string path) + { + System.IO.File.WriteAllText(path, Contents); + } + } +} diff --git a/src/NvGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs index 98e323d..494df76 100644 --- a/src/NvGet/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Extensions/XmlDocumentExtensions.cs @@ -121,21 +121,28 @@ private static PackageIdentity CreatePackageIdentity(string id, string version) /// /// /// - public static async Task> OpenFiles( + public static async Task> OpenFiles( this IEnumerable references, CancellationToken ct ) { var files = references .SelectMany(r => r.Files) - .SelectMany(g => g.Value) + .SelectMany(g => g.Value.Select(file => (fileType: g.Key, file: file))) .Distinct(); - var documents = new Dictionary(); + var documents = new Dictionary(); foreach(var file in files) { - documents.Add(file, await file.LoadDocument(ct)); + if(file.fileType == Contracts.FileType.GlobalJson) + { + documents.Add(file.file, new JsonDocumentReference(System.IO.File.ReadAllText(file.file))); + } + else + { + documents.Add(file.file, new XmlDocumentReference(await file.file.LoadDocument(ct))); + } } return documents; diff --git a/src/NvGet/Extensions/XmlDocumentReference.cs b/src/NvGet/Extensions/XmlDocumentReference.cs new file mode 100644 index 0000000..12425c6 --- /dev/null +++ b/src/NvGet/Extensions/XmlDocumentReference.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NvGet.Entities; +using NuGet.Packaging.Core; +using NuGet.Versioning; +using Uno.Extensions; + +#if WINDOWS_UWP +using System.Text.RegularExpressions; +using Windows.Data.Xml.Dom; +using Windows.Storage; +using XmlDocument = Windows.Data.Xml.Dom.XmlDocument; +using XmlElement = Windows.Data.Xml.Dom.XmlElement; +using XmlNode = Windows.Data.Xml.Dom.IXmlNode; +#else +using XmlDocument = System.Xml.XmlDocument; +using XmlElement = System.Xml.XmlElement; +using XmlNode = System.Xml.XmlNode; +#endif + +namespace NvGet.Extensions +{ + public class XmlDocumentReference : DocumentReference + { + public XmlDocumentReference(XmlDocument document) + { + Document = document; + } + + public XmlDocument Document { get; } + + public override async Task Save(CancellationToken ct, string path) + { + Document.Save(ct, path); + } + } +} diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 23054db..085ab7a 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -12,6 +12,7 @@ using NvGet.Extensions; using NuGet.Versioning; using Uno.Extensions; +using Newtonsoft.Json; namespace NvGet.Helpers { @@ -72,6 +73,16 @@ public static async Task GetPackageReferences( } } + if(fileType.HasFlag(FileType.GlobalJson)) + { + const FileType currentTarget = FileType.GlobalJson; + + foreach(var file in await GetDirectoryFiles(ct, solutionPath, currentTarget, log)) + { + packages.AddRange(await GetGlobalJsonFileReferences(ct, file, currentTarget, updateProperties)); + } + } + if(fileType.HasFlag(FileType.Nuspec)) { foreach(var f in await GetNuspecFiles(ct, solutionPath, log)) @@ -188,5 +199,33 @@ private static async Task GetFileReferences(CancellationToke .Select(g => new PackageReference(g.Key, new NuGetVersion(g.FirstOrDefault().Version), file, target)) .ToArray(); } + + private static async Task GetGlobalJsonFileReferences(CancellationToken ct, string file, FileType target, ICollection<(string PropertyName, string PackageId)> updateProperties) + { + if(file.IsNullOrEmpty()) + { + return Array.Empty(); + } + + // Parse the global.json file to find all the sdks + var json = await FileHelper.ReadFileContent(ct, file); + var globalJson = JsonConvert.DeserializeObject(json); + + var references = globalJson + ?.MSBuildSdks + ?.Select(s => new PackageIdentity(s.Key, new NuGetVersion(s.Value))) + .ToArray() ?? Array.Empty(); + + return references + .GroupBy(r => r.Id) + .Select(g => new PackageReference(g.Key, new NuGetVersion(g.FirstOrDefault().Version), file, target)) + .ToArray(); + } + } + + public class GlobalJson + { + [JsonProperty("msbuild-sdks")] + public Dictionary MSBuildSdks { get; set; } } } diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index 67e5d05..127a201 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Xml; +using Newtonsoft.Json; using NvGet.Extensions; +using NvGet.Helpers; using NvGet.Tools.Updater.Log; using Uno.Extensions; @@ -139,31 +142,57 @@ private static void SetAttributeOrChild(this XmlElement element, string name, st /// /// public static IEnumerable UpdateDependencies( - this XmlDocument document, + this DocumentReference document, UpdateOperation operation ) { - var operations = new List(); - - foreach(var node in document.SelectElements("dependency", $"[@id='{operation.PackageId}']")) + if(document is XmlDocumentReference xmlDocReference) { - var versionNodeValue = node.GetAttribute("version"); + var operations = new List(); - // only nodes with explicit version, skip expansion. - if(!versionNodeValue.Contains("{", System.StringComparison.OrdinalIgnoreCase)) + foreach(var node in xmlDocReference.Document.SelectElements("dependency", $"[@id='{operation.PackageId}']")) { - var currentOperation = operation.WithPreviousVersion(versionNodeValue); + var versionNodeValue = node.GetAttribute("version"); - if(currentOperation.ShouldProceed()) + // only nodes with explicit version, skip expansion. + if(!versionNodeValue.Contains("{", System.StringComparison.OrdinalIgnoreCase)) { - node.SetAttribute("version", currentOperation.UpdatedVersion.ToString()); + var currentOperation = operation.WithPreviousVersion(versionNodeValue); + + if(currentOperation.ShouldProceed()) + { + node.SetAttribute("version", currentOperation.UpdatedVersion.ToString()); + } + + operations.Add(currentOperation); } + } + + return operations; + } + else if(document is JsonDocumentReference jsonDocReference) + { + var operations = new List(); + + var globalJson = JsonConvert.DeserializeObject(jsonDocReference.Contents); + + if(globalJson.MSBuildSdks.TryGetValue(operation.PackageId, out var value)) + { + globalJson.MSBuildSdks[operation.PackageId] = operation.UpdatedVersion.ToString(); + + var currentOperation = operation.WithPreviousVersion(value); operations.Add(currentOperation); } - } - return operations; + jsonDocReference.Contents = JsonConvert.SerializeObject(globalJson); + + return operations; + } + else + { + throw new NotSupportedException(); + } } } } diff --git a/src/NvGet/Tools/Updater/NuGetUpdater.cs b/src/NvGet/Tools/Updater/NuGetUpdater.cs index 0cc21e1..0d1e71b 100644 --- a/src/NvGet/Tools/Updater/NuGetUpdater.cs +++ b/src/NvGet/Tools/Updater/NuGetUpdater.cs @@ -144,7 +144,7 @@ private async Task UpdateFiles( CancellationToken ct, UpdateOperation operation, Dictionary targetFiles, - Dictionary documents + Dictionary documents ) { var operations = new List(); @@ -166,14 +166,19 @@ Dictionary documents var currentOperation = operation.WithFilePath(path); - if(fileType.HasFlag(FileType.Nuspec)) + if(fileType.HasFlag(FileType.Nuspec) && document is XmlDocumentReference xmlDocReference) { - updates = document.UpdateDependencies(currentOperation); + updates = xmlDocReference.UpdateDependencies(currentOperation); } - else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj, FileType.CentralPackageManagement)) + else if(fileType.HasFlag(FileType.GlobalJson) && document is JsonDocumentReference jsonDocReference) { - updates = document.UpdatePackageReferences(currentOperation); - var propertyUpdates = document.UpdateUpdateProperties(currentOperation, _parameters.UpdateProperties); + updates = jsonDocReference.UpdateDependencies(currentOperation); + } + else if(fileType.HasAnyFlag(FileType.DirectoryProps, FileType.DirectoryTargets, FileType.Csproj, FileType.CentralPackageManagement) + && document is XmlDocumentReference xmlDocReference2) + { + updates = xmlDocReference2.Document.UpdatePackageReferences(currentOperation); + var propertyUpdates = xmlDocReference2.Document.UpdateUpdateProperties(currentOperation, _parameters.UpdateProperties); updates = updates.Concat(propertyUpdates); } From 964b56dd13bfea4ad8ccd81e68709b40235279ec Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 3 Apr 2024 13:35:02 -0400 Subject: [PATCH 195/201] feat: Support for `*Version` properties update --- src/NvGet.Tools.Updater/Readme.md | 12 ++++++++ src/NvGet/Extensions/XmlDocumentExtensions.cs | 29 +++++++++++++++++++ src/NvGet/Helpers/SolutionHelper.cs | 11 ++++++- .../Extensions/XmlDocumentExtensions.cs | 18 ++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/NvGet.Tools.Updater/Readme.md b/src/NvGet.Tools.Updater/Readme.md index 6fdbd4f..8ea9ae5 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/src/NvGet.Tools.Updater/Readme.md @@ -94,3 +94,15 @@ properties.json example: ] ``` In this case the `UnoVersion` property will be updated to the latest version of `Uno.UI` found in the solution. + +## Supported types of updates + +The nuget updater supports updating: +- `.csproj`, `Directory.Build.props`, `Directory.Build.targets` and `Directory.Packages.props` + For this type of files, the tool will update: + - `PackageReference` and `PackageVersion` items + - MSBuild properties using named this way `UnoWinUIVersion` or `UnoExtensionsNavigationVersion` +- `.nuspec` + For this type of files, the tool will update `reference` entries. +- `global.json` + For this type of files, the tool will update `msbuild-sdk` entries diff --git a/src/NvGet/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Extensions/XmlDocumentExtensions.cs index 494df76..d122b02 100644 --- a/src/NvGet/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Extensions/XmlDocumentExtensions.cs @@ -88,6 +88,35 @@ public static PackageIdentity[] GetPackageReferences(this XmlDocument document) } } + // find all nodes inside a PropertyGroup node that for which the name is ending by Version + var propertyGroupVersionReferences = document + .SelectElements("PropertyGroup") + .SelectMany(pg => pg.SelectNodes("*").OfType()) + .Where(e => e.LocalName.EndsWith("Version", StringComparison.OrdinalIgnoreCase)); + + foreach(var versionProperty in propertyGroupVersionReferences) + { + var originalTrimmedName = versionProperty + .LocalName + .TrimEnd("Version"); + + var nameParts = + originalTrimmedName + .Select((c, i) => + i > 0 + && char.IsUpper(c) + && !char.IsUpper(originalTrimmedName[i - 1]) + ? "." + c + : c.ToString()); + + var packageName = string.Concat(nameParts).ToLowerInvariant(); + + if(NuGetVersion.TryParse(versionProperty.InnerText, out var nugetVersion)) + { + references.Add(CreatePackageIdentity(packageName, versionProperty.InnerText)); + } + } + return references .Trim() .ToArray(); diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 085ab7a..2f82947 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -140,7 +140,16 @@ private static async Task GetDirectoryFiles(CancellationToken ct, stri else { var solutionFolder = Path.GetDirectoryName(solutionPath); - file = Path.Combine(solutionFolder, target.GetDescription()); + + if(target is FileType.DirectoryProps or FileType.DirectoryTargets or FileType.GlobalJson or FileType.CentralPackageManagement) + { + var matchingFiles = await FileHelper.GetFiles(ct, solutionFolder, nameFilter: target.GetDescription()); + return matchingFiles.ToArray(); + } + else + { + file = Path.Combine(solutionFolder, target.GetDescription()); + } } if(file.HasValue() && await FileHelper.Exists(file)) diff --git a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs index 127a201..d2a0c65 100644 --- a/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs +++ b/src/NvGet/Tools/Updater/Extensions/XmlDocumentExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Xml; using Newtonsoft.Json; +using NuGet.Versioning; using NvGet.Extensions; using NvGet.Helpers; using NvGet.Tools.Updater.Log; @@ -95,6 +96,23 @@ UpdateOperation operation } } + var propertyGroupVersionReferences = document + .SelectElements("PropertyGroup") + .SelectMany(pg => pg.SelectNodes(packageId.Replace(".", "") + "Version").OfType()); + + foreach(var versionProperty in propertyGroupVersionReferences) + { + if(NuGetVersion.TryParse(versionProperty.InnerText, out var previousVersion)) + { + var currentOperation = operation.WithPreviousVersion(versionProperty.InnerText); + + if(currentOperation.ShouldProceed()) + { + versionProperty.InnerText = currentOperation.UpdatedVersion.ToString(); + } + } + } + return operations; } From 9b8e93a9ee4c6c29db18df5905abe94164785607 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Thu, 4 Apr 2024 16:21:53 -0400 Subject: [PATCH 196/201] chore: missing await --- src/NvGet/Extensions/XmlDocumentReference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NvGet/Extensions/XmlDocumentReference.cs b/src/NvGet/Extensions/XmlDocumentReference.cs index 12425c6..937e477 100644 --- a/src/NvGet/Extensions/XmlDocumentReference.cs +++ b/src/NvGet/Extensions/XmlDocumentReference.cs @@ -34,7 +34,7 @@ public XmlDocumentReference(XmlDocument document) public override async Task Save(CancellationToken ct, string path) { - Document.Save(ct, path); + await Document.Save(ct, path); } } } From cddbb6b2c4e2675c0c381ccea8a83421c730b77f Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Thu, 4 Apr 2024 16:22:06 -0400 Subject: [PATCH 197/201] chore: Move globaljson in its own file --- src/NvGet/Helpers/GlobalJson.cs | 24 ++++++++++++++++++++++++ src/NvGet/Helpers/SolutionHelper.cs | 6 ------ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 src/NvGet/Helpers/GlobalJson.cs diff --git a/src/NvGet/Helpers/GlobalJson.cs b/src/NvGet/Helpers/GlobalJson.cs new file mode 100644 index 0000000..81a0226 --- /dev/null +++ b/src/NvGet/Helpers/GlobalJson.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Packaging.Core; +using NvGet.Contracts; +using NvGet.Entities; +using NvGet.Extensions; +using NuGet.Versioning; +using Uno.Extensions; +using Newtonsoft.Json; + +namespace NvGet.Helpers +{ + public class GlobalJson + { + [JsonProperty("msbuild-sdks")] + public Dictionary MSBuildSdks { get; set; } + } +} diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index 2f82947..a3d3244 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -231,10 +231,4 @@ private static async Task GetGlobalJsonFileReferences(Cancel .ToArray(); } } - - public class GlobalJson - { - [JsonProperty("msbuild-sdks")] - public Dictionary MSBuildSdks { get; set; } - } } From b136152ee1fb982d292b1246fe5f7bee209df12f Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 17 Apr 2024 09:12:57 -0400 Subject: [PATCH 198/201] fix: Handle unreadable global.json sdk version --- src/NvGet/Helpers/SolutionHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NvGet/Helpers/SolutionHelper.cs b/src/NvGet/Helpers/SolutionHelper.cs index a3d3244..5c826b8 100644 --- a/src/NvGet/Helpers/SolutionHelper.cs +++ b/src/NvGet/Helpers/SolutionHelper.cs @@ -222,7 +222,8 @@ private static async Task GetGlobalJsonFileReferences(Cancel var references = globalJson ?.MSBuildSdks - ?.Select(s => new PackageIdentity(s.Key, new NuGetVersion(s.Value))) + ?.Select(s => NuGetVersion.TryParse(s.Value, out var version) ? new PackageIdentity(s.Key, version) : null) + ?.Where(v => v is not null) .ToArray() ?? Array.Empty(); return references From 0d00e7fe98a9ec8ef49317bbc97013cf35741fb0 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Fri, 17 May 2024 15:08:22 -0400 Subject: [PATCH 199/201] chore: Update to uno platform --- CODE_OF_CONDUCT.md | 2 +- LICENSE | 2 +- src/Directory.Build.props | 4 +- src/NvGet.Tests/Constants.cs | 2 +- .../Tools/Updater/NuGetUpdaterTests.cs | 2 +- .../Tools/Updater/PackageReferenceTests.cs | 6 +- .../NvGet.Tools.Downloader.csproj | 8 +- src/NvGet.Tools.Downloader/README.md | 2 +- .../NvGet.Tools.Hierarchy.csproj | 8 +- src/NvGet.Tools.Hierarchy/README.md | 2 +- .../NvGet.Tools.Updater.csproj | 8 +- src/NvGet.Tools.Updater/Readme.md | 10 +- src/NvGet/NvGet.csproj | 8 +- src/nuget.updater.ruleset | 151 ------------------ 14 files changed, 32 insertions(+), 183 deletions(-) delete mode 100644 src/nuget.updater.ruleset diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 79037a6..71db77c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at info@nventive.com. All +reported by contacting the project team at info@platform.uno. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an diff --git a/LICENSE b/LICENSE index dace5dd..6259cd1 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2019 nventive. + Copyright 2019 unoplatform. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bac4060..e6c2df9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -4,8 +4,8 @@ portable True $(BUILD_REPOSITORY_URI) - nventive - nventive + unoplatform + unoplatform true true diff --git a/src/NvGet.Tests/Constants.cs b/src/NvGet.Tests/Constants.cs index e7af79a..1d732b0 100644 --- a/src/NvGet.Tests/Constants.cs +++ b/src/NvGet.Tests/Constants.cs @@ -11,7 +11,7 @@ public static class Constants public static readonly Dictionary TestPackages = new Dictionary { - {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, + {"unoplatform.NuGet.Updater", new[] { "1.0-beta.1" } }, {"Uno.UI", new[] { "2.1.39", "2.2.0", "2.3.0-dev.44", "2.3.0-dev.48", "2.3.0-dev.58", "5.0.0-feature.5x.88" } }, }; diff --git a/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs b/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs index d34812e..91609ed 100644 --- a/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs +++ b/src/NvGet.Tests/Tools/Updater/NuGetUpdaterTests.cs @@ -19,7 +19,7 @@ public class NuGetUpdaterTests { {"Uno.UI", new[] { "1.0", "1.1-dev.1" } }, {"Uno.Core", new[] { "1.0", "1.0-beta.1" } }, - {"nventive.NuGet.Updater", new[] { "1.0-beta.1" } }, + {"unoplatform.NuGet.Updater", new[] { "1.0-beta.1" } }, }; private static readonly TestPackageFeed TestFeed = new TestPackageFeed(new Uri("http://localhost"), TestPackages); diff --git a/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs b/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs index fc36035..fc7e5e8 100644 --- a/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs +++ b/src/NvGet.Tests/Tools/Updater/PackageReferenceTests.cs @@ -21,7 +21,7 @@ public async Task GivenPackageWithMatchingVersion_VersionIsFound() }; var packageVersion = "1.0-beta.1"; - var packageId = "nventive.NuGet.Updater"; + var packageId = "unoplatform.NuGet.Updater"; var reference = new PackageReference(packageId, packageVersion); @@ -40,7 +40,7 @@ public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() }; var packageVersion = "1.0-beta.1"; - var packageId = "nventive.NuGet.Updater"; + var packageId = "unoplatform.NuGet.Updater"; var reference = new PackageReference(packageId, packageVersion); @@ -52,7 +52,7 @@ public async Task GivenPackageWithNoMatchingVersion_NoVersionIsFound() [TestMethod] public async Task GivenManualUpdates_AndVersionNotInFeed_ManualVersionIsFound() { - var reference = new PackageReference("nventive.NuGet.Updater", "1.0"); + var reference = new PackageReference("unoplatform.NuGet.Updater", "1.0"); var parameters = new UpdaterParameters { diff --git a/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj index fbf11f7..8b02a72 100644 --- a/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj +++ b/src/NvGet.Tools.Downloader/NvGet.Tools.Downloader.csproj @@ -12,13 +12,13 @@ - nventive.NuGet.Downloader.Tool - nventive - nventive + unoplatform.NuGet.Downloader.Tool + unoplatform + unoplatform Nuget Dowloader allows to download the NuGet packages found in a solution Apache-2.0 icon.png - https://github.com/nventive/NuGet.Updater + https://github.com/unoplatform/NuGet.Updater diff --git a/src/NvGet.Tools.Downloader/README.md b/src/NvGet.Tools.Downloader/README.md index b9aa59a..12df44b 100644 --- a/src/NvGet.Tools.Downloader/README.md +++ b/src/NvGet.Tools.Downloader/README.md @@ -5,7 +5,7 @@ NuGet.Downloader allows to download all NuGet packages found in a solution, and ## Getting Started The NuGet Downloader can be installed as a standalone .Net Core tool using the following command: -`dotnet tool install -g nventive.NuGet.Downloader.Tool` +`dotnet tool install -g unoplatform.NuGet.Downloader.Tool` Help can be found with : `nugetdownloader --help` diff --git a/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj index 79f708d..c29ceff 100644 --- a/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj +++ b/src/NvGet.Tools.Hierarchy/NvGet.Tools.Hierarchy.csproj @@ -12,14 +12,14 @@ - nventive.NuGet.Hierarchy.Tool + unoplatform.NuGet.Hierarchy.Tool NuGet Hierarchy Tool NuGet Hierarchy allows you to view the dependency tree of a solution - nventive - nventive + unoplatform + unoplatform Apache-2.0 icon.png - https://github.com/nventive/NuGet.Updater + https://github.com/unoplatform/NuGet.Updater diff --git a/src/NvGet.Tools.Hierarchy/README.md b/src/NvGet.Tools.Hierarchy/README.md index a4acb68..f8de996 100644 --- a/src/NvGet.Tools.Hierarchy/README.md +++ b/src/NvGet.Tools.Hierarchy/README.md @@ -5,7 +5,7 @@ NuGet.Hierarcy allows to display the full hierarchy of the NuGet packages for a ## Getting Started The NuGet Hierarcy can be installed as a standalone .Net Core tool using the following command: -`dotnet tool install -g nventive.NuGet.Hierarchy.Tool` +`dotnet tool install -g unoplatform.NuGet.Hierarchy.Tool` Help can be found with : `nugethierarchy --help` diff --git a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj index 88c1c99..73e5c45 100644 --- a/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj +++ b/src/NvGet.Tools.Updater/NvGet.Tools.Updater.csproj @@ -13,14 +13,14 @@ - nventive.NuGet.Updater.Tool + unoplatform.NuGet.Updater.Tool NuGet Updater Tool A netcore version of the NuGet updater - nventive - nventive + unoplatform + unoplatform Apache-2.0 icon.png - https://github.com/nventive/NuGet.Updater + https://github.com/unoplatform/NuGet.Updater diff --git a/src/NvGet.Tools.Updater/Readme.md b/src/NvGet.Tools.Updater/Readme.md index 8ea9ae5..a5d335a 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/src/NvGet.Tools.Updater/Readme.md @@ -5,7 +5,7 @@ NuGet.Updater allows batch updates of NuGet packages found in solutions. ## Getting Started The NuGet Updater can be installed as a standalone .Net Core tool using the following command: -`dotnet tool install -g nventive.NuGet.Updater.Tool` +`dotnet tool install -g unoplatform.NuGet.Updater.Tool` Help can be found with : `nugetupdater --help` @@ -38,14 +38,14 @@ nugetupdater -s=MySolution.sln -n --version=beta -v=alpha nugetupdater -s=MySolution.sln --feed=https://pkgs.dev.azure.com/account/_packaging/feed/nuget/v3/index.json|personalaccesstoken --version=beta ``` -- Update packages from `nventive` from NuGet.org, except for `PackageA` and `PackageB` +- Update packages from `unoplatform` from NuGet.org, except for `PackageA` and `PackageB` ``` -nugetupdater -s=MySolution.sln -n --packageAuthor=nventive --ignore=PackageA -i=PackageB +nugetupdater -s=MySolution.sln -n --packageAuthor=unoplatform --ignore=PackageA -i=PackageB ``` -- Update packages from `nventive` and `microsoft` from NuGet.org, except for `PackageA` and `PackageB` +- Update packages from `unoplatform` and `microsoft` from NuGet.org, except for `PackageA` and `PackageB` ``` -nugetupdater -s=MySolution.sln -n --packageAuthor=nventive,microsoft --ignore=PackageA -i=PackageB +nugetupdater -s=MySolution.sln -n --packageAuthor=unoplatform,microsoft --ignore=PackageA -i=PackageB ``` - Update only `PackageA` and `PackageB` from NuGet.org and a private feed diff --git a/src/NvGet/NvGet.csproj b/src/NvGet/NvGet.csproj index 1061715..91c8449 100644 --- a/src/NvGet/NvGet.csproj +++ b/src/NvGet/NvGet.csproj @@ -8,13 +8,13 @@ - nventive.NuGet.Updater - nventive - nventive + unoplatform.NuGet.Updater + unoplatform + unoplatform Nuget Updater allows to automatically update the NuGet packages in a solution at build time Apache-2.0 icon.png - https://github.com/nventive/NuGet.Updater + https://github.com/unoplatform/NuGet.Updater diff --git a/src/nuget.updater.ruleset b/src/nuget.updater.ruleset deleted file mode 100644 index b6377ea..0000000 --- a/src/nuget.updater.ruleset +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From f07dbb77b0ee254844e52e9be5da6f31d431c18a Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Fri, 17 May 2024 15:13:32 -0400 Subject: [PATCH 200/201] chore: Adjust template --- .github/PULL_REQUEST_TEMPLATE.md | 54 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bc95e67..f081282 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,42 +1,50 @@ -GitHub Issue: # - +GitHub Issue (If applicable): # -## Proposed Changes - + - - - - - - - +## PR Type +What kind of change does this PR introduce? + ## What is the current behavior? - + + ## What is the new behavior? - + -## Checklist +## PR Checklist Please check if your PR fulfills the following requirements: -- [ ] Documentation has been added/updated -- [ ] Automated Unit / Integration tests for the changes have been added/updated +- [ ] Tested code with current [supported SDKs](../README.md#supported) +- [ ] Docs have been added/updated which fit [documentation template](https://github.com/nventive/Uno/blob/master/doc/.feature-template.md). (for bug fixes / features) +- [ ] [Unit Tests and/or UI Tests](doc/articles/working-with-the-samples-apps.md) for the changes have been added (for bug fixes / features) (if applicable) +- [ ] [Wasm UI Tests](doc/articles/working-with-the-samples-apps.md#running-the-webassembly-ui-tests-snapshots) are not showing unexpected any differences. Validate PR `Screenshots Compare Test Run` results. - [ ] Contains **NO** breaking changes -- [ ] Updated the Changelog -- [ ] Associated with an issue - - +- [ ] Updated the [Release Notes](https://github.com/nventive/Uno/tree/master/doc/ReleaseNotes) +- [ ] Associated with an issue (GitHub or internal) + ## Other information + +Internal Issue (If applicable): + From 781e101de7fa382bb8dd5bb173399877032d8b32 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Fri, 17 May 2024 15:31:00 -0400 Subject: [PATCH 201/201] docs: Update doc --- README.md | 12 +++++------- .../README.md => doc/nuget-downloader.md | 4 ++++ .../README.md => doc/nuget-hierarchy.md | 4 ++++ .../Readme.md => doc/nuget-updater.md | 4 ++++ doc/toc.yml | 7 +++++++ 5 files changed, 24 insertions(+), 7 deletions(-) rename src/NvGet.Tools.Downloader/README.md => doc/nuget-downloader.md (91%) rename src/NvGet.Tools.Hierarchy/README.md => doc/nuget-hierarchy.md (91%) rename src/NvGet.Tools.Updater/Readme.md => doc/nuget-updater.md (98%) create mode 100644 doc/toc.yml diff --git a/README.md b/README.md index fd7a8ad..e4cd179 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,16 @@ NuGet packages manipulations made easy. ## NuGet.Updater -Documentation about the NuGet.Updater can be found [here](src/NvGet.Tools.Updater/Readme.md) +Documentation about the NuGet.Updater can be found [here](doc/nuget-updater.md) ## NuGet.Downloader -Documentation about the NuGet.Downloader can be found [here](src/NvGet.Tools.Downloader/README.md) +Documentation about the NuGet.Downloader can be found [here](doc/nuget-downloader.md) ## NuGet.Hierarchy -Documentation about the NuGet.Hierarchy can be found [here](src/NvGet.Tools.Hierarchy/README.md) +Documentation about the NuGet.Hierarchy can be found [here](doc/nuget-hierarchy.md) -## Changelog - -Please consult the [CHANGELOG](CHANGELOG.md) for more information about version -history. ## License @@ -34,3 +30,5 @@ contributing to this project. Be mindful of our [Code of Conduct](CODE_OF_CONDUCT.md). ## Acknowledgments + +This repository is a fork of nventive's NuGet Updater. diff --git a/src/NvGet.Tools.Downloader/README.md b/doc/nuget-downloader.md similarity index 91% rename from src/NvGet.Tools.Downloader/README.md rename to doc/nuget-downloader.md index 12df44b..c10a53c 100644 --- a/src/NvGet.Tools.Downloader/README.md +++ b/doc/nuget-downloader.md @@ -1,3 +1,7 @@ +--- +uid: uno.tools.nuget.downloader +--- + ## About NuGet.Downloader allows to download all NuGet packages found in a solution, and also to push them to a specific NuGet feed. diff --git a/src/NvGet.Tools.Hierarchy/README.md b/doc/nuget-hierarchy.md similarity index 91% rename from src/NvGet.Tools.Hierarchy/README.md rename to doc/nuget-hierarchy.md index f8de996..e576cb6 100644 --- a/src/NvGet.Tools.Hierarchy/README.md +++ b/doc/nuget-hierarchy.md @@ -1,3 +1,7 @@ +--- +uid: uno.tools.nuget.hierarchy +--- + ## About NuGet.Hierarcy allows to display the full hierarchy of the NuGet packages for a solution. diff --git a/src/NvGet.Tools.Updater/Readme.md b/doc/nuget-updater.md similarity index 98% rename from src/NvGet.Tools.Updater/Readme.md rename to doc/nuget-updater.md index a5d335a..178b60b 100644 --- a/src/NvGet.Tools.Updater/Readme.md +++ b/doc/nuget-updater.md @@ -1,3 +1,7 @@ +--- +uid: uno.tools.nuget.updater +--- + ## About NuGet.Updater allows batch updates of NuGet packages found in solutions. diff --git a/doc/toc.yml b/doc/toc.yml new file mode 100644 index 0000000..7f41690 --- /dev/null +++ b/doc/toc.yml @@ -0,0 +1,7 @@ +items: +- name: Using the NuGet Updater + href: xref:uno.tools.nuget.updater +- name: Using the NuGet Downloader + href: xref:uno.tools.nuget.downloader +- name: Using the NuGet Hierarchy Viewer + href: xref:uno.tools.nuget.hierarchy

nuUona_iOxin|~bq7wNMfQ`pGj4}#wy{+w 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 Tc_TlWc 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 literal 0 HcmV?d00001 diff --git a/src/Packages/NuGet.Protocol/uap10.0/NuGet.Protocol.dll b/src/Packages/NuGet.Protocol/uap10.0/NuGet.Protocol.dll new file mode 100644 index 0000000000000000000000000000000000000000..fa5d84e348810f51fee7c357870ccb8537748034 GIT binary patch literal 641536 zcmdRXcYGYh`TlClobDvqK1nCJU|c|E(Mbjj7~3?{Yv>R{@5Lc3++o@>hp3_V5aNX1 z2?Rob&|By|^iWLigqDzyCVtQJzPnfCA_nfHC?o!NKF&dz>syE7cm zaU382-+t>jw*vAvSFZo}WeCxuCfz>DxhebPlv`UiesaoI`|Mkud_XDMyR_?%ChxxM z{`*GVhld+a&Aqa(Lu!g{lH9B1Pe&pEl=aa}6yO=s%l zww7fb=iaR2v?_I*H-JwD{wAQ~j1k;by-A?{@|&D<9MJjmoVmv_DgSCP36g}L2fmvU z0pIp26B5#2&S?j_*}|+dD<%CWaB@|3N!^O@`gQrHAGGJ82O+$}JxRG()5*s|SNV+v8y6&6QkheD9qc}kpslkd z3H#H!i*3U_Bu1-(=Oul;THf|fx6>OgOh82T6kd^)prg}E!bw&VHEXyUivoMCp1`?; zHM@}vy9u>sH{7$Z+hX8SXE!D_!l%)0%xhv^Yj%6{=?kxDup2Wsv76*-W;dj-vm0~& zmv*Cs|HW==AV2Mgwq0*Gl3_QY*6fB`9Cn-7Xg4M`F|UnwV_qYCYj)dbzi&Tmu$yFC z87Ujv8}l}8Z&xk`)NMl$Rs)SBJkuF>8m zHQJ3yP0VYf-I&+Jyw>ctA3bH&+$7Q&t4)huonv)<=aaI3D<)#d!gV3djX({8G;M$bix+LDRw$XkPvQw zg#EO)>ue1E{8hW04H1Vb*@z&ph|al?@I8Tn-9(_V=>9>^*v$mC+RX)KE!54SugE&M zN9E_n!Y~$ov9ODUSuCt#VH69SSeV3QQJ#uU`ygBrZrgis6TT*qAh0c#dD6#o%V$N_YMA};g zFWBD#nwTLqt*fRbA>2l|P-oxi10ReO@~#K_dq_0knts<0LLa!7z)^k}AgJZ^oXO4d z_=B172VgwLb-g4x#{%fuYy3^hKiUo{J-JSId;070`sM8>JL}NrbGW~9}8{#~xelQzVda35Y+pb%y*4pwtV`-0Y9oZ1@A1JdEU+31HrJO$Byzz2h#E`MQ9 zYlIrk4ZgTA*E(QYBUF3twch@`-`|39tLvQXOuvoAa-0iXuks_riZ=TL88#8FR-Yt6jl{C<*#KFMt z0TGv;{u%BO;OvJbX5GtmV}>T|rf^$t-maE}{y8p3Y{vD%L4f{}29@1FhwFP$nc7O^ zNez)*?&y|8y@RSr_D_-w?K|UD@?@QG{?H*jR!DVZ`xZv7mx&HR01mb}qZzMDy*A--Xg!a^{tb@_(Bow2 zF^YQ3J{DM_O673ml$I3(88%1YTDGJOF(!m@i_ww5a>Z3mGz5eu4UYoSL}x}FjVO$J zhkG!P5Yh38?b>I8A5jX*>7HVQ)W0k%Hz zgYby|8?Vu*7bsL$kd%MPjVGhg4|che1Nh{5atpY z7F~tUaV5DCk{w}4I2AKNK|mm5MvyTf8Nqw^B_R((eX)uBBCEB0m_L^U;t(4 zg8$z@O5-?-rL#FstqkU7rM)o*x} zrTPRo0QtG$$zWDK1s6M=48+luPi1(STKF`Emz8ibttMNvj?)=IQ7(EoGp+ot>lnKn z!muIw*{vb6d)Q-p%Q-heVkg^M5|J}(c;Ojf;hBgwi_Qc-Fv*Y30yGh=Ca9zd+mr6Z zjVZuHw4sUSGSPbw+m_OW@;)f-DP?V@`2duTlp<+90;S9Mz>2|?AuGg0%#vx%=Kb=j zz}S8`fD!vmz_?^%2X;hRyzp$Q+*bN|T~&nJA|>3bfj2q_(QZsrdhW16hi%F~0Vz5c zm-dCZ40?d@r@&upP5Rx5&JuHL_X_=gx7bX?!zkw=aa&J&aWVASKLg&86+8Li0t5!! zY=3L-w(YH%8E;Bit`J7>vy#+v!IaA~@$w@XThSi3 z5ta|4%R%ZgYW}>5K7{FK!*q2iu3(BxEytA%x7NbKA)9c>WTno#U|TkZY{dHRN}2%p zv$DIA`p-sE(H&ks$mTQo)_gXf%eUp*^Bq#RU4Bx=ydPZ!nbY$AK&wIEY6NtUH0Sf- zZ@;Y{CrLY4V=<_YBEV+OSXgWg{O5r4$Np{$z#g^{v1+ckf)SIpxfy97?L8f>onBo7 z$-v0hP=STHEJkG7EE2E!K!aC>$wl^fXWa5C(!~sC?s|%2OwL=?EAv*3eu;}T-)-1@ zIUaz)9E|qz-5dc-b~DknNYdWw`4KcW=qFlRO&JXwje=tdH@Xh-GhUMR#4$PAul(Hd zuMokNHQy~?PZR>-L3l%#Z(w*O2~!PSh=Zk>I&8@}CVU(cqF*ykmcRVyM&OkPFawa` z!0&(zo7a%C{2N5{4KXI{fDdr{hBG@MbNOb_!%?7wqj3@G^qthAeIXcp+D zyNJyu_5!gv#NHvcGO;g-twJp00)tcW$DR6y+&iTLC>6$qcR~_74xun#LK&+Ra#zX_ z++~fy%8M`%N&^F-)G>f_^~a_f?70X7q0}XxTc7-AizF8ci=3#pMt$;!7fCJ@S}jQq zS6)-*okfxhr7k&KbS*hn@kdxsD0Rxfm;1eGQI}$Jnm-Y}4$%HN-ZSWOTEe?v|KXPY zTu(phmiO?cfMaMlq)eI{y;dXbou-do2XTm&R0}R-kj7w* z^X{v!ar!oR3uglBJ3?%3Qrq&T@+Ode4Ub)Z6)$`X;aq8RG$Rvr!AzBhQkV6hxU4tm zvfe?Wz9Ghh=)Q~LyJRv7Zl^%Qj;QT90eml-WG;H2$>vpsV|bM22|r*Iv|g%C>$JIm zym=iN&6jlyD>#c)+iqX!`%pRRf&5(MBQR?-GIkjpWx|gc%tW665wuTUbb_CEo`SnPels7x=k`3tqdDivcXu6txPK@yBuHBa+q z`&Zi6>@W)Fs!J5!dAP5LY zr368$!oDS!8z^iG&y6k;_SfU;MR=f^OEtJp%%Q>=8?_+v6x2wIYS6W;k#()9s83ht zPp0CMfh%qS9gByMiF~;Ra>bM+*WF#t0H8UcJ-N<)fJ=Y}*Y%%hAUWy@^UW zH)=!lz(nJU?om5JgB=J_dkOVpebZBVtLvwvv>Zz&<#<&T@$72?efzq=z%CSsa=f8P zVBb{4*tY};J0QruB6z|60no$@X*nipIY!OzW zO5Y+J9)POr3NV`+!?Jk2#SHK~@LXTbp?l>{P}<5}xVD!%GASSJI%eMiQ)Q!WM0S~3 z=OBQoSpgxH6W&e{nQ{-~TczXa%tpNk4eXH5MwnCOx^l%i`CMI|n&_^!S|1bML{9CP z+hJZOeM&SAxsh`$N;P=b!uC8oD}ucUzShcFpmbW%c&5@h)|_3w#U!siZ;&uqm32$Y zute-LyZnr4#cIG`=s92{v<%m|z*+A--TL67x4m*yOFN1vZF4^0w4_cymP+yLCjx!@ zsldSgQ6TE{GerXXl_JJ|u1LmyAxJm@YTAzkFW3(OP0Wy1?&4axB!m-@FugyD&;g@` zy!7@tSNng~@<2~rKzy)_0CFf8*R%(aFGM~sS{&SRMXd8p0>XLgoiOu%yv{cce9#GE zj2YgIe7(71t|!OU)O*Qg@K4l}@>T#`n(3k|5fM!mp{Rl>K%$?6Q$NGxYO`!y`%iZ?Vz5D%w8q3*g?SfLBRih>^= zNetcP(d;fyL)6140B3>5_4)?er`0XcT1j(OlEx2z_uYxM3-P2m7h*Pn{$o|tPlypw zu7AVYvI(DHqPh03TSp50MJR@psgs7wQ(A$n2HCfQ8`g+Vp}x>Ng{Lx%hi5Yx?O6%x zy}2)JFbG?6?fHA~g}V0aBD8fa6!k;D%7rbMV-|r8%yH`|1E3@eU~nX;##|d77z~2i z)R9zezP2-dC#_Q`dU`q@5d?%YQbL0ChL~8we{rlh89b)5{zJ?#P0lsF+lJ56+!7u? z1IPA+rNP)S|A_(l+lnL3S5|T`q($T-^2u%UL-4d zD?zb=b`7w%L&tyz6Q(?iR>Noxs~w#GV}S0=VB3ixA(snR#??ft;M!JR71zE@v>L&< zXmx=0*7ANWc&d;YnAplGJ|5|CpIs2nMWo~{@51;j)|eMfP)7G^`moTv22yvlX3KA} zHV;o*pUk`0M3hAzNK5@9u@Hl_l!Rw?_m)3FzM%!A%9_;`D8uqnhP9B}jn>AsybdmX zZnQ2yw4UPY6W;(Aq}h=8M!2*Ur(~jyfwUVTV%~)32VC6Rgh@8VCGGoOs&~D?! z$EBlK$VOWL>B5s0Qc=V$0WhM&-9Pw_EN^3+hrnyKvD8dwaQD=oALdM1p!jKYHUFqJ zo67kpW{6RLk>;sLp>JRxUsw#01f} zs5kz8FgM%*IYZ3(sGVo)QEB0r!aV5CjC4LYN9NZ`=^IPW0VG^&O&CQ~-k|O)Cf<7GDd3JS&iOw|P=X z*r&I*yb~?-D@42Hzrh%mIn-Ee2YWWOgQKdU0CCq^{DP0Eu zr^kBP4suZq;?H+RO4dKz51?;%6&Tpv1Y*vyyCC5n0CrEo3wAF+6Ema@0h2>E1QNo1 zaN$z2-yLAP)igRx%G@0?S4w2=izJjePoQsqC@`>ak)T87j}!^){(`9RNbrI^0MNt? zsqm##I0++!V+>JUo2L9(iTneFlV?i;eOne7*n ze0T^Jo&=RIHfEnWJCS#=+&@?>66o7Q1qSvof#G4uY3B=Gum=H}m?7nkDkFCi!Wan= z7XF+!&Pn)N$?MsQK;IrIFt9@cQJ$j|3GC5|7<-H&8GEcC;Sto~2*C?B1~f54sss8+ z>OewRsnY@F;~sfwM<+>M?f*{{ykJiNG%-WUXF&Oop#6VR7V=po;d8p=4UeQgrwLxL zrvjRoA?1T{CiNj9Jc|0vbK_s4c@MU^;nB>3E_?mr^?AV zaAU~F@Ht=J2*ck#*PaQ+eKD`Nz3!>rUc3=&H(TGZ*%-;o$3S3C63h6(cx5cG$m7Fn zAa(#xnhB$0k<5*GgGa|v<*sF0opm6WKi7+o2aN*;lc4D7Q*@mgif$UzA0w?7pCH#0 zaSgjc)^_1J7+i31IbZcDH$$XbJ_+gUEkynBWZ=oPqvluajS?x7k=j_y{)W`R-b4`d zirsMnqf@|T5WW4WfaTfH!2TA|#WuIRVjA$G(-1kZqQdBO2Ert81_B9sCOMpi3k*aa zCRzy`+*o7_CBkUYL}w$3QhTCvfQw&YAGUhZc7B2yqhC2kpl{C=sD0Skf*0&rfF@>0 z+Y-kAY)d3)9|nG@kDQzEy-@O^8xe?3OJHCx6^Q%mB0<8RKq-5H;01d=potk$K^WXq z5DDQ=sXgU*GX7ixa>DaKL-ANg*bWl?48g%JKH=hi6`^LL1t4O!nflW;6aB6dk)FL; zpl`1c7}#qCLci-23G6QgQAMv5ykM^YG%-V}C`S5Jlmu0D!|;7*!F{Ryc>MHp>b}YN zp&OuR(r*p@8n{k$f5BMlSAre+bIa$0WbZ%hhLAhPy0@bSyK1_!!)rVh#*HH z2m%863PBJM$Xy76Y_SA1$1fQJ4pFVTrH4ltL?(gbE01Y#NZtG8r7eTWW2SEM2$dhFt~8AxAUvVU$4?TL)WC=)84YSv$alMKv&cXr@f3qO zu?rbfFEaqAOc-4X`AL4x_X6i?lyzN{)eA2}RL^8?`s^)V4m@YdR{-F_`jr59!h03K z0QPUafwtE>y{)tk>-uUC57)TCZ!*y}Ow(36wHj4F&8uG`TR3z6R6Z`ioVTYqroDB3 zYm3tYyYvjV^bC8$`Dwb2h2?6jxGW8>t>s@*6#Fz?&zs`R-Y%W1(*2%f4Veman&bE%IYNejPwtru-`aY?i_vx@31ngLY%vRX*M+_%8XQo$T0Deyg+C zl8Qr|v+T0YSQd`YNw!9KeabIi4}mb|4FKV8^@dp9C=QX#NroAcbnj!0 zkK+J3+gf*zM)xw(!J+;EfX=ozSG~sr`wCg3jP&ocHU4GncL#0@_Pe(W^zEGjwcou> z@PfS+(8LUBFN3uc_A(@BzbktYbZ)X?!uJu$>)A&I`u1UgfqhIM`n87y(HBM@6ue*` z05mZ}Drk8XL_+$)NZP+|l*swLWW_TuYnT%mPrpz?|B)1tyHzSWfJZ0IY!1fRqXw; zsGwu*mjo}^7XeMokgBtyszZW~wPz**Z-u~V{WE&Yw*e_*wx)Yl*%<2U_Irt}*M&cx zGYa(WLV;ShuL@qUuK=2uA(f4}CF_<1ty@_Gm!ZM<%0dU@@5o7-%NcbQHej#|lehz42hE(7z6-Yw( zun1(iIDg(Uk@pYD>Dhk@^zFX{26kAW7U%DR7wq2vP0WyT$9==LNP-pzYr+^^B_o3Q zFl^}#TDN{pcn!$jN@RQ14QNx}dIG~oSdtdO3l^`VLl#3S3+;=tNC+QeNp5$`k2AwN z(k8|wmHutD(&2C4mKwI0w((sIT_-_5BK6rWay;81(6?g*26mJ{)aPhL0^24?_ymRJ z1TWYupotk$?Jy3ab|i#PQM>9qVw*%>L2`PwN1$)V2@GtnK*$@b2wnjcBz&6kx&$xS zPCyegr1Dl#c_f6tPvm7B3~HS3LteNlqA>$}7BOD*9IpBV>jMlpWUGJ|;dU|uM=Io- z62*a1ImQi%oyIXOicNA%Qke(|vJ-eQAj9TJ-m@=&Vc!sA_)0}FdJ$ne0l`enYj^k( z2m@1#WSL<=ZJuONs~Hg9*#Mp;8%1kY~d*7qXZMW%cxA0?nkVbq^A zDZB}EKxt=Ov`Mi}*`Bsd+S6o6XM36=P}|ca!3%bAKoc{h7F-irpvFkh_JpzFgl3KC zJ+>b82fR;zkksd(@b&D{0)5*rFtF1FqCTx7~%RNKzf?W>K#0;sVwN(-cTJE#l za4D1|9-Prf89!gt4n?`D$M zvzrU_?G^&Vg{+lL1uxi508Px0^2Kr|!2Up> z^50tUg53(x#0)8ajAzN81m*u!`7MYUdLJe)Er%*7t%pnaHfbM`c5x%^V^+;+Zn%82 zcKb&}VzBuc(s(PXq21wGH{R8|J`Z~{^BussKvOx6@Ckl>rWI(JVjCg`@~f_&{)rs8YdR^% zQXMQu`WV@*e2Mt*=j{wox-PQz-0(w)8lrsMJk%X&Rrhdaa<^?%9cnZmUw+NP1@+uvZC0`?*FDtT!rR?6rb~AJI6M3tq670h*X0 zEh>iZEGh}%CrBvwCHfC%uSD*zB_o!-1Y+GwAXcXZLhj9igr8E%4T2Z!^?)X3NTpx` zKq(}IpHT{0>1x$hI-j+BA1>*DFm0!MC++k$Ve8x51!~>hDmcz60K{1ZxT+{L8j2!8 z>kegUtfyK3VLg4L_4GHbCx#m8>FbE$X`$c+`#PYB8B+ByMWA{lgkL5tX_cxSwxk_S#HG>S z{V=h^$HLUNp9oYtd?YybZ31H7Cax+7^8pGXp{^Zy&YLJBf0!-ShS6asRDn$#?6Tt9 z(XPN?AUi6cS1w}H3C7<=+ah!Wx@n+$LeE%VV5m4J!l=jauYi+(lyRJ47hAl+g{Fe+z}dIOaegE` zu$Z84z6==IDcxWG2D!?A!v*i-GPc^o9dUm2cXIA&p9QzzWLg(Q=&Gvk*-n~seoyod zBip9dy11WtaRr1`fG7;T#_aq$cF8%98%?E+qS;JeA>RN59764 zQ~47Z$wcrgAj9VOWaENO-wQh>FhR1H z(X-u1ghpLR(lW?A62_)|-wz)}6`@WI$8_?maeAxDiO;nDS`D#hu03SNSY(DssaAA4 zlv$qS@<$M0S0EaMk5d@ruSh*OiV;NpAN$Sy#n-s@Zzw&-Sbr7h+rJA8>^B18BmN;s z=)qR@FM=2B*MKHwNSzP{s&qmmgaHyJ?Vo-&O5}_(DyLnbZ*u~%Iw25pMk^B74n>R| zBS>f{IV*UvO*X!-|xxM5`T0q8G)Z>6ekAg75I)*?TR3s1w z6$ykM;}i+(ctvn_ksx7~dUOk3uw8&AW=QqGG>v+Y5Vlc|bX{Nk}yM!WvT~ZNa`vg(Biwj<`69G-kkjlk0j&ez8lv_&VE-e{7+b2O%n%&Ye?UzCaaFmys9X|sp&^xfP$G9#$>`bD1mXxifq}(ad0fu$?dpmI zcCI4Et|5rZT}AMMT^Z2C45?h)vMetNsa)e6igq<0a?78>o8*jZ*JUo>u165q^#Sm* z4q8LhflFl^ZpCmEuz@AZqXGI{6Emr%F)`DJFQC;%V-VY~WXTofLBNvpAL+F^frpr` zaqm>Y`K{|X2UCa7M2Em`04{!IB4kBfNZFRDES}=+er0@$cbJ$dTL-=FB8~~@M$*2) z&S_Z*XOply&5PE>mgY=lQkpMfO6SuQ1Z74ik`D97guo!xSJ26m!K`SS&>8>H`NdaQ4nuAWS#SPk@MDg)KP*zT@z z-Cg7T%5;#V5p_+-PZ*e#pHLYjC25SGvoVfCQ9{hqi(TW<1#@_r9F8$QKQ28WBR?wN zlkd%s&5zFK(YKGucjmkD-R0}5{&^hkqeIY!yK^ocPzcSz180~=l-1h`x{Y+qBw-gA!u!$mp z-Bb}{H&Y~IHy1=5@kW9d?1q3QW=I_b=J9k8B&Z|au+)oo@A>-9;$l=V^;L%_I_)96 zJiD_%-|i+bu)7F^PP;1-*j*Jdc27aViC|}U61-q{1T-;2stTs!RE30alFId*$6yQo zTyaR1#nOtbct|D#qZS~;=2h0}WaR1_VoZqnb+O_%rg@ENrbrsbgqZ&pD?wwL*O_Lj zq+v{md1tX=8q+Lfnk6I+V?xXWi0jf1fOjYEPO2l>h8YmBq`E6ql&mDZuldK;~Ih)%m?8)U2DDXiQ5qd|>FP!r9d7A1(rUWU8xdaIE} zFIf7rkwy<#%H$j5()*PrHqz+fN=r4;=nYHjG}7qtN;@~w=+R1NHqz(;OV>5h=!8ml zHPYydO3yXY=#okwkXEw+Jy7Y(MjHK8>D$J<^rEHS&W7^RgO=84q|t$@f5|v_jN=@E zwp7|3Y5eH>SWc+?2n|JUkNtoQn}4x6PLt-ym=IGlJ(mG*L`6Fz16PglFxroHV&HuQ z+FJ+m`cz_Xux*v6675Pj5`>A#fnTkInaZ2!2#Rg3r9HaPH10$I$GBvw^h`Z{tQRf? zHE96@4BEf;|wt~?$h4_RdpO_C>e!%$zs>HFH)wSs%h!oHXdsd?Uo zc}5s6;QBlc-NFDK_n+x7gW1fW$zn%Pv8A`mFAqR&SC9`%xo*rqaj2#mEhsICa5RY6 zX+0QN3@nD%lI!dQrip)vCII zJ6_{j6}IU=esqhTFu?Q6uvnEV?S^FDDOiY^pp~&`ii1X^pzrdXQ&3kG*tI+z1?WTX z_yH^zEh7P6+MonN%=wBrQ`x_drk4bD-Ce(947C4VOobfi55`0BLU>Y!@$>E1K?H(e1`jFv(4F z0&7ZmNXJj#yaMQ-pDExoQ~2PFtKR9kVt45hNdS&hPn6s(}IP2F{DUfj}RoB18z1JykHLpG%-WkBV)$M z9+`x26(q!}g{-h}^(`aSXGr4LrnzvlwE;F7G>fJfF@v9(moE?4os$gd4Qh1;iRD+fRKO zY7(uNq*-6nV8>fk5|4wfrHM8`3h>|_PWDSbrj8qehW8DrS2P)SPg`+nCYl7K9q*aL zzZl7dqcb$r8f}D>$$R8Ds<^h-(b^v zRfow&-$Tm5R+DXwHmQkvu2EF=ysqWeN@W@6RP>1_QdVBq|&o!hLz1hkTF6}v;|O1X_;K&dZH~sshowd3|-CyGHk}O`nEzu-wL!(f1jSw#Bvl16bfkL&aE`hU^CZbe6bJ{v z?2B0Jt)2&avcL-9+0B_OSmD>Fj2>lj_~mi;eC{{5Cc;@XTcK+Y4Ya z`t9=s`u1l6)ldCY@Pa)T(8LU>pTb;~eu@P3QzP8#ywP5`AC##&(kWHfxmpsHI@@hV zCS8I)cdyZIJ%ey^HGhNa?eBr>WdW0iuk4fPdolJw**trp=#STJ1ZtTs5FD@D0OEBU zT(wM?FS1M|G%eF2iq4{<|F$@24LH+iL~&Lkv7sa-mW0JgA0taWu`H)1Ww~7R_U#n{ zwJetjUa*$}nwTLi3+5{<3kg~lJO@p8klqd<)#nqNqp`?&x?CH_6q%hLy3Y6ByV>1wziliUjr%L3At41A-Uq?*L8AkP5^|m;y=Atu(Oy%<7}p z7is-k7hbjf8Hw$m60W{|TA?bUr6dy6_7AyXx{V9gdxd`vc;IfbqR)MPi98W`1v!KT3x}ZJB|r zoAE=ek(k`VCECLXc|x51V{kAjlSUlU$Rw<_jFFl-+=N3=Jn<_$y=l_g&Jt6T_Y}^9 zE@z1@^~Vu6Q>AFbruVvwLHtbJx&SqJnSujpif(CwwK`dVT*Ao`F zi&Ng?!Gw$(PC0Zi_!<(c#;fb$yPNFgTm^Mf*N{?wbWYNao|iV`+ZP0CJ9l2td5mIXJGUCd!4>n@g<;w< zvaFv8@j8d;4iADM2FAKE*<~Wqq&58s)t4(`6i0fkfnjR4lo4Zu^N|s6V8Tk8NSao8 zJ`WDTn6=nHwp^~_xa(>=Krd@F7IaVTb#7v>SHw2HeN~{^>kon#?8|^AW=QRYaVqUa zLe*aB9>NBDZJpXn_bB~TWV!ZT;p^FV1p4+pfq{KrApFe-iePuVBF26si0&PHTkwK? z3(&+2sahBUQ!NtGy@R!V{pHm$62be!pCPu-v!4@SbL|%bjs23)w_gz)!GQFM!)7kW zF9*ZyAqKw1{)0yc+F8adn%2>myuFvxSkn*-*B*UUjYWm5T6KeOj;xThK5+j_fBF~b zz`e+S6{z*`wcrK&XFwA(qy@)VnFS|7>m#XOtR2g*_ME%cQrS=!8=JZ{X}8mAK|c3h zp&CTX-xkG%&-?*FV7DVcJ#8Z}u-^x)&i^oNWNJ2%m)M0d>-=gD?WTEZ-e`(ucv%C z*hIS!ALU2e1LlhB`2$Pfm7f?f|3|7RJw5*>i5pWX&#><<9|3MW@xLf<2SN^4?HcZa z9jQ9&cP;3<0Q#0UK&#?90nUHoIsuM&!pcBkGXjlmCG>5UVAyQPCTJnW#Eb|r+Ld{q zE8oETIr(jp@M~udypuqHR}lzMOQQ*g&8FmYr0^ky-wfcSy^H~~jF48VHqLEO^L4JZ z>MMScw9QUrXWQ%&sBJSZc)^YVG%-WkHZeYA+a#gtE7F~H4OZHTR#LyTAMA9#$a3ve z;p^FH0)5*jFtFHj4joWBe29lgV3$zDSR7jfitdw}B6z`01~f54suqTNREvb#e#TuO z_X5b(j!0Hi(J4vaH3Yl0CHPUm+uaU)f2TNrZwoBow915-ZJWXFe2`HL^PyJ323&rR zr*~V~-gDZ%GoI9QeP=+{g_MN@PSJF^=$I*Xc>xo*np87d!S-wg$AVdSqpU8mFM9{PE+(%X zgqLrpGn;2|ZlXV0Js}o77}Cl8v*@-lij}n=u-uU>tLqY#<&aLMz4%fhroFo|_BaUa z8)8g|!ELcJqcP2HOmn=XVNB)6)DNT6B9Eaj;!8NgW+x^&k#vmIii0cG>+>A+>8U#u zwofCa$k81rCXP3bPD0+~{e0RUYYaGtAv_sW93rJ%@+1?Tf-u$*qEi6}FqJqBP(H7~ zYV7G>4d<}!4o%RJtOP|lu~@x&+gvo_wyEAXmp~ThNUI_X@0-;G>V30{;Mnj0hz$?8 zY6Ha>kPVcC2A__Rl5;6_*QJBgp`YCuw~ia4EsO7|y{V?>2+bOl3`ID%HLQkYRHG>+4K# z=^J7UAB`9JnS}JYA@A|a#4fJg2nlGPbp+xwcme~vonnoI6^a+a;gQZI6a>)J8GI#4YIPmT?&QI0Z8%tiZ!-z3nG9tjl zfU`M2#JUjPf*NSgh~o(1c;Msrv%i$&ZfpKok#-Kw>BX@RC zJ{vWL4~k$EB7IEYa13!3^_?&Jx^`1qIk1}nfD80JnEIZ>cDOlcezZ4AJTS$L*(yB~ zlZaG@Fx5|xDms@Uid_u}qMwRf6m|mU4g0q^d>=kvK2&64#s(g;0*ALusk0w0hiN^y z9!9R`Auo??E=JQ)%k8u9c-Sc)j+hQC2M&T{w?zJI%xm;BB&I7aFCY?>k3c#+6XH{$ z>)=M@b#%KGQZC%8t5x6k=3Fkx{c~he6=Xt(188MN*OmRZDU(?gcA1d5ESz;^uoy;P zYGM{pw{i}wlg}i=xB!=+zW|y?c`eIZU0}-&V=EpAwe__ecVBXSZQ)pqiZUovFUtQK zK8AwLo5|{$9N;$R)e#><;iio>u$aN$Jp32%kNGvvVCDKKOj@I(ZiIdk=$Q1$vx!## zF}zK_A&eD!gt?aSIsP{oL7xd4o-I4O;XenQKX}sFRZnWhVF0ofE{*%MuR)ujpTL7* z@uS-cR6mMQA7}-O6D{Ec7*Z#I@fMu`3F=4Zx$z!oLO72|zjMTZlH@@LgDf-G%=ZMW z^R?&0t~`V5KlZgKA79-d&~Gg5E`(u&8ZzYzz^;5DdF~0a%JiKLX$<_n(?9(ucRX%$ zEWnzP{`Qw*KiBRqw({(b0)2}|<*;2~@%$VR{uaY#BG~t$h_Sl~qW*RV!3!3HYM7cK zH8qB!G&Kn|e~a;yb1me4XLwrvaxqf%4KXIfXcj{Y^~~yHIiJ8v`v2}@M`*Q-8?)y18NG?%#&bBtQto`w7RNTR zjkIP*_+Oq2fKK>MfTS?^bashtN}t$*U3jQ)v4c-9{? z)ip>J{gO(tf2vCmT}vW;B%e9l37Nm9%q5T8RsNNvlZQqFrA%}^g3%4Qv==vP zjeZRTU;1Jv(&@_%!NG~Xn*1vEF)~7v$JBDPZgFB#&2ZfqvgGthhJ4lPMzBae{dv{G z8MRM;x;*&LkA8#b!3oM4A8y5J5!;t=F5}@bmLQYoZUfE^C#Va?gj~{K-j(a$uHG4o zAI_mumpSYjDEo~lJN+>ge=f+elnh93nZ6NEGM|@8s<$F6bo|_1{gBda8#e*j|#Dgb% zvr(lpLdKzUV8`F^zEJuQ=(;MJufAL``TXASA8=;;#9)tn`6wqJBQ)WikS@5Tp#P_4L%Rvc+!13{2$eo7Do zxel(+#lpb)q)9G8kn50?AP5MDrUXGiI4mUyGQ$nwDU8+{Vh>Ma1(}17gc7Ok)rix0 zL9iPGeFN`V&zXvG)=g|{vO&X@&mMWvuemb$F8vCeBerYEfej(NAMqO)3HL_)H^hGr zJcI)y;if79_oPYr-B{&^yNb!!!G*Ymh~Hezg8PJoJ4is&C4MLIdlbKm_HKsYfa2m)Vl5(rfff?)%F0cqk31VJwJcuEiixlXCi#S$YIX_8A2UFb=~kO<5ABZh!Kw)Qw)chCv%wj5Hdz^UAg`n-IxNs%E3q_t7r3p z44bo<>=C5s8)8fZ-zJHvjaNCOG4a_<{Fo$WOa$l0F@`5|^sJ0AsI?d29>QYhAhs^= z4QyZxPM;^eH-d}A##`+7+{=wwAb#ccGv07$Pr2x6T=1fcfHch-zQhUVDJfnMqoX2~IXSfAFmGPhkkG z6XWqNA2=QkFl^4Fah|6tFW}N&DfOcSLl?m0rS*wjNNgWs7Xjn)^33X@$Y~&#cqrB) z`&Mp)&K!?dAcN!aBL(Vs+zMW>M*y0bAsvrnU4!Fs5_CKcSu0g#JwaJKEol*1wJg!cu?^g?0EzzC+3vfy-S9oNemxuPy= z_MvG?Ji=)a``n$_=MqR@yn^A954 zgU@uj$w!hGO|^3JL66ZeXh(LkP4976-b!2um_XhjDBTV zmC>Y&d>2IhOwrlyIGyjp#H&`D;7v3!55Z9rr_bW=P3(J}*wVMZ5~%jQR`7!TC7_8J zQu|_YhxR3*+SXUA3i_CWc#g>;b$MSR>t^BW+gk*xteXTc*xvw}m?4#gb$QAnp(<uQxnLRHq>s;vDf>&x`u9reSy zKk#>ntVe|}_Ld1$S&s-_unz;8m?4#QjmjdSiL75y)@QgZQmzLQSx*aJ-##NyWj!T$ z!9EFSVun-})qWr}_60x_Go-Sx4oX=h zRAr%OOk{2Gow6QEWW6DLefy?Bm9MS3RGF|3tq7A0h*X0m4)?5$|9jE3qC)Qb!}5w^qG%{EZ2T6d_DU|fjDzapvwGI z@PhpW(8LU>%V8`7)-gT#9Qr z28(ohRs!wvSYnrKEB(4>TLt3G27zjqjNk=p08Px0s*QC?s!c+JUHB~K@r3UL$?MsP z0)4xfz`#xx2>UE9NcaY|94~mmjsr9?Ln;W1fD}YR_!jM>_Fn^Xo=EH;N><-4El}Al zB{;r61&HrY;i~Mg7Djd?D7%qoY0*v`5Uo;mI#U#DQb&;b?I#oYgCYyt83d}` z1_UqIBA|&GQu$aPp?nh5ZkTy4DNCowFIc7D+S^lFG391r$XEEJJ6HQwQ`nfyOo(J zkIXAp1Tt)%muPzu^uZDMaXh{`AVvUKw_;5+ zK0%sCE1m&W4zF(bUIK}7Uil*s@ilB*!%uj@jHS59v?G~!3^?P~(ec1`e6W6ewQ67# z+D2}6tTb^IIwXI2Y>cyGleTdcqI9xv%AZmhHzsLZZ*Ovd`yWB<-$E^mjl?%l0A^Nt zemdui*rXu8#4&u1IEw|hx@U}^Rh9ChFA&|k%$V?ZP^BahVgfd++7ULcH!j5Vr&w7V z29?TR3&^lp$jbi;9QuYB!|wvw&DIA2p>U^f(qW{K~#Ac1ZXUr+FYT^A5%ZQ!aEj!6V7oP^|9Ve#F&!xFzw5^O)lU#i4zbfapI~1F_WM`62h;MP<`!w zkoRmNZx_kw+3f`Sc6)(=-9aGa?Wjm#cT&XIodwaRjZZa*;B5g-%#aGkl!JmvNS}$S zJ)cYD?kgERi(NZl;M=_f26k_Okc(rkS-iinczXz5u)71Am?4#d$p@v75Pl=Y6MNF< zKcC2%FIhc%m_WRRC=hQU3WS_P1quH~IR^<|uw_6KGo*4b;h-E6!hbZ%c_EQ=jAZrf zu>$cfp1{B!ClGQ@5Jcr1EqKA=qgjx{kjla2f^tZxmqUN>Vj|}p$?Dmk3dHwh1P1me z0+s#Qg5$d~fcUNquFC#iWluuN9`*{HGWfp){z3Qw)-SNWF$~aFeiipkd#kU@kI{<= zwNA>)^O(LK4{wP2$*bODWpx5?gl8`48=v44(#XZ+8SEu}6A`(137>F3;F9p4Fk$ox z`LSc0UG3OLA+~f7E=k9h_G@?=D)anbNV$A_zCi8QelB>yE&#+t7FR7R<`pz03Dxlc zpJASdvi<@5MtqOK8|=}?hS$Tx=m#J4iJ^X|p?%?=wS|w`rxE5)#R7-%_0O); z>MXyq;w{j9ogX^IbpRU-_W%t?>#5;VySNStzYiPv%En9SpQP4Y|nm|@=H_crt~@IG}bto2=qoH#0tIrd6-dI!K4xJe=TXUu z&)^93?SldX`;b7$c~}v=_aun+F~1YMVDASsF+(a4vrh^nVMHJEMk4Qd$>~{~p(non zX@NNNQy}EwR6InWPkUAoW1ka5`xqP&h*-P^1&G(6a8<#Wic&BMjeX3UiQLyEqi6ph z(6_G$4D71{A@?;ww2ygN@Pd5_(8LU>6ih-Xg@pP(hJE&1iJW&Nt7qR8h;KIt4D5RX zA?JNX0{ej=q2r+f-x9oF-vl%bdgZMld~D*y$yNz$P-#o-O7?~>=?v= zl@WaZ*+ZtmamhxZRpDrINt}s%L@mtKmX6Z>5LsvCwbjR@ZStL@Y=0Fk@fmV~zWqjE zVE--~8NL?h4HVG%~2J98&!llRNu14z9+cp0M=mKwSX zw8ma?%w#xw?i5U?f8s~hKsnx%I>YxNvU;E4LIdIX+8mA$fUcg+3dHUf z{4kL>MRIy}fto3~VS6a+elF{a~Nq1-m4mi5XHUSX`hK5=Q#Lj}tk|N>;qEU$Y&-~!0N!ezLmY!W%pl??Z7}!+>qHJ>n2|IYR%@Q0Fen3q4 zan&MWQqLlh5ROJdH2!p~_Qyodnv&JCYYEivDXk%R!OjIVF+<7@b5pV-LBFTeFev0e zxP1?Ok)MYZd(bW?{aS@c){Y8EeK~wg=4Zx;UtWFm-~x^{|_W9 z=CJ~?w^v|bw-*RGc*jR?sT~DT=drEe1^azK6Ema&F-xUD5)$XZdRkXJB3C%mx2=zE z@Ox)eLtMkcU@h^9u*#Eaqo)W0`*h7l97lYalwmi~#w z>#H^wKz%&}(P-1p5(M`507;wP9<2VusXinAy{AB7g%&xXVf^;6VHSrPi zApI`hUlN^90~_jmszBeKE>QiegH+bUo=+X52FEBOyNLo9!y9qV$e9h*Y!xn!{^v5;}(I1;X0Db!t zfq^|&ARj2?>{)^r?3sWjW=P9{bp)1!gs>9{QKjO4c@KV*@W*3v;r|PPSZfd%*oy_C zZZA+IuonrUb^CL{u?hi*RR~;FB32qGkp!*VhiYG)sg-sSGrowj!vrs>2}DP(+5Iii z@fwkam0E$m{iQ&xkPC#4*C`U%Unzo5w+o{0kzXZv!CnbyVun;%EEiB&64Lj`^;DhY(;yb(o1ACu9$h%*W!2V7VV;>Mi1>Yrj!QKgIVun;OmKZ3QgxWZXa{rmgeMB`<1oe7%!OgJRmFE49F zzd{h$Ka8mFe8Ad9O?!6E>q zkf7f-;XUU&gOKwrUn59p)f}7|>?7}j9Y0ZR(!qG%n?l*>S$IqhSJo_pw zh6%h_jO~Z_u+yS?e<7-P_Dg}j{aj#RzY++&|D=eqe-=c|^qJrV`$s?%Go%H;ET07+ zLCqxo`C5vDyz=4VpK*{q;$6PuVA^nQ^(P3)BZ!ZH;9Wubv6?QmxP%86+=v+nDPjw9LJQH z>l8ywK=8^>|0~G`LwM~*=krC)OMCWpF>Q#+L(wj*+L!l>Uo6cL2`uL0M2z(W2@m7w_+OH(VE+kdVusYvA44JxO+pxRbe#GHFOl0OOg!5z z(6=1|@xs4A$Q`XnV8O>o4BRkcyB?1^8rd&;vaT)zAN``|$BmBw z&-Wl7H-wzjvj9tw{NQo*rCqH1=vqh#{5cNF$a{^V%RrCrP9GTM$!oF6KGFu|*8O+N zVN9flyebcQSfCnDKw(tl+MM1`>aHNh$M+8e;`;{z@%;mVsJn53Xbb5U9A7&C#McgR z)tdTLYl;MIAu5M$Gf3o2l&qdzT%d0!3B)sgfsivr5qtqb5o4DSMBB(>f*0%rKoc{h zk}*AF8zDj42;1g-$Tf-Fr6i+gLxGr53&cqT0wH(0B7yB!#Mq)D89N||Dlkp(g6#t| zF+-{V=4@1e1XW-QSBKB?(FvT%G@sRdP117p)s|K-JPg#qcDJ+-YSq5QMhfRC@B%j` zEWL&pFIo&$S9%|)ul31rT9s%+2%)T-!e3Y4mSJmuJ8AXrB=B8A_3!9IoD6J}C{+48 zc=MoAIVX4wn$TdMK5)4K8cJ;P6?3ksYulGMws2rx9;tI)Y3fuQw2MW7-24sb_XejW zpXe3_jBL)J%{7+#xfy;brdU}X78K7s1IVyBm7a2Ol&^1yF(KxYMay#3aqI1LtB#&lrwwndn|w#o`gj9ed0JPh@4Od0`GTYk}E5wgxz^1c#z(m!~3+T=L!kom9=NpWI=7aa#d8n-p#vWWAV>^b*yJ#EEh zOlARIz@qq8JS3S076ZMmmko~x4?gpXmIA3~IWO<`XL`5Ap%$m@JLB=nhaz3u%0`d@ z$D1#o4Sn%-Q#%XF8g3guhXn|cKg1leXlIii^Ezmh0~7tEng|oKqT#k&C1PqEq6M1Y*uc#Sg}xLCNz<@q~<=8iYyInvC9N8Y%{vi0QUsfuHZSHHrn|N zl(*cEggLjg4cby?OVo=-K6^_fz*%KL+h;8cyfrES@5q+svF#UWhm%k#N1J>{=a^^)aD0Dhd6LF}lphnV034rc2oM;}T|GFa z0Tkq$$+Y+2_1ICvJ!p{0nVI=^oG{a#Z_l@(Rh_P{hR%6##5?{tIt-aP<}a#d>#Alr z{eXYbEW}n;YOKq%SeLUA(Ko~x9!R52D_R-E2C+Ay$vA6l%;&c99<{s+ffb~NGXap_ z;d5JTzSWm_fiwM*_+JyWz3@*T%O6i$IT!M=rm-4i_n#yd8saTeUIo&JJ_5!k1bDcU z8)D7D5$GYr_g6kaaBzuI1@-^T6 zlzK%|>hn#hSf^@Y$^DyB7c`|l-;`>WYQ}fnrqsQfQcq|~y`(AiiKf(rO{tw>GtE|M zNA~CO{vc}rMCAsak6!S2d+R-<0YXn@L-tDRuv* z)H9k=pJ_^U2bxJ+yeYNRl=?tZ>W59KV+NZ^+psD1hNjeKno@hFH{-i(Q|ew#saG_m zzS5NX&!*JXmT9KLIZdf=Hl_Z(DRt_y&G>HClzLTDD)tXG^E*wcS2U%*-;_FjMl-%! zHKiWWlzK~3>W59Kt1RD4TBRxV{-#v3LNgAtno>(mskb(zj$N@C-_@E@=QX9?-;~-q zvnk)E)I*z6FKSACvnh4zO3kG0)RcN!Q|e<)shzW$@!hZ~^@yg_8=6wTXiDAh|FQSx z@sSnP|M#8S-M5oYGBcT^lUZP9STbDF$*?1vz#xk3o3hI00x~QY(tt8O6^30AR9sL| zP}xC5*+CHy6;V(TTu>pPxPklE1w;(b`*Z5v?%T=0!`H{(@2{s{ufC`1RGm|&_U%;F zma7ZxPpifDZnZkUyRH^nUoG~N?O4WZ(kmPTb~CmQlg}{TTC{k>LRgy>X0`*9&w{PQ zJ20B+?uf5oRL?U4Sdtz2przfq%A`E@Bmr>O1q9>EvO($g7&wHdLFwzE7+NKlm<@p% zV^4p^Yn`C~HGT*w-Ii^TXQt+D^_}pKPUoI17>(58LjkNPM2W7}ir=wiN>^Ytb6-%JgE<60FgKIYzpKDf0OtpZ7rN`a!rbjZ#Pp8D<%;WWU}Cl( zYFap})py4__B6>e_6(oc7A=}qTf=A$geOte{|h7AFx~?q?fv}$lKOe1s@?Bb-a4}} zIscH=4QXb^`K7BVWG|#AbyS zf`o|o!=o-@k>H6kArm{5J_W22C@HjB@INX1Yc7uMo{?8`W9@U!L2f&bNMU^q+(#CK zUM+GR63<^)J5romsckw~scjf0`yPunz&p2x}7jI|fgm!|oFmWs~V77)%g0vXGcbfFN8Kg*+I+jvUP$H zvX^g=yNL|4b8nDv6K^QFF>i=m-3&h5W6jLuJ(H`zV8ZO%(R;JmT z02bX!gC%#WLBe(3P;%qm5V_OL5Fet1ez%#c?p?sBA)61EhC|^B5g(?6&eEB65{vKQ zAXS%+FvuH*21#dwg!^`HD7kld1Gg%fA$~hW&>dxFp6LTd4cUCSGkmy0#P3vvQn;)) z1}w@DZo#o4W$|Qw4n*(Xpie}WufU;q4@B>tuV)&1rb75cRQZIj)~)BI@wvii3+_sT z?CBaTxvLBkpKH9K(h#MA?>nDzz;lTkxU!E(bZWeqPPJyW`|9(6o(qb(~zA5zO%Kqj@#=2^MQ? zJ?P&2Wd~{(*Wgdj(y<5IA%q!YtF>@^f`T8`HFGqocJH+zGE-B`$eq+R0^+BQk< z7P5D?JJ>aJ?Wn71u)6zu`umC<9EXaJAq9KNcJV@Akz14d?b`aG!Q@nuzOOVfZ%O9} zH=wEUPH(H!X|u`s{lmQ_k}mC2jtZ7s5O9Zy?^9H6)=bmuUQ2?4`-;J$`?5iF#to8Q zU-gEPyT==KYzDZ=z5Bin>d-(toSbg2@#7n>FOl_;} zJR37K*S}j^ZPFUb$(L@tm128e27VepQLckUq*?ukHpW2{jBwinLZ=-ZyxMQ6&vCY^ zjEnJRL@3!1X}Y$!9`@yA1I+zA9{gQJX>{%qT@CvqjhjAB85-(Cg8>JYi9bOrZkNRN z)DGgoK&UJk^=Pt!=DO5PwbFV5*)g)EU9jvOeCdBJ{xdHq_*865BDoOk2-nBpQ%XmY+QoQBa)&oJ*Y5B`?fD&+#yy>ktUGh*8+Hz-gYi>* zF!6d2^0IYraiAD)D|3mLBx`%=J;#gfu3VG~Qjy}Na_{g$^>+9RBp3Jc49og*=wumw zPJwm!R(4_|jaekOyMEt)^v2ip51*4>eb*PWN35&B#y&t*vKhy)J>-?vi#Z7gpfXmgCgYgKdV#Rv+Qu%L*-R_^QHTk47vwfju$fo_US0u6PEDK)XQHXV$ z`e(?fk&g5(*5Q6%4hsPr4==;-(@l|8|8H#CNR?tbz-DE?PA<(3!m)4Q?y;jCh^(@p zVYHdNKR4?4{{6oXQ+-g3*`w*CT5k9uq+AxPqcqjJ&6VHQm`p? za9!l8|3>X!d&AeBg-U^`ZJrek>yc5a@p+n7NOiOBX)5~^JX;ycg$pO&rtN^2-Ad>#(n@YhC+F zp}*Lb4#JwL_;qCdNO57;#Flum;ouM09-MX&_vnTBMURPS$wXnvL|_v|zmBk}`h-)0 z4X^UsXc~pr@ACZvB|W(EfuNv)sF@!O3SY#mvF!(l))LZ1cVN9=seS{WN;ptQjf|UY zN7g7yC!#XaU-oYva9_Q2aO&*ES5{2ymmBJC$WWIaWX9+^eyBUTpyT8XWbf-Zc|$>l zz{9}&5ZJ3U+`<-`KSP?md_^C}+5iV#gUwD3=Dr3QCF>!;^}MVJS^yQlR5P(_PKd=_ zWiLcu*~Z(W*d@gDKyOu`78g5ET25cDQa#MVp1EzXCSq8h`d44B04i61SGm$q>0ciG zqDM~(6kX7Ts|sVW%Qht=>=1PbEutQep6$_d1UNe**L}ixE6T&{AV$A|9C3Voo0A|% ztn#~PI2i|9-6RZ$!|~HFrcK;F;n*|H25)4IaQreVson-zg&*sttV5n1)E0zhP3gEi zO%GLCout(?mYHREyx<=gPEY$j*UTyZu(UmN!Tn#_dR?FhkWXQ-7P77{S(DQVx-v1) zRVXKCV6^LsCxU?s&LxqDtYYlPsTx5M6rI3F#(&1N17;B5G+Pv0IjjWC63Eq8n zP*Wak9$J%Dh_~|{lg5Ud5Z4jn%BBV9 zxtHW6{OLp!YB`nBS*g8{pkY%fFal5?z8Um3W=ZehbE1EJVU0@ESj4GQ8?EF%YcNR`nY%3iAgjg-px`FyO5 zU4UUSLW!VK|A26-RnARzYz@%0n)E~3c9TnK*_ z!}2Q(wgl2E8Q-Nw>s2X7&0KXOz^Eac=fhrw_z2-uDc24M;ytta;qNfpDCM`Y@hG@W z491r#>KmE4>NW&M4cR*%vE<4Flxx=!(x9!VF}?E>(aCk|4mZ>(}DkUnkzP+gv9%E_FQI)}sE)N?<)exiPbNJ1M zP+Ul;V5n#o+VyhcDMhsDZ9;CtEc}Rb@_8Ja7aNK11jk~HadI|-ncv50ThVQzbqV?w z1cjrClbII=1qQ^_fc1l(&9gRl?O^c^K|yV95F8Qo9PiCKIXD0CfaNPcFPFZbej2+L zw#n9aG#lW8vTTngekTiLqXT2}@K8MeElSF#Xo>4LAoGgsMPD{#>ib%uRhzu6XPZVL z%%x4|l8N<}Osu zpzsyEX$@EPZnLzk?P2A}m#N*&Ty?tvqlRq0OfjNVnH9p9DK=$hB9U(&3Z!NbY;O5H z3v7ial?w5V$aT^z8p7^yrLY}k>BMqqqP)qIuS9qAtE6LZhjKN$Jv;`A3Qt(Vo3py` zrqWq>FGoi_d*P)`s5vR@6VxU9W>DD(gAlR>DR3e1&Arycxxj+TzA7P^l+~}rZ{5D}5@@Z8jY>3tyjUqLs z_koFuOi&-(Y!@s?=Po^jJxGg$F{8j8r2@z7ppx9s3)Le%ApXsw$1lXTc-RD zGFl$NG{`HM20h>X&0KZ+0i%X&o-g}g;wwasZ!u7v-%8~yK7c3;F75P&xtq-5y^|_6 zcR>`hmsK0~)nWbyUGYy;-ESjVn$_LXfI6SB`&1j=!wjw-=*}3>c>Gc9DfB1xVt@Wxp4K9mDCPgj)ZzMX5EYcUX=#^AxgH zuajP5m%agbyV!k@Dai`hE%EHcrrE2Pa-I5+9Kwcx@6^}Dr1n^S(bhoUU#|z2{)S#j zKIvXnZ&b9JeWTjBu{1sF%M>nW?V%v} z!vqTMZ~>ms5@^VR46S~Bs3G5=9&F~^P(yw}J<~)@MmxMr_zN`1O~cP8rB;uW_SyWm zr^ol#^T*e>v{X6R26yzYd+U#AB_W!70nH90wK`iF5%-zZe**flojQ|N0yWF`7NvK?FV{e+PwdY_juoMC)Ln=bZic}Lf2otXuuSl4{0$q~Xw%J; zB_y65{}@)XY~||}h25-Fw~Z{dd|$j%md;Imi`O=}DZdVB+F&3;D&L4e{p-l)HZbEi zWF*mw!ygJ8OMx1D#rKmYZ25gxAiG&pj_4eP!Rds%ozk~43TpJ-na74WCig3CIn0oJ z69b)2`~WcdAe_?e=U?T=yJ16NC*Q(Pca8Hq-^S3xp`r&xw4CyuJ|_iHO9zX7TOnI{ zVxk`g3xY*kW%;sKuqg3nIvYhdIg_vp#rOcY4PRQaDJ!XSaw~Q6-byj51pZCT4Te0qd@*E? zl@%;M2HOj$Uua2C*o4AnCiXxKGTizRg8Na}N=y`8CG;!!YAB+dns3DsVh)r)rLx?OO#Y6gVuTR4nH`0PT%^q5czPFUT;XlIm<{3?i_hXBBT$$y#yd^P9a;$(K)e@;F$N&7lXk5kWc%?p%Vcm+H6G0c# zpcVqvO3YTUv}E2_{A1OurtK$fw#Q_9RY8&;1&iFymOknviatGpge3z;p;7W-aT{-M zEQ@+_-_6uQL!|hp`0FYsHv(80>7EF}ViZ4#t*2vj zae0JC3tJ&~CO;?OC7s1`n0yE?!=1&BAdzc#Bq1&TccM{BVDCD|h@_v1oR)v{C^Qjf%}H)3A6n zC7fTRNvodMmr?m?=lgW(M;)!^W@>5uz>}9@MaR2_hSMcL^Gx3jp3?$WU?v3*P0y%jk`JGX%~3{?wZ2gQn-5x_Ym~s zp6iH*e(4;rZIt(@cz;kcg!Y-kCud~%Qk(z6b{T$Vaio)38XI4=(R9eTR?D%SYP+Tb zLn=wiG*1E&AIYl~48V(-nafAW~-G@14rAPYT)aGP&L)=+pP?Ho4j_H~9aO zzHJGANlV6}lQ1NqWI$qxq$JEleX|$M+L70{$@U+n8DSVtvSUB-PET{U$J9!xl_b2I zH@xpJ@4y36cwh<-68vtq;5;1Y`Z95!S;`fd`KMYcUvw5ELBBN>;cY2=yP)LQw@r@!yzGcE;fXJam<9Ka zG&r=)ECr4dw6Vymi~s+&y`oeqCPF@|@xeR&av$%qBisQzo3 z$DvhniBVu;wLM1PrZ$pZTS4y^jr|=KQH;7t&1!*XnO5l-&5oM0@R8%VRNCWHn56Io zFJBn^UuhFtk4KP15`|oq1Z1@YQdP|aJyC)o_?Tc?&zkGowjS^wmhS?pS8WaTKl7Qa z;E*i~y`D;Ol1ZWYLwo!Da&^5ugjU~7M8%2_GR`NbA)I0%{2%T&3)^Rd0R&OV2x0+~ z(C?A}tL-RSO&C<>4e#$x!!N?lg&+ViuutMIVRmNf=1 z`ib+@G)U2w275Qt;9;BniLB%&f>X&-w2>X$BqMtyI!PqSDF+fssi3e)1q%`p z-mjqlo8`qA!uSJX+-%Bs)nGTB@Ex7c8}p_t4=hkmzBo>uLF3&DMfP+3p=Rn74Ygh3C^}hOM~mDSnK0 zb88~WYgHmyv7W3-yH3j~!f0Ms8{Rx@TuL$&l8n3|obR$uY#k?r=f_&$=J%GLmrFxm+gc{G zVD(QCzQ8B`7oR2UJvTkXM-eU;SFU^!mw|!!-+*iZQ7g6sQ`c$JRGAc`tUjq7+M9Gy zinMltZ2hpdgfry$MGzmi^*Bq8ePmJCuvj!*n^8<&YVi&OwO9M3yx-i^@+!pWHA^W} zDH$3&J{I9*d+Tut(-P{w*CX4P^wGN%5m?fhC{$e8%vIL`yN!jWxH`JwIioL_Ekn-=yM5+J{PLb)Ah%u(wRQjtERhcwc!c-%Hg%>^5MM_-J30b zx3`BkhrB0#!V(^;+0)}UmCnL@6?FSqcz3ji*XP3%mhe!Oo*v#*It%Yr(0MuUY!5G$ zQ`lFIUyCjuziD)|pI?HP8rCnT@U9eoMUXnAwvsiEV1LEmH`Q%3_tToA8Evez+EA(F zT`0HnXx&%LELn@^Zy6aJ`z#JDD4Ig&qqIz3p%cBNI#tIhXc}Ef#CkpzfQm47CnRwv zCEZCv8e<7g`+JK3?eEQk{t53J4OMbq0A>MKproCm;$281>D%Je%{Rke6j6K*j`;bY z{FpM{sq{Nd^oN8t)je$oGew?L!t)1&?Sg-Dr8+6nOy_#u2Ry*a%uR4MeTVbK+=57T z3MDAlXGZ8Re1ufu?Nks$qo}d;#!}0*Q?ikN;5%$6hNGil!(yBd_A1F}=;XSVk&T#^ zUaw@J-D2xat?+_Wr$po4HzTKhVdDnz->n(hRvN4j(=2IPXk7xam1I49tZ|j!OmAl%y0y|DRyww5)f&8d+P8)cH3MD=Fs$E# zRLM*N8|Il()z{Ram{i%ZtiK!4lUZ_xPSs>O9r8&Y>2&#gefivD8&GA>7kTRe^EsL2 zLuatwhQ9yM+T_t$8l{8!ZMTnSBagcqM!w!Lw2SH4qZp1+jP5)S*~u}CKStzAh8{bL zZq*Jtc@Ot^4;SHK?8MB;eHE^`C_ zj}?f<9Gn^&DTh(~DeS%vjW0)7>5yCb3xkD=eY};PIs<$VeCoZ#wo<6i0d$p3YpOd$ zF^6*!wfJJO>Tl`hv=pd@z0OpBFl!ypGYooy#iLoe`abnw_^VI7ly%&!PrbyBzPZc89rxt_5)BH)ZC)hrI3z1s&HBu%;{dv3iw?cg#)L{BV=Ga$gwk})s zjh3%PYbCzN&PS_MmFJSTVpXn?(?CE|96N~b6xPep5ylg2Ghd~4`UfVFKj#nRMXcG z!j2O(6}{x7I&O-`V9$jJiQgD|WjQdmvE;Do0eHIusWb=8&*%alK5OEcczP<5h z{ttoQtQqmKEYpSYwUnRwfC`x1Zql{IPYAJ&Dl@7Pp}QLTji3cGY z?MW?jv5YKFE$oF)OdspY#C^t&+oR&>LWkZ^T62$xHKk`mHY+O#VeFS2=w|S*W(_YEqL!EQ~iF1+~>jv4-(= z;`4QV)*uBck?DaPFDL%%@>eOY9U1zq#D{4&Qkt(pV_PXs+-kd|J|25nA93mfm7AC0 z5!Jsz*{0 zUI=FF@30svEu-aSvy4)xcCA6|4kJx0RV!itWONB4)Su=P_Y-kn5NogZS}??-|;m5H|bw>dJ=j@Lq#;U(&P)|Tmi(ncpYo*Nk30Jf=`d44$O z9wUUXz7BNmCkl)-(cDa>;BYH1KL%?Jgq)Lg@mQ&C79~R%sN%XRf~)e9^#CIaJCYcf zaLGkw!6vg)+ptg?>ZE#~U(3(hnvIQuA#k4_)nM6vt^VO{$Z`a-sDHSHx+I;tTMZW7 zZ3av3c7qHu@9>6_yVDGDjnLiAX0E!MfKfv>-#U?NYMq3LNAc+QX%*gE)9~&yO5V6J zSaf$AEV+9P65dz6fj4f<;KTchnR(g<$kR4_eR!O9Q+Pt;!rPhf-j;^aG#X&%4gqDYE$-c*fzcw>WSH{ZoOc6s`BFV>~%rsZa&W@e0zafB?@Hk(y&d- zNX6t78DFc~@}fQ)z?kJ2d5G_VIjdO3*1W;w9Ox_K>g4OR zbCoXdiYB)X@Fk}!%#FZL^eo9&J(F9ScEQ46-fR3=mQhczMyfwNQDE9|H^S7p1+S8< z!X(UBgxN%xGg8bwO+RO08h*AgvUxk{>8ZyS>6z0=&lV}w{imt~z@G`;mR94JnaeR! zkatE3=9@Oywg>9mzQm)oVxWFo`jtnfW$TAlehThUgS?t%u;hMV(63{B-^^9_FfeM! z=F8Y-zKjXs$6T9-@gqnbHh!g`Udcv$el0(YBMQO!N^*W_+!(2}nlz1DTK9cvT(mEZ zRysRv-%e=TYyFK+hF=Qsny&yAQ(=bVk0G#i=0Cz_HTP39SKUv5QA0MLo}3+2 zofV?(t~v32XUg|iMq6;dHpo*F26ep@SErb{>YfI26$)R^m$QE2D@6PnJPseERl0K) zX6Z<&u{cNE`Czf;QJO@l{6`|a!spQ^kwHIRae_mk!d2w`_W`D7CDY(BH zWX;#0KMVb5GgsZ8fKfv>pJtrPQ<@3k&qAj+ZD!-%_>ts?p&MCD&;NA6m)GRaXQ?4cUBXTl>(2XbY`1FE}m@uh%#f+-!ro zBdg+OnYrp_0;7g(p6fQAs}S+_c+7G%OY`GXo&!c-a8-l4RjT6p&CIhyK%O1q>v?i! zNa-SkuD6-SGf8b3$#4cR&y({O;wePD+iG~8 zobue+=(%ISpl_ddGBfY!0(nQ5ujk1b3-J`fx6f1JeoD%7Z=*-M)}WVjPcv8D9>Ay} zo9D@S3h@-8P0oF?N$~jYNYB-qI(M0d*K*rqn%fFB$e(U@k=cPHrU7{lt$O;dROSOs zKF$jp)NK+Kx4)S=Ck*7AFkde-XEP+T5b@r*v~9_?&osHRI{fZ5yu*xl!5wPQr_mv1 zuDZ7XqlRogJkDw;JR#cBDC2o*%JV3rNA=O5&&zk1x$52yj2g0eo^SL#g_zdYu1NVE zXS4-(jKQKi)}XI*%gtPMM+2jVY@YAVp05zT&QZ@ace)eUfG^ui^i^AZ9&6Z8Zc2hT9(pt}0}SjiWu;vAO%&rca*l0eB1>>S)ttYjv!C zF{hz!H8ra1Ob*2FNR@3uS^$RaO9$pJ@8c+Z zqyccN0?U6?4;4RANEN_>K3Z%t%W_#%zh(rBNdx_I5{`ruvs3Fac=X#9nwRRg#u|Qp zcS_h$^4PusSA20SrBc67^Fxl>i?`DEaHHTJ>Fn92ze6#YS^j}#9q*$*T@ilHqwZxT z@5yEHA>{U|=S8wSJ|3>C{yClE1yGvg{VRnp3g*^$+RnEHMwEoq7qkTvBi*PD#DF9s}~jO^Kq$g$R{e+=VGiyQC*3P zs(~Wxca_S?9jqQ`r%6J2T%rC;6_(rOun(_cLo?D9b5J?G{epws@tQ)i37e4j$q6c8 zeqNPnxin|#C*tO2z}sXjS-CqM8S7uBrPn*+=csW)70}u!S;+fan|*!MkOQbkn<%s` zU{~OdH&&gZ`WOLVW~nKv*CowKe3lH4iRihhM8)J}Tv0LfJZ!1Pw>M;|KlA2YSTbV0 zhLN?MyL(y9pn@WHPS^;6qjSTC?Co=Dxe#^g+>GFLzD5OVURt};611h^qt$@cy#uqj zsM;p!##o9+>EY{Ct{<&#d4al6CRC=P9Rej+641UPS35}@1df0bSRUI2mP?vfS{pq> zdBtKNc~x-lu}snzDGzV%>_iQr?5)X>?Q<6YNW)TYaDrzZ&%goY5#u%)vEmrjPer(d z&0TWN_mw)AdnRFPE%XC$*Sf>|3>Mw_22q?e$UN`;X7CF;=b5?c-V2NxviVLA#WQt! zLS&n1>O-||bXFS9MMjE_oI&1lH&}8XHyGbSo98}a=BoQJFlxx=+3)4q3lZOjN1x}O z|Jf=3ONu?Lb~`=j-|J?fDDQ#$RKVb5j0S7%g}98!Wo3 z40^too4M*P19BlhU(a_R&sPY~cSX4P!WrDPHz!>DVZrWj@yCFh6bCr+G}roOot{0H zFnz}Ayk>)(8!1Ot1cnQBF0E-+ol9S)h;~sX(n8s-B1fUD097dE)Ltm%qBWb6t=GSY zuTbaDpBBbw>GAUHEOg1geGO0kLMhoxDr0ldNtWLj7nS5`W)?dV<%H;J1dE(w6 zFWeiXOn$)(zD$13%vE=hV9mOISgWx2)7Rd+KmYRKkgLD@?6LkKU+O=_lc!8dwHF}uK^;AEt)-U)f! z%rtLi1Zz<)-luZWPI&UeES+m3K1$Hjs8ERTr@HqE;Q9D3B%86q%C=ktLR)fdO4*~8 z+K}IcKgu~l?N~0pk_5@-c(%)p*$~WOoOrikIG{TPbJ{e_sOoAP)hhW($Ev>df4L$& zTd7cI<-Ov1l?a*>y$w+>P#QF5ff_xCTgHIN8F;GH7E?nokl^uIz=7m!zNsbA_%Pi$ zdaQlwV;oriZw(*CJKIz14@S0c&0W-PzCSG&U$d-cPsd=<-D|MqzG{$iai1A{xwzZR zRreKO)R4`W3zWZ9E`;#qf`O_co~Sp{@NR+0qH)#>giSk`>1I(1a=l!34V3Pfns$d& zsdwM14?8pmaacG*G{BsD8zO7$?_qqj$%7EZte|;&1!}ZC6a_qUJnVTnz-!pOZ zWp3!3Q)7J-rMO*Ufi%w^uxu!}ZyJo>qZr(8=BoP!Flxx=vt~b^HA2Ma;W4XM zDgz%(`8{H^1@~QpMfW{}CHJsF67KtE@MYjTX0E!2fV{ZC*9Ww}4@d}K2AE+e!3**G z2zI2W5H^$#X5&~avpFStaz2hrGTVUewbNK@hE;D^_GSuolLL%2X4sAy1Q=jvSR zIw3|ji(#DV)-A#~d|B972vmy42a%CsLq3xC6IL^+<$)=}pj%UhN4UDqJj&KKunik) zcoetuq+?x(g!6Y^q8Jy@H&VXEK+R)ol9DY-8H>^UWpQCc0gdh-HY9MHj-5o+y#p+M z4Cuz73NNJJLn%okaiJpNb%0?F!C zl^bFy{rzT+#0w#%{bFB|7ubTHb-bLq4rK8Leh%BWVL51>|6X4~#jypr4&2DLTpq>~(XwNi!BolCGf^}t(#^*fHTIY*L*1j_o9OBZJ2`)gn_d3p=jCCr&9*_8bO@RI zW=Y<*x-cX$PMMmr=~97DtNZ;tY!v+xpX{O`%10{77b`+nSh*28xfY}HO>MHqhx&0R ziV5!1a9zB$r(t6n!^>L?uaR-kiOQD;uY1V&&~)xBG1e(2U&WhkkBttbmMQ5r(ZvRP zD}l1~8Qs9APqz2{5%@N*4P1x9IsMu|ROkLi&pk3PI@T1{cpFNW6pj?j-4kV76)6ns z8Z+Co=Vtd{U|V0VR*Y+l!;vx;Qnp>Vs;Om|9wz zN=CHZQn>H;~1)gZ~b!z8eQ^ zwcsu1dUF$C}!)57ZE>=Z@Upd@2GKq9WZ`CbD%CW zS6u}dHDvSM1iF(NDhLsOTjQ!X#53R-k0Es-E{c5{CK6_CF6@7b7!O~RP83Fpg~3f` zk6lO#j+I_VCRm!%G7YQvUDujU^vUr&Yqkc=5kKqUhrz-6^14G4oi3A5YmwtQRj1YXBIJ!yNj6U5CW5}+YhJ6LPcF@kdYY$Pw8pSjEAa}Z zd)87&aAF%4V}=nmC)QypMs2m`Qufxa?BUsG{Kq#SzkNNLsg7~q9DBF9@pGFRE4P`z zOxBvXGvqg|J40RrTB#~ug7PLPVnK6P+={`TS=a>B;!qe}5+=t`q`WMQGk8^_-=o6h zZ8(JW_b7S8X20a|BnOAMim2Bi=^P?OpZkAr6+d$i18IrgqBmE)_E90e9Y?i@=&9?;DrOM0MDd*> z*{{tLHOa7);jL7X)RdS3#MT+Y3?R125N4pkR*4*58s7`w_#QEzKfX+U&r`BH<*+Uv$lOb{B|lH)Sb^aOL=@Ztl5!O2%&}&FtE_h z8{dJ@MyvJvaakp^AqJDTs|v}EKu<}=`I*g-NPZGNhPkG&e;|H9f-9ZFAX@`%)wO$? zQWQrAN~1g`b_A5D>-?(eK#IMNpqTFMC{T2-SHL8fu!2GhX81SAu7oy&!fn3mYPWKg z)*%_}w{dr7=Ad#^zmllic6072uY%hNc7*yyfs)%f4NO=CDzu>CckwXg|L5%{XOIT* z!z6%S^IAH-3!am!)VM>jpC~1!8AzW}aBM19I6h1_Y8qWJhRJ9yHO91qhE~bN>j=FW z25Dil`;r+E{+yudR}(;;QCnqv{dO*c;==e4y)e1NPbz1g4aaNk-sZIelgqTgboZo@ zz7RI{M26AL!p0to*>$iUVTZ~rpTm;w)9xil>BcrEWg<(j7>f9@@HRioI<@Xw+Ex64L7Tx9syu}s@oqJHDvQl(Fs=kg@{?`NY@n#f%N4rfp6$X46SKn{3Bu= z`Y!#5W->Vy0*y7!Y@))#*ypj>b>41YoEhn`dlb3dWsZLco3TeT592z$Vt0IVxH-}t zgcYZ96xLZuskpt3+!LBvpFxUqt`|W0k?-1$X zX0E!!fKfv>pAaZ3DCsaGE@r^wgyMWI@J zg~<5oPG7c1gKOYhM?;cruh4x#bS3vi0iGBK)W3-sU4^J|6PBUT0eear-OH%{01iVv zPWa9k?dsb2)#iow^sdA(8SN%t-;%FYa*5HBt|i~b-d)?UBY6-*U$M-wtxfRf9}|8E z+-QFr4)^bXO)sN=Ab%Ns+3pMuc^PM$>$AS96x4~+wZy4p@4V5;P106()wd*&?hblK zlwB&xrr0_bb*2YW3%qeqOx7n`FB_UWc4CFosYCP1mkrHN%?mQeHS7)9%ZAp(aiP6% zds&P|P$k(y{8T1N0mp5E>y)N@gOgs#&BQ%-fX46KbhEd+4<70qDU;U7zA|$ntCHND zuJ2|2?&s2cza2IjkKSgGtyF`)-@Vn$Rd)+8YRKlZ{WzcPLim0c*~GW~?kcqetyzZZ zertt>7UeB!N{a3ffs$W~IaFq@?ERM-Ql<~`A}BwCau+;IP}2%CDC|OW-c=acHjJNx zK_SuCzp-88q7W?g?~>((M8^1g7)ScT_yzN-@BH}Rs`A?2DV+RMR#uY}$zqxSTR*=X zc)g|BWVMTBC-gQfm>323l>PXZNf4XSdHI>Z+djF;4EK$FOL63 zOvdW;K3?dtbIoW8F$SfbkEg&`SgRvCPfmeKzqSh_0zI+}+9_rVco8Qttyw4&kPDLySxdCYWT?XG4QVJ+ z!7G1%%gj~x05EFE=EFI~ha*I${GG+;3u!oy7%AuC3>MuF43^vv4HC|e%;3ZMzL_~7 z2jqYpUmwo9d^ken!_i##i)lDNHPV9nnZcra++fK)VUTbpyrJZt^oGbiWd?uj`Kcz7=KmGn9^n(nQu&G{-p(4 zaKANJbiXrLa=$i+%)j!6lKYJ}MDF+APQ4V9EW}AhP`38%pkZZ;0IA%-}=*iy>pAOVEPqHc9g`ebX`+G?F0+wsS!vv$~=rgcrsUgJD%qa^Pwsk~jOycL6- z`!ZN^9R`uN+Z#%*#~UKo=?!H!(+pnT$jnt&0!9tlyu2&Dyh60eD;aK0Wf(A8o(wct zbaM@sT-6{l%=3nlo9_*g>-UDT8#IHLVUC%rt``_JWb-mOFM|*{8FF{D*z9_AW;p&6 zl4?QnQ@$hf<7=gua?Fh21VfL}{hbj13@4tnemw8*6HR|Y=lqTRiuOFTuRUwcDrQ5~ zsGmiRtlXgIX!@D!o9qQQYLGXV43^xmL5j;#Z;0H6-cWWmZ|HCvnZc()Z04$44;VFM z^J%~t2bCKka%m6+=aCP$BSUc>=PX&osQ4>4S$C<#PY|8L%%XNn>%XJdk2x96HtYoG z?)p+%fS%6eNk#b?;u$}MDfu~{j?S_7kyc|1-oPr=FF=$#lAZ8fPIlotSpPl)1wC+r zGC;4#jkD6k3xL0r-?WUXZNDQ;n{6#loEbA%bgwm7a@!fCd~9z9p8#8%nZsm24wLcq z3BZ8{C4dlF0%Un`XBy6qMp|&MH&}GL7%aJ+3=+;8y`kjZ;0=-6*&E7kS2K7KUT5a2 z+W{CgWb-1RS1u8R$V9NZtGe)|REGVGw&3nRz1;$QzM-y$q*&8HC8mPzWwWhA$&S!ME*0_IQ0I`4w3*SpPNOt}^ek{sx1e zhWr-Ol01g(n|OD|K_g4aZn^eEo$WVWpwV-wl!8nlAY>XTgkA3elp;!{XmzF2u_z$ePm>f*qjy3Up!p z1_m=ji&tS!es3)76oKg3SICl(hLnO+TfCeL43up)(w_M`=hbfPz#FI6xaWxkZK*AB`{NY`2C?x(^`+* zYXWC}d9*R@WRX|#Aa#O!EL^42$ehC1fiRB$3`=`UVT84#f?Yd;c2w>7sC;pE#}qH4 zb2`S3o<`JRx0CdA7;Rs1ps!S0x3}0^y0~-vFJgX|;*$$zV7Rw5ctBsNx71rCd45nH zJfN%0Gus=VKS&Z;c~(DsFS1JS{H<0F3+`Usm5*=7c?7n(Bi$hKerZf2@%-9TjGBDS z9q&gv#|f0&u>ikTqO~&yJQkl5Od!s(8RS-bgYn-8&?RQBy5oUSLpCogy73ZLi1;6P z%+g7G?@Rf<+h_~!RD(sg!l19q?=o}ModS#+vU$Gf#EY*Gxw>2m&VldO;L8&Q#39*= zR@YuV>hJ0AD~8eFoKpNI3h&4|y`^aEY7!-3^ib)GdZUrqy;1TUB6b0v2U4pCGVhD= z-tWDZd&?ueG~V*s1C&=-`|$3BS-GRLw^-j>%lTua{=Ip$0ZB$#-S#G?Psu-_;cQHy zFj^R>%^0jVsGsqEs`59m@u~bslo8%IW=6ZTqTo)Dh3ozSn)1k-P1Vf+Lhf=nF>^2j z2yro=W&n|~I4N^50}d-Qgc(4%3}FTk;~ByXAWq8=W&p7&LzrO~E_Qndu^VrIj*nG5 z)&J2&N5FV-H(_Z5UO`o{U`B9R>~WT9y6X7XC=ME4k+XS$;lrJU~QWqPuOYg1Q>L)0I0 zEJD}B%ErEoVR;9@uu9?6h1V*H8Prax-`20Lt?MBkUIy4kH|9j<6Fgo?ksRsGigWT- zGh0Z>@gC3RcTw_AZ$4V)<$SpCFX8GtIt(RDeSsF9jL)EXRo#+)*;*zA^Kg@y6$Q!6 zX)6n0FrI{V$~}d%f|+XuXf6aZb_p$IoboN5FYl9XMsv5@Z^?yK%0zS&3PtM;OEbBc zk}%#a`3`Nv&WvW3J^nKAdizGV44)y+G=66qEV;7`M(%8}C0*q%D0e}*3uf-`k^MFp zj*nCMZ9T|gn2bK&TF8)#Eo8)}Nvv)0jjl1q*cL%(7f;91zF?wu@zG{0Nxq&-Kqz0l z7oYANnM=;X&Zdbdvdc@t>p!*|FN>9H?ML6Awr%GiiPiwlH5kvpuRGh!Rd*IJYRKl> zv~zu%CPZ8iQ+7V~qBl?8g9Fcv6%tO5)Q0Suo}?%bzR%f`@K{c_Ho62FtrE8GDlRf} z)s=zFOZfV5&hz025wk+xY?CGT11X;lqb;~jgPvc-%vIL~j2g0eevEk477Nkjr*f$G zRlWtkdN(eWaQrkPmdmeCD8G7e8d@cnn4=uEWR|$r7M7FQ7`i#p-3#o9&r{xUE~1vq z!JaPF{WQN+*Ibv|C>TCCTURawFCxRYks%yk)gC$rq@sEs%!XFUCFamWZT$3hKb)3| z>iy`2zcI=oAGGEe03Dt2MXz;Mjf?~&H`>I-oz#ut#X!@uxVokIU!EA;L>f%+Hd&D{HB zj6bCMf_lh@f&PZrMPg08l)GPs{GeEE#a5H3)AdF?h^3uvIhee7P;!U1Rax5_=0fEjzkp!kizN+8#5gbQazPD1H52?-g_ zEW1=Vjazi-#dnl%l&jXhZc~f=86@+=_JUofFcg}0W9T>DxU8~LX-8cNs;kY@RbZm{ zD8>riWdl9-?JIV49^P9#d}Op^q}(||Ycg6Y4(wA~1N}&HzOPh{mr$PjBIZNA(R^DN z+6V20fqe#sPw~RE1tVE(xy{c#?;~E{C0=z7mD(QVM`-vs@XB`}ejmbgmHT53@l-|@ z_*?M4Nk(}0Tsp_=siIFtoJfu8G3NL}n%7q(DJQ2=97oq|GK0#s0pXxWq z1K%SpG$;HR383-7#|`>9&P8Ufx{m^*hHSoHn<_~Y8DNzcv94rVR#Z6w}@IE>O1%PGE1+5#Sz>`1izy@iHlELzez-{AX_wZZxacPEUD(yfqy z?h0eDWmCkGWsIKb+o6rRD@>Eda0@hdv&o4|LdKiL$ZL#>r$KH1y6v6dy4U*kPKlpL$0Z5(w?%sRK#4>(SeaJz00D1w{& z^eMz!h($L1eSwbtDkWayYQByBplO_s@gzF?FmPf7VkHWNH0wQfSpRBy=%`@{E>HZ)~T1phYW}R|@skcFKoZlyX^3 zsG8T7gZoNBfZ((xx8sefekY*I-^NV87i>bAJ)#f92vlq)N zeI*u*c&5BEy3E=b$r<*R+?NGc-ock=7Gi2{?Fp8RXk_F-4PGfYS*5Y`t&Hg4#4|=L zEp74M|D80<7N<)yNZa)a(ky?~(xB$!lw9Qor!R|Jon^8Kkuc?r#y^kbxuW>a``6BhO8+7_2jk%7&t zybSlWWRQ!O;dAYN?v8} zr`+k-t0ejKO(;s0?kBign&Q?(Z1p*_Hxo5^J%qm4EiDgpKXI*`^M&yM=L_b4t?bTy z<`!RcCNP^zkyQghaoL=N4yhQ2BFJnJ@lK&{~X$N-rdNL zH`G)X;y21~Hs0XUrJb>JA=y?`n&GAOU9hd%HSdMKZE1Zsp?A-tmq>YuZPpsDd&qUq zyzAPmF7GA8()!Pc^Qx~AbTd%8J1s-tHX*lf}>@8W=TX^J9KSwi@#b5u@+WT$|gHboxQc=SrjXe6BEa)m;vZ z8nSsloEH!uAv_;q!j^>bQ(rnnANfP;OCLn&O0?zI2_PEW`a)Ihu<;#`rdsKKfC z(JxfW_Cc``9e3t^2%q3QIzH8CC zun!h($@QWfeHph#_MB@tOkZ>fBda|t^oqXw~iP@}GcJ&rV zq!4VAPVYZP#k&??C6a?6>8s4C#0O&--8R(V#B#N_;vQ4LK3mxmx*G2jr-cn+``DTh zoUGF8=Q6btzXdkz)F`pWzs)BXrC%yahu}W6N-ps=Fx0H6OU;#qX<_wt#fRd_H&-om zIX(=m=FFNmQC5P*6o*auzX{81?AcHArayz@n!|iMrs7$M5BE$|?^weQeq{CTCpa-5 zK7w29F@=rC?fZm%C+z#AeV?-L&-uo;QhbslV5S=JR$y8q#EKg63xSnC<*OQTEirvU zr8jKIff^w&S*0QLi6<)fyd6IEckt;>j^s;`zl6AucSm6x8fCOC$!sE8eeD-~0;K#?ySl#xLbDIS!A>@$$X2 z{s;9WzXsi+^d4A4FVn^b!H=!{{1z{?+vg%gSbqj0z26B(ee2JYq|QR9KPcm0&FNYD z{?5L?x9=b9`$zjSZG!yIeB;}d3kmY5pq&6r3!1{Dp#4o?<c#SK z<=?@^;~)zObGpbH20y&|8|l01A2>I?&#!YothDL53Ri#|YX!KEMS$ZS0uhUCfDR8T z9`t&^6i;ryB_P0V-nNlO^0pxlI1wXq6f6Xmd9bSh zy78OSq_SVJPLU!WpGH!_kfI$nVe-S+^t6{-M`9I9RtAppZ_@B=IF8ZdCe)|JmeBN%K^KHpCHj~1m|a+GtY zQvS1@^0^kJTym82rc%DpPWgO`QZ6~l`BN$X)lT`(7NuNrlnbU(zSvIrLW@!^Im$Jr zQvSQ0l8b1ww3SPaa?PoflkJo*wkYM2qg*(Z@}+jlf43;*lA~PIIhB+hg?v$%Y>~<( zPs;6HU~*+)MmyCP+k|*t+N?K?qozWtdOP;i6DrvEuG;EQ| zB~LnYDru>mw9q1zOP+MrRMM!OwAdn*OP+N0RMK)gX{kjjmpp0jRML)i(x^o$mptj5 zsid9lq~#W=T=JxSQ%NiBq#Z3%x#UUvr;>KHlXkX9<&r1$tyr!gcDIvOTBLHxlMYM` zwWpnwwlu4)a>D+eGfflJ;@}z4|C7st!I@ltW zOP+L{sigDUN$0jm<&q~|cPi5{3W9AC{k|!OWO1f4%>7o{? zT=Jx~sibSSlP+$N$|X-a(oQ}z)Yl?Rbdj0NtcOYWwv>Z&7y|#q=$AlMi3T^{kCoIIbMiB09i}bg zMzChs$D7zuVH_|8E&`+7~`nXEE}mjiE12uxN#pH1OK1)ul8hbuVU z23T1wUb;uvkTGmX3X_U^4>2b1Rp{sO(E`HzeCS%`v(P);Mk4Z|vw%P-wI|+Uq04~< z1cAxwVJx?r(uIXyh|h=5#9USv`+Tq?JVl}4(^bZP6U)mou$=pX@y@DX4Ig%UBEBVN z^8OqfA;iWU{9A9xW!nb`MVy85dFXS#$#HZ+ zt}NtPeGp1-&tc~cF39D2GC@ILm5W=dCw-b)VsftNLPf5x9UXrNBon-B4*z4y^}G1* zL6WnwU_Xj#@K*lXLudNh#LJOcSFjHr4&jP1s3zXo96p1$aStP|jg9#v|3cQV zu?faJ_TON+#YWVsE0o!Q0!Urk43Dg9eUw0>!2<_hS60EO*Qix)3*+IxufgoZpK7>` zK)-vfw`~j9;t8ggT!)8oU?cV=MB}9?Fl7!KTVjttO5j#VRYlT5D$O8CKo!|eVCCj~ zRYllktUd9Rag_sAL?Bm4MFk%fQXepv`3@f7WgB!7Dy)wv7F)~M@L$R0lvEi)=v>;p zNFpR3gQZjYS%PeiX6MI+*NQ1YbLhrYFY7&J(kf>&kiV}yryPHhI2?Wc^RynOnySM& zit1!EO7hn~wYs>Awika^E;#RAOD<7Dbk6CFFU5d5hgvTy7`>ft2e_prqce;-wqASU ze(FZpkOSK`D+MR3H)XA-6^+jL(_+~X_ICJ^Y8hgU^_G97u=@;}(s=iE&*}E9_p(r% z@?L^&_qvt@-C@k4TF<0}BRH)>)}1TJY5#SbNqxE4~sr{8DcVU5>8; zi;mz&D2*f=tC<0N?K;ocT+uPgS|m%>8^vrkN`}-8ZvClHTcD za62lQxaJc$|LIzuV0cXOHg| zvtCYP#AWbe@56L^uZ59xd#^JXe-7ZTH*?i}9vC%b^ZWBR6Ab(FLd0L-LojxB>k#1M z@JrQizlc+^9&&_@8|C$H1-2^)9V*ANyuoBjyZvNLdKv$KKLW=GF8O3r6V zJ2{$_vy@VCLXD4a(vLfdAf4ewl#3^9Ebt{<@bG0oSF|O&=Ry50%suRk3+E&nX4Z}? zPEujMTW0aae2>ic%3O}wXOGH_Yf-xD^vM^4NR5&$ev+hhUzO*EzKI5Eos-dhnCoBT z6E?oiSFa(}lKV0B73Xjx3kEi@HENKooa}ZF2(J7V-#IL?--`E%H-!yh>4;VePFBB? z^$a3{&4U0b(U>l;F){{hu|I^1ucs|^NBkYI{;Z2MXrBH{Fi&*~JzWJ`h`%f6tdx2^ z6qVD7w48nqXO+{34f=BWeKS|xBS5Ze;p@xkZN8iekt?TBKxzd~S{^-$6N&Q!kwhb> z)_;gW8T2Ed`!OHPkIDQKKIP7ap(dlBVypj*5BtmZeZsyIeB;|+E{zqI()dZil~3_i z8sCwov9L;G!CV@PXlfc8BV*8(#x3;JG`{@CW*RHlG>v_oVbaUw#$08n0O zMj5ef{bd0Aq=t1*OBgJ_1iD|PmS4;Ao7D1KS)NHP&jN==JC@j?%vvc{-EqGIS!P~! zr;_RSSWNBo4;WIlQ;8w9(?1HX{1acPoqh@LPy8BTc#{*9)J_E_tDKwl8?~ZC?ex!b zMQ^p}{(?#Kyq(w%#mHtX-^5zYzsh9wAo_o3dk^rqitGP-SxLJqSq5ZFvcZ4}Aan6b zrWc!HOz*uX5PE3gf|nAwdxa?>^iDzv3B7kh@4Y1?)C7W*1d@;t0;E71Z2q6`nOkOM ziST=$=hgFQ&g_|X=FFKhGiS>S zCH-JUx5QIfT0vQvwl;Hz%&Y**m=iU5|H#p4+ofkVwBC_U+mv=n*E{^A=M)OtgWHRL zZ=4aolJ=cZo5Kh(Z>}3b-1AWzwG41|MMqya0JbG_a@H?xg%`=|Hzor|5E)J#1)x9d z&oPIi@ebB-Y!gbEHA?GgD*6c}_03aAG5VId$KVDJsa>}xEIatLmT4H}%6f!F%Vb}r zwgmsOV03k<(8ECfj@3INhvddp>PM*85F(4TDnR{O&|7dR9sT+Yh$)t%8`13COb4fz)b$+warna`rQ)9_f64ie zm!FvlSB~*FFm^Aa%NL}CyYV-$q z@?~novTepz<)DKi9EDtnHfm22wY#WfTcPN{xvfyvvGsu~na^P=uHz*=|53@v;apb+ zt8OpoEl-S*ZMHlaKfK_yq;{J9G!}I~+G|V;N|KN7~1l!Q<}u72iGG@W0w;3XJRjXj>qi zGl&g|9mkDle#cO;R8hW5YhSe;U*90x^&Mdc-tq0hMWBNaL!4bO(_Jff?RG-D zUAqm(5sfo1oq+aCPK_R;134W}kdxCKT?rNhD>-%F5?Ziu%Y&J&6a6j0LW+>irMU8c zF4*u%sw5k4TVWyeALbS8R_74_uPy{I_MbzZjQwWS?2f+&l9k>?0>$V8K=3i)@x?+! z3e`X-XsZ#_*}Y=)8c0`s30MXZB`QYW#SQwh-Y@-Uz2EU)BuJB9Dp)@sSDN%G@&6$D zPcc$}5{z+V@xKlizW%ek=3zmkc zG+uJR-N-RAn0w@F=p0>1*U*bW9e>hqufhrD2EKk%+azmQ^|?~vkA(l;bo92xBYhrXmxp4y2Q*LJM@Vlqr zQ#$NsHj>sa=ST9@;4AQYq8N0;P2MeY!nqpU=^Ucr7~8*sp9j%61Xk*wP|UOF?i|7@ zLBUs&lgZ4=U*T~BXFRzY<4MBw`!V|2tywHgQzxept{gQ2$BMh!SW{d@mc_`ZT`H5F z%7luO9(SnNF{Bb-qqvSC+}nK?h71pK!xJp_xVoSxI>wEP6CF+<4zzZ64zJ}Z%?az( zDXWX2aJtjW)*wzVWB6liv?8sjuJ)h@hB=*+qbor#*mP_}eH140|2_D4BmZ6a(?7`$ zdn00$j3Hc{-9{TbZEPeOX`D9BTRump zEk9>p*TQP|=`eR~{xx4t?PV!`UGPhT*V;anlkLFm$G?BZQgU)IxMTSD=1^+HL{9ck z4*t2feeNKDGk~`PWEV@J(Ah|r-v7Q+aihC%-Tm)$BuvA9WZ`!!{1fALb{c;#-p8MUavB z5g~fx#{lS@EfI1INE&LCuoyjr8|*K&V6)^AAZ3Baefvfxn`}l?_Be}Yr}L7eIR-wtm&Mchhhz-irP7(;h-G7NkEQchMp2{ z+c?;3tIa;D4WCMMv#!OIrQ4NowP}l=R0>xcx-OHe>QJ3*i;)7`;HIDuKzyEbN3ycEeHGMsw2ju8%9>DqsmfH8DSTEW-r-R>D2+5!N?sdrA z-TZrfS2F3%58CvOlI95L_@@Y>&pZe4bv#3GVGv7)KRuAJdOBVyCH6lStUrw_vH!6I z<(s4~MhcMF3wkBz}V51!#iGCg<}uh%Et^q@Scb7&m7F`weJ0uL3%0`A8kdbf2Y zU18M{gZ`ZLj$-sYnT1Qj`1nO-TZ|0&FIYDIP~sd$?w91nI=Egr&PCi^qcZk9!RNdGugF?xl7UX91E;_=2{%@LBhxGdbt zU2}xj1?xY>)f|B-cXjAgwP&ONF;~zVgO!e)G#4K~JVgU7#JmO)4tWFZaG`ymrMue2 z+)k9{uQO^rXV*)O04tawdK%d|-sb2^uq;@nMW`kmW>XhlTWE3PD*hV^lclxP>wfaf zY@MfywCLl&Za(F{Q}rj~=NkmU&tCvo|IR8$YzlZCEHf@R`(#vvD@;GsVrgi*taDY(= zo$(ey)ME%hJ+@I1-AxE?WC?v}I&RATXv<4iKjp&m@Ye$MU*gLC$Z%2} z-nSSDOV<(bMoc9k6Rj*+EUG9Q&x4;asAmRA%hxhwRi7)<9GxiBw9aQ-lC_uccZ`iBQY~WFuzU?vZ6=8DJHfL5S-kIl2-o z57zpKDVrvnhYIGE97Ra^sR!Nu1bRw%1ojv%s2wVk_y=IJC`+{16#tI$-#=`7(ZbCp7?%XDbg>u$AlKvUZ@ZO*FTIly>ld+;YZdcA5j`@@>c+Day9uE zYX?I{{0}h2$hm*PAB)nYy*{$``5TD*3F4&bCT^;x`#V9d(N>|<5smffx zoV`V4snPx^SpOJTjmG>gwRb5%?I!58o6;ri7F@}-GgpH~=@4xSV8!eRy1`n|;GcXt+GFh8UGWBBF zSbEe;-Xn5!+9Oi^^21v){i0Hn{vbWks=JqkzZf41ZScWubZiE5?&H5oAoIpnLW)t* z+!oyMB#f8gI-&_(KU)=aPf#ff28=d=`U1EbFesrqR8vlo0vIy{8a=;E>!?)ZGlzU8 zt;?PU5xug0A!e>em`#n=S7C0MV1+Symr0(K#^?>_szYs-i2^iw3pRS#fOjml;@W}@ zl%+{~Ng4P>zNI2JRJe?H8@VDiPj^L@;!0j5i*_nf9Xbzo6(a>`(Jt8NVL#a`vJ`Ag zPS_)hdb$+DsHt+*G(1r@VrEPw$LemqbP7r0JL?YTsjY$8L1$lV=3tZ6GXr01kf+W73<^h*-V*v z*v{qX9qT6JK^CJ0&0WY`MGKjPm%`1-*7gy$z(cEP&ZS(XV!8zN#Hy*7x5dJ>Oq4n{4b!b)e#wl#S=_T}hGunkxS0PQst_!`G|?<(}AbFCMCk@Y84pe5MW#g$%} z3IG0P{F^`!{ObWY{&nM93^Ahs6ue|32sc=i{=y9xPf9}%qI;@Ci&`cMkRBBD^q>{z>%nD|fnU8b^`I-V z6i>^GL=XO2MP6q@r~v6fK~E1_kxmbWnQMPdEsOPjP>KCPc^tVBFD{O6?_aDvs52_`ciS40 z&u>Kh>JE;ePhy9fC?nw^7zrgGg zwJT4ps6?mteouU-_f{lmbiKDKAzXMcmzChydQVxP_f`|AuY@bT_l`v91d1s}3PA4( zczRE%GQFp{dsA6s>OCtAfn9^-Nofcy173Bg+cHsr1Xj?~dsdvU_f}U1e)Y!Gd#=b* zJTos6y~ojl>d+PNo8$H>61^wr={+m5N$;u5MDK~O^Xt9ZNz!=6`R>?Js2`^BxhQ(2cz7J6ye6l&t{fcvd#ZgG` zN8$vSE2|x)fnxV`&I1!Rjt^V_=C0VuJrT~7b%eb*o!#SkoSHYX@Q*Kt2NPrPspi}t zQ0}g_3URf4x;f5+in9oL^vzR9u#aAPX|0{h9H);Z;gM*F+KwvoEMbc6!TCTBeGWf0 z@6r1h+Rpw5yste@rX3|dx=4<^H7yWaKtw|%E%Qt>9S`NSD#X$HY;&B473UD+mO_F* z!6@FBc|z!Kg4W}?)xqbQ6Fs8PLoJa)0t{Kz01HfYT)BVigm2F`CwVldMInxs7n~Xu z?^29}#T+2e=wU(T23sW|leuIPoZPJbyt%l4G1^?%VzdRI7;TBGFJ)*Sx3w@td;ZJXb1U%Fdm8}tKDu@>H0cbu5V3N= zzpebr2LE>QE3-k)BQqoTlV)O#(j_F*yrLsKLXKp>)^jEV?v%Ys(t2)t!TQ#?TF+sT zF*~WSTFeQ0>{22Ya}tBhR%RJl2H47uBE(HJa&#p)8m#(#Dg#&CmdSt$ZVA5SBIy3& z66oZA8!vVs2wv<2fESAtYiCd^oAh>pS9#1;~8WE;|(?1&9lRjUHy;t}`edT!_diI1ef~ z*&sUF!?XD@_la(vXsZ)ugr-4fD(FOJFfj5!u`K> z-y70i%sU@vd{u1{zRsv!PHFSL1}0mCy2q=T(r^Ig+FB82UDx-a$b;?vu5_Z-N-Xx#b_Ubi_yO3?q}})=6(Y=>}Q=2 z@J@S2JI8?L0h2ej2JTH3`~H)M?cwn%`#=)X&^xa2ndm9j^*cC0AqOery4>JIeupRF zOw{qEZ{tZ?T2<0@!GkUCp12b@zJ$4?nw-Z|fKD(8HhSQLYiXq;r|&3BeXgvXSq_ud z>HzZ!aU&`B6vDe}r{adE;Z(QoDi7{XuuUe!_eSXonxJ+%fqnB7GROs<4t<=fst>3)p-c8W7gmGCRr~_i!O(4^rA(kB)8j)MU5U|T;>w84jG}0 z8+CD^t0Opzj9ib+(UstAu-NdDx5`$6=UeiB9RKOQs@4qO=f9W!dME$A_|reF0obmy zHGwzBQ;99XIhHl!SlMjlpP9@ZLJ%?+0_f}5{0_yJ+N|~}M;weqo(==a7un36Yi`Zl zBg|Ej@F(i*vR`S;GxL?R5UT*jh9d>)*k5dHVE&O#pcTOIAmACVl!|<0yc*mCzt^Bw zszI4<)1V3oXq##Pv)M#gCp~xP41%J%9DPU)oIx=8Mgc1X+QR5Ef|_<3a&)@WklF+9 z`dMy|dhe60zpF}3`wWy3V4=OB_R`8Qdv#R$0iXlCY!7{D+e~^(~KOQ z4#%0@bnw!gPj^0+@kyk5bB=Q1CHT}ZJ`EBCpNr+tg{dH9S^Osh`7&&>gIPcrx0 z=ALY>Vwzer?ITv1@^P$EzOd>$0`=o?#VS;cO-qD@RRW&gQ4%td-Z6cXO}8vB%cN;q zlB07zBzuzQU7polWqWn^ZI7oAH2NC+3_`dhVD9O-W7-~-1#9p#1?s2bY7I{BPHm40 zU=1$dt-+NlTZ2zQXEc@d3zCnz2V;e?20xqRNolOXk@wX0r~s|O1-&)673Z(P&r$|{ z^~PL-yCO^RIeC$+!4aL*_NV}@!3DiFxE0y723MKM8eDvxe+}*ks-1%_i;e%?v7=Kz zY$YyF^Ge*vGrl*i#B=nQvpuTe@^$#R{IYepc^LH0OA+Qt7t=Y6&Q}EONAU7v;lI|e zE&xTp`W|3hY4ZBV)8W|LyM&*BO9eQ)4#@cse_z-@UuTIgvj}y8vc6qV3eKfh#6QsQ z=&v>`SnRrT3eO{~pYcw?oYEKSHWYPmKKPk48M8R*_(QM-x8Jxvv}i(c41!mV{${yd zq(m1G->$u=FC+HpMx*LNU%p(Rej%> zPPzOaYq5(uN$pN*6<@5JjCUNq$FHewvVo`*N67}F?3hM3{pAWP+X$z>VE9XWrvlVp z1U=z3%G+!M(G2Izh9j|CXPnd|!Fo&~UE#r09Wu2IQ@tILY?x~I(iL8guOvajHBQrW z61Qpx+&I)ufKJm3dY#n@FS`v>J2YuB6RFmU8q+6F3vP0QTX9Sm`t&HP!siYSx6EQ-bWRuz0HLSkOO>nO;W{2Kojxe z+B{cFK)DFkJN46gr}VaMLKzE8VAzDpw@q_&CD}I3&a&xJhU}=f@vk{l`lq=SFSXi1 zXK!w$Ce8Y-yI;8)tIexK3CAL}&MX)HrA&&N6I>0RcDok9Sa)Etc8{uY3qQ-zKdc*E zho1xR=H6iLjpp8D?#;NP%(dKrr_AXjw+hs+!BrRwbE=8 zOZK)K{I<+gs5f6yqHO26zrDS7E$!Gjyf|CX$q^;X*0N((bXivI99;>1pjxHtZtuL8 zXy7u(D$Sk8P=Iuspr_lcIH%iCYqZY8%0Q1Oq}9u( zA3BfWX*5?Pr??f@Rgk$rMMb*v7z)r_K+w}@R%FRE+ISR*3T#hi0xH%3%)8)e*srouw5eZjL44^fFcy|6roaOicmBttYxKG+V*Bs|b#hGn!6q0;aNBK5Ne2UZiTus2c*ttSA;*IJig_@%?g__Eh z#-%-IuS59v#wAtU)2+Uqd5xga^~^5_v3D4&vdFZr)Lx(JtOGp87t1$GjCa8UXf$!8CPT} zemgG`J;Mv(sh&}Q^o*dVXROFvsu&!||ZXRel>F;;iSjwJmsJtI%Eo-sfjP`-sdC)@|MSXmTAo}>P0Li-tn8G z*8Y`?(YuOUYV_PnHQ0II37ckLZ;emib%cA$Z!eQ@3<&@DHMDs?N(HdJ&0hgc*T0|w z>_UFI3ShJlja}uh_fMZRsTM)m|DbY!tb2REOw5-5$qKLfK3m4Gp}EzUWrY?GU%(3P zl6*NHzFsY<3{ehs-b?XO4 z;rr(P33p80sw~j0e-^0!5m&mETl&?Z%dPblfNmA=bgNQjx^-oX=#@35ZneVbu^*8< zDUBYxuu5CWGEsnfte~e`tvFw|ey9v0ugEcVt1Ge;f1DSIZsoHO)ghZ-L<*2@74&qg z6`APP_WmvG4WL$A9H%N)rRT*xIFAFAYJz zewZSbCsV{GJw~>*2hx+TBy;pAb{^T{IqvPtjv92td~d53`;Lme6Vy6I#xxyZ{28-^ zd4DB#HnH1|@4Q`u&;hOm)IMbECkx!8#lDVe?-R9}H{hrkE3YXlRP?Q7PKZf9KOsYO z{l5Ur=WeDWbEa%HwRG(j3XDG^pcpy#ANchM5{Q4|a@cLl>~XqsvYct`}%Nq72&u~M2OAjIujY3m?09R`c+re;G@EuL1xIq~SO~~s+XwtG; zSP843H=Q(sO3Auf>}-@?)wJ(=8ZPK2t$P9JGTkb1x=hBG{iZK2=k!@fEyBYT15_HgRMdJ?&BTARt?Zydi<@0#l5f+krn;^-{z* zAyhw3wd2}@EB)i|^%_`*_%c*hjqe`|CguhCKTEBXFi_t5UjW3%JjgYpz6?NHhwBVo7{q)C@UQ~Nt8{DU0oWbpErrmV3BCHCQjf@q zk*DB}#%E{ZQEB2c_sL-<4}2J(IrL-aDTEc11wAG!QNrZRsACyf2Jq(Yj5j&D65NyU zCbjEab5~}|Qo$|3y*WRAp7Dcet@tq&06z{-`LQIwi_uriUCP|0al_$^3;HQ8EMtLv zxMOocWrz#Y1nb-ZH!duia6wpcLD1uZ5{=}7Wn>vNaUn;iTu6Cv=?xhVRBRIu-i8OR z|1L`qJXj9k^I$r^IS_1azqtdr9uM?WJeXmD%j1sC1C=2jtRPqqaMk0chzGlJ^q1e_ zfuP3&CGxvlCJvUH<YVIOeGgqDDiY?4l=ko*O16*gag+}PNBPY4XvYRV$$HG&bA9HXZZ?3R*cWEpTvDQ|=vot_bhx`;4GCS!AH$vLnHz1B@ZG&Zpw%m+h z+Hwm3WAF=dO&}63X8agk3Rab+5qo z@P6V=V|#9Aejyl&zwZ@w8feTE> zkfXgUbu6A_I2Q4GS!!3o`sTQLS&Hw@RfjZd;>a2(?S%$4UA0JAayg)X1-mn~ z9oS-|dh7nGmKBj|&es+nAR_OK=#F5?i0X#CXnO*CS7QF4SmA?;W4N_W6&E8B?_Pte zJFYmCv1VY3djWyZ&vD7aVlKzLTIaz^%wNPPmbYDcseeokx^(}WjdkPcL*g2z$!6VQ z&y0!oo)wp$kMdW7=p8Fu( zp!P5ow1=(zkl!_hOt(AebI22eqtW@A(bN*ZYS7X7iurrumw}~7zZ{)QIXZ>N=+qlq zN5U&Car9#b5B1}H0qEod(8<88S0E|JRA&gd&`7k}XS>tbe^>0sh@C`mwC|^AOT)NPkE@Db8hXAVSlu&uNaVksGkau94nq_!8Ag2Q16TSc1xX5>i~>&C!+MDX_Ml z;2k0T9m~HiGDZAX@h`$6*)bQ(B2cC1_d4RrS(a{^cfOp*RvK(n>uk@F={lRSLZ8yy_VvGu=SvkXU zbU9_%0*vv+!oh{ruQb*`YPoR3?b)156-oFqqxKn+Fz*mB*=W8}OYlCWhRn7)lC||_ z_W6>zZJQMiYjGzcr!P9VIfL@_BxE@+B-Nic7Nfc3Ta0Sv9%1g0<{oA4(YU&G!Iorgt`J+BrEW7zM zey&Zvd7xQ&hN}={oh0OnX>J8fd-oo^jI4~$#3@E+;p!|!o*5e}=YXVZPg2)DSN=3D zUHei^gI01dsJXXwrYPbCencYNPvJFA>!shvC?W?Kb!q4C5|bW$34pnXn)_0`9Bec9 zV%)sBrPpU8EQwxMQuO-w1?u0!m0su5(d;!^b1MM7F5oq{Qjrgf&5o%Hue>HtxNT$W z5*28TK6oKMv#hBWjnZQ3y{{0xZ=OQJSMer9ic}K^c}yHMskP;_5oc7P=QkmV>rpv6 zJ9n1a51x4~)9tE^w^!Rz;8BYL250zn89_1OP zb+$1|0WeCyW0X=QjOwf`I-^oq61`lo4x%!>4#I4@6gz6<8b{>G_D_r>MuipM#E~4G zbP>moa^bdnGJc5WCVu<^x}Bc5ilBra*Wu+!Tyw9*&G|9n(Hf;ePh2lhzZzG10&{S6 z=tSd(0?-oz9zT>S(GyD;7gUnR1=^<+I~wE~7vyPD6IQg9>8ptxJAwz-+??qM(K$j# z=sss_;Q?r7$&PH?K$Hme3m0z0Ta0eP4L_yZ(Zg=W@0OU;7)5U7SKmPIv|_PQ>XU$P zKv(V?7Yknwj$&p;l3VyOFs(FryG5QfZSXccjZ%=spQjKPyEJ%5bFAeQ>ow~(3JLn* zuc@YB0N>Olf*C?wNlDZ9_Vkn^G<&IQS#p`DVl%a~T@qQl5ZP`8Dkjr()4wo=ZC=<| z^LO~h9t?bAY(<7X2cVg{wdJtp|iOw zQc6N5Z<2`~39>$B*;sn|*bG9P*2&Rj_jFp?#~ytq>tiZ(#Jg!uzAn$?>mi~f^7SxY zUU)V4QQVLlRgyARRwgoc0!@cHk(n+wn260&NFp-4Qftwi7;+>f6GP#h7!KamoKsb? z&XusvQ-~zG8mtJTrhn=S#@Y-x9G@eG6QzO0{m{4Ij33Hwmz#fRCBhC3GVvryk%=El zCT38kWa4RXv6z6LB>?9Zo&Q<+#S8Gy$uExJe_noZ1V2jxj>yShS7<69%d0xSgq%ub z=#?ZNZ}VI7F$7HHL*yVIFACJ3!j*iioXCf;$cI3qhozB|4<#X!VKT|&!?LmT$j9o0 zIQhuYW%s;VDjzStlF5e(_3k;f6rP7QY%swqroPg_8#8z_OGGYSGWVyr>Xy~;S4o9_ zomWU{s1S2MD#WbWDzvI9bTqGj@|y|?GXOot6I1Vx`9RMky&$1)Iv1 zr+MJ9qO6RjfhR}LKR3JJ(yZR1t!eD}X;yD01!=wM;?(;!b6>|Dv))QWy-@+wo2i7= zdo|TN<`^8mskeYvZzaj=J)wv+sh}DLsZ#6;mTR?_r@8i4pq12A`y8FtK5g?$uE^>x z`kLyVw>i2itvljM-RV=fi1OHVR~qV$SW|cOmeqarq|Jq;?gC!jl_amb+MHDx$MQh) z;3uxtTAt=wTTxa}A!TI+m1?zOpSOX@wNHj6Ggk6x4yLEPa@2OKw?FrK)3yzI5o zACOSZX@3jAPVU{~eIHDEkM~{tUvQ82cLZ~)$=vtM{e!uG#QpztkM~a`^zZTNx4Xyt zfikYk9s3@ys-%0of3~>q;OZXl8nj(V@$1CXFr&GGI?(vEnNx7%cr$^r7MQOMp zM7(d5)|93^op^oo6f(*^-VymV-{V#F(tEsC^)tz;=^n4J&G&c>licG~=9$IXxKS4u zn(p!D=>MPY@v6k+9&fq|)40`I(1M2@Z)ER7qC`8|9PlH&#pq*m|AK2{^dLIeXX{#G zs~b-hV&iEs`kO^^aEflg=EbhmC@u^AoVN6ZY& zv{`YlB$XL!ovz^<&PNvpWdGZamr==jpra+5(rHMxd0MV>{!Wx+ocSkS-ZnM&AGl-c zTcttg{7azz39bfC%#1e92#d}U@N|xnH0vDJ2Bp{)Dc5w4Jk2`Cin21AbWV=`LY*Vp z5}k8r@~wlGm72(Hbux&=&ufl9es?MPOT81Ww2*Dbg>uA2ADNvu`3qu#@u1Mc39Z$ElskueJr0c9h(X;yBR~ zwc$p)n=qFQQ8u@NYrLPGX4QZDpZ(vb7TSD^%5n* ztk~-G#->m;uw?MDZJ3M*TSel14~mJV%YryyL=MGhGOkb;-*nh5upI8BYaUVsW=~npLu%2{waTUZGS*d<29? zkr$JfhOP!jqb`fl68ugH*-@z_m3`x5;oD6*gE^9`vp50;DwH96EQ#dop=!-uzM}f| z4%kA;ZFoqm2yO}3Fm?8~mI9aQ3T$@5?V?!e*P>65fO1z8Ed9 zm}a%mB+?z$ol)BpPR(0^piH-<))B?(JxHI1?5MF=r-5Bg)A!GYi0hKANHp>J`k<(7;z{RCl+L?w{aJlP8ZuGo=v{t6vQ>Z)k1W}biF`VP`Usy8%$`Qatabj&$JdN=1gaht zUZwG=nw(HIJR#vtZ-;ojuXtUuGrJg4?QdiFr+;HQm_w5Ut~l+~7@xF%h&)89^Yle) zTTADqKW$82owS@uNZM*@W97*lCL^-R!ph^gdoMl}JF4a0+CGrcf&8rwCRQVvsyGPj z$gyZm0+X?5wvtC{o4XFK7&_{@UTGMM))lC)g{!fMLD|M4VHt}Ays=10n#UsgeJOTD z%C)gbp60R0in21A#-bekg=3Lu%hq@ywVyW!OxEj~7kIjxd;;yXr15xtA~MO^9KibS zc6iUH+4*6kNB_dXMT}PNjPW@bsu-O%BCy+fIERl}cx11`ZcG4&kGE#yHGMF46Nh>) z`zSoevK-2PLv}Mz?hyl5v@3&SUEp1UU|X0tzI~>hHRd}G{0feQoOlbOun^pYgIpF_ zG;p>HH`qcu=%=ARY=hKZUvOt^Rby?Ca{ixY-2hi7AQ({WqpZSecT~`Od_jrW9X*5m z_}I*jsoGBGA!6|G9se4A$ZG{V2>g3Z|-TL27 zajI}JlOxh^4>J53tncow9SF$6=)yY?#>85CpE@h+_IP##Nx#!(*sgdv<7MtHxcQ#O zh|Msiq2KK$P~Qnx{ca-~Xvpjjkpk#?0^Xj6QqlG1P!H|#v{l#=>qVETK-=1=ZK+@+smQ+ezrHLw4bTyVx%&Pky7&(n?k>C z{j58wVdm4PEaOA9N{Z zKf(IfaCIq$nTPd0VRgJz(Cd9l#EqEd-lsCv`vkq-XJ=2X6w95XJbCZS(Mj)1`<}k1 zp}x1(D1Gm1;0|i;dm=mOdoyY$O5bvrBiFZlfMrQ0yIJ3$-yEP6#ppmluvgOW4iYZJ zn$f|#8ZCuiQNHUl2NRI=nGkO=`lh*u;*Qy8l!iWYm_YpyT=f}N9O;*~6hNO5@cN8W zWqoFeY+qE}MrFEgv#Yv|BSfxs8+n?$jS*laH+7pFosM8>zq#>_%r+}}N35mJtDON~ z`-*Xmu)dafE}&656I=};?WpbPE1x|$(nEN3I1XogaDxxTqa_HH<_Z(~qiM=t7UN(C;dAEuG_88;7ZD@TU=lDPAxQ zrJogm2_gc?Ss?}eS@C7-;+9B(GmhwoI(YA{r3M2H1gOntD-qaSA`mO5NCAj|K%)mMh1-cxDntPDQwJvL_;`MDFb8*Li>2}k z-a-Xqq2ll`x5_XY={&M-8e7SHkMJZVc13fm40T%_(&-3Jz!0E|WrB?!R%>3_r2v~1 z&pt{koTv;UugLtar=A1a$Q7v@y{^b|{O!C*rlnh{NOurG0XhOL=-u_S>Xz*-BGY8a zOa89fD!#TAGakqE?t~t-fJwbP*}TmT8yOi^k8g%6ck%Cy=V~x- z>}V~#OgP6lk&TlH@?_&)GSDG;WaBhKiqR?No{F2xhK>n%vY{+!-O~l?-@(`IXXGPIasfa2P&)3+=Q8Se{0&4d zrgrrD;e3jNQqt1$PP2r>d>>6i2@`$f}W&W*=;7N)~)03Dg(c+JF2d2&}Nq@ zN~-LfqGZPvS&lExi$s&`q#`xt=B+Oki6#+j^t_lp>uN=ogNm}WhsC~UMXKPLvVrng zoxG2jS8V7_D$jho6n?H`?GxIAhNEPWjFR^F5@qBn-(C!M79}rOE(+j&L|w4a!^F~K ze0zZHn>TB_D*an=b>lQ%3s37WI)YCbbDHqMKftgLW>EpBXDQsUzmsI&M5fzuvpaS* z6wbPUJXsg8>kr0Dqnyp8O`ylEdUABq1=9PN(uLCrZXq_0m$0yB;tZD z;tv&(HH60JmcnV!#ldrQ@a5o>ezzG!YkcPTx;~sNJ$yBxyg+B}Rmx$kFQB;CuETaItY%cXmLRpl;k z60B91R+ zeN_UimZ=Ulk(!Sp>VL^JM*Yu`+8*CXnuJC4KUTZxp<4y0{|S1dh>_avrZJVY{7P-N zw0s1_@k_*5&ylCO=UCa+EKNNpM`weF-*-@I1?cnpj)ck^J6a06!ewp$p_AHXS&0QUctHCj+jkY$&3hhDzPh?5rJ1d`7DbStpJS(f}Y%3+1_j-ndqtX z+Sk=GuI_TJN#x0z#1@fOvX#gpGFN6fI%|^be!2A7GVr^~zo*Z{Ft1Ho3m63p9R8q9 z?jp$3D|e8=5+pvCf7<6>LW|Me=H7#w>y;7nJ7oih?-Qv12v_qv7E0A2?(We(3SfRG z;OP~mO6PZ>#ZyrsYpZ0QueR{!`Bn;T@gT91VrUE2v(=#^EV%;I7J{A}SUGL3gP85@ zeq~_w3inoFEve#Y%@v0$tQ zA@O?;oa%-5WKkgn;EJro4=aUZBPT=lRgo8%tSLYzLj*lpvm!AmAm@DaE0Hu;M-^V% zSMp|U*&Q3YJ9b0~XVNE6CVloqi_u~?n#0Aqqvg0{lu`nt&J3b+zEJcr&OujeQJ#LirQZS zQBvzk`pQlqG=f=KNZ-?>P6|W%zM;ZCu}l;o=@V@9(7&8OSZQrRMOm8kJ*5o%+8v-G z+f86t*mk%gH8*rcmf~mfA{jI>e5T4r0U9&~8$HW46Oj+N5 zELeXISJwA~;OOw-oWA5YZB!TZ`j--sjrx~$u1-fyr8bj!;P`e}*q(9Eo(8c}EO)H$ zx$4f*3HMT4)KA~YY*8YqiC_1^uj$6GpAeLcpZ^sLy5pC@6r-P-`!n3UpJ@E_*569X z`1y)J{UuzDp9f2hzDoAIED4J7Q^4ydN|nz0Dl_#oKxJ_Kbm}v)RkyLbTJ@FoGB&qG znF7VHl7d$uaa~h=&zefV?Kyzp49sQ&d~DexId7^FGe)DC8RKip(-DpEA0nc4MvIf1 z1W5P=z1A|Sy|>Dt(vifRObupSt0p(WpP^GYass;v?#@7TClwKYt%JyuHL-ONqt2*m z>L58f?I6B>)V|lL{CoXFjnUNJhY`*pDf-9j1kv7`Ghce$wfCD~INFLEeiJ^7W`$EC zTKX-4`Wv`v=|jb|MXjY3KuZgFEv-~}OY14CVr6h0HT5ad8owl%1F5*-$FyMYI&S_X zLWPI zoWt|RQGgmp&}$qcVE)EY!L>uxIL3hPgcAL*#*wGFaf|>fxT$e+blQuW?UyUSd$hja zkHoyD_Tf-vq2Kz^?+BuOvfgj72F;bGo*TT#~*_!Mt?TEpvNSoV>Fdjmpye&Y}*GnO!=fmwEIwNf6N0WqJ!JG;#%@b zzi0{;gm)uW`|dKnqp2MKl~@T)Oxlltrs27qiv_D0yP&t!H-g+!-_6(^59C^_$YZUd z-E}j&TQOGUCbN5vPHrToIz@Zv{qTNPv&@QTN&BQ*MeK+F&9Iz1&fnPJewUpQ{k5G7 zZHa$nF!&|9I36~CPdo%HMf&B=in2wjv!X%-L)xu^2EASJ5%crqO$Po!LG+6{jsK7u zzji-6OYvu5dE(F9f8oZWCq?{Qh`(ou$a((*A|C-X^{^S2Te_i@)5PdZ9c z!nBv3qyT!7fTv-UihMNXV(D(PuDAh?<2Qv0uhVc68iJ3|aJ~S)^{w2A#YkQr;lm9$ z8p3-wdXZ6=On6&RiLF}3;JM%6oJd!zdnhBpe?pYLfu*LUl@$*fr3bX#_9%4pO)SNV z!ZmXAPx?AWlJ?V4(>|!Qt-#k#oA8(h3rmBaE$IZl#Q&Ydna2kHYR!uV*AQ2|Tp1bs#JS-U=gQl3-E39l> z9q~ezeO&-X(xC3kH4E!^#N*qPVrA7uM>v6E^yL}_^RlB*kI@C(munuA;P%E{U^%t1 z89lQCrW{>kxpXM<1zKV0(3EtDotbhvL>xeeOc1D-aiv2bwL0`OWf~~}9U|Zz+fXX< z(XkCqH>%uXUB$RVDE~+5-xJqsPfA0MBMJqT7XH?fHGCu@YEM88y9=TreBAjJ8a^sAB}q+fWNA#m zICxJWA2A?aZ6b{pcH`&+GQy`HkP*l90U0$=_5m5?uMfzmaqR;#*2<5lKu4Hd8#d+1 zhE1D_8w;%JO|!imonGWm&&6oj!&5y425{?R{Ioxfr}u(;I>9eR7hcUVb^h_R8#?$Z zi$9*On2e{twXw4${++?#SCaU5&EFHh11v@Q<;K&9mSQ{=BKVCro>p241+JR1UR#8M zuxIGw83^|)fMV@gF=i?OoE$fI3U1uvAr=#&*F!9h-|NaEpJVstE}@`oHa}vZRq=F^ zr3C7W;;NI>C0}l!RREntz#C|lYUDus6>#*_r2+mxy9~dRfmU8lwByb<&{|@vY$Vd> zp4&9giV4vq95>MBoN1JvlQ5!x6 zmBuuBWuM|YuF)$8#Wgq3DnPwb(Cd}PHrFdhA85s!5d-bRHXd3n#F?=NS`j|xKGAUd->2e*!6GWro%t-1c4g~_ z(+FZ-JDY10_<}7F+HiXY0b9WAfQPL?EnpN+3z+GG^<{CjfH_WWe;>qfV*!*FFoKPq z@1;BMO2-0bCvxhn*cs#rJhdz%b z2V0KxwRyDJxZ&CEw%=d);Zis&)B$9DZ15r2nt554Fx4@7jZV~O-U2x~X#x0b4+)hE zKgr)gQT052wG}v=S6IdPRVB!?!|O1j7K)4p%M-#44RdGU=8M4*_C;lZ?&0ZT*wv3K z-NTHjI`li`6e$4RBj9x+r9$`E*{{mXqZP9S6*@5ig2cmM>dg@sY3f! zZnBf2`Lof(9ov`;Y~Z)7)Z z4d>|01ktn?(2abl?|Xe#f{~2RGiooRvgfS|hIz94*4q-j!HU>Etv{Cw7m;>Vlh4(O z#Pn(=fNw9ZwsbjI#uVNx5Y6Y8XLIoA9LDBj8KzciFHK%kbIx8~OYGl@5m}dQ%F%7i zX8QMWFf61K&is_V(+w(9x?yd>`Wm>>4JWG2iV(*A4N%ezg5I1{>Cg=}=d7>E4|n*9 zOECB+FS7WOdgE(SjT4w1@j9S1o_@0&-3n3Vh+}!~8Mef$OEtXiOSE#tAqm`@aCrzA zOt=JV-*8Gg(Ok@ln`GaS-WJa$Rzf;M+DRgPW9vW)(2yqReK^KQcOQ;n@D#l<;?mk_ zTkLMNCQ>wr2)aSU8BEsV_oVT|h4xA7a-JOS2yw(xD58nwa&!Z^@Uq%sD8Ts$)-Kl< z5bRzR{NDr*Om$@*sWSTppkEXm8!Vyror;4p6J~f46`9}~-;Q;Zg8N|dw72c%gxPkC z8f$UZzxmni9G#xsW<7}{*tzNJfG5LSf864XNMSc{Kj2^URZ*t+tMR`G9!@_NxQvwj zDUQxkuova`8HrwY&mvlvU$&k!59`S}DZ)G;)=LrQNxRmJ+K{^2`U*^Ox3+{UQun!s zcN7j=)E0gNy&fvSCe&fOHq%>@R}bw@ffjA6S-;$f+>p;L0Ng1sGjy~GKbrfbz7Jr1 zlDjknaz4<%C$O1A`S%1iR~BsZbkK^*&ZXm8jrlEfI{iOf9C^|(L`O((K8hlSp)CdK z8{=vi`Zkh3ybF&Z^P4e9pwWXx&F#)A37Jfla4w0Hy*g%jSte|5ada!0@8sy@qts!-75 ztkR(h#aS--=tqkV&UMT@nOc{lyU_~m;p>QHd-x{q{3L~4nz=1a5?f0zgJ-~*c^ye@ z@y_I$kc5i+jv9J*Zrp&CeiLl;u-0=eZS=GSePm+T6-=w8}BJnnn~iyUo#+ zU|Fz?O}2*RD#JwX2n^X-m?${nN@3i@MgZlZhuEk5YH%ZDo=;qH2qw$5r-m zN~a@MCE6n;55K`ov47ce#ElP7irsE?B>ww~zasI&m2f7uRu`E+TDYHfGp+NrS7 z!5yekWps7sSi$-cxVlhxx_TYYUkj^VC+NwH5|NFD293AY<18b~fF8FRAx;D5=t{6U zSiAEL%WO`ivqTe`+g%j21ZyZ}+U_Z2Z*9;*n{0t=(k2rM#rS=*`?bV>G#LC#GxR^0 zzbAeVSc>$^*#gg?de{Pm2>wGvx3|_-_$PC>wXBVgry$z+L;!89otR?nBbXhZ42CN; z<|;IfJj6*t)IG$vg@}cqVSc2J^fATl7M~`R%O+C%kP}rM@#(_$#%BQ5V1h%DAFIey zg^rx7g5!A^!ez?M6EI!@yN5GjpiK|sUEaH>3}4;V$PkqNr6*9%EP9a$%FNi?0I<%+M=~l`iq292Epe2HW&C;1zJtyeNtkNZVu5+kCt&+8d z+gfXLJRD*1dw||Q_i32q)!p82a{Fs--u@bDQ5B1_dSLyuowlu;%3)3{_D*gz836C1 z96!AiA-6&Yw`r&y*z!n%6YoDkqTx6-7djbB~W#$kU3gPl~i0=xa zBDhO~JGLcJrAVeO7ObC-E15c5GBp)axWfZVG9~C)5|s{_k|nXWCbHZ)w`JYJVc|>E zhY#$OE8~WK3}?{uq9-~R$C=F?d2$=2Pt_6DBC+ z#ha&);9PZ(b@)B8t-2!i$=ULjD=gGCNW16L*9AAw-Gn224%qaG^k?ROBE6!$SzAgQ zTZ|nnrE{^#(PgL73+aB(vM`HNXNCQMX2Qg;zcT-G!5zcDCm&*TA|K8M{ISwoyI(?3 zV$;+@{MY=O;jcUX0U=!D!5#Aqof5N*yj-AuDXx~0=czT%qW~`BfMO*n;8`=3Dz#=7 zD>@cAuHtTr*crMX63i3RxHGkccHZPT-9I4D(Zp3DVuZV?aj3Q%ZoPv6MjFHI70S)g z#&CPSXmcl-6`&DU&^s7l#JPh3P1jk(hosEhnCczGlzqmPS&FaB%j6*D1uF9dBU}Lt z;&nmKm}+HaS9e^6nMJVLE!Fzytw~d;LCo|hPj;!>4p~~sR?DVCmN|NqvwIK1T&=e~ zIZ+dO=b%~(&!K1Bea5Q@O71_*f1mLh0-20I>fL9&Ry1lMjT*fUKNo_`6@JY7j4A|q zx$;3v28$nGxa z-DfmPM!nDISS~t(l@Z|VKBMr#DjI}G-e**Zndoag)Ww_T{6un$o87Uip>QVu@-&;? ztp-MUlj%K2k0Sr*f|2(bC2}M7#ktRTn`nt{$Nj(GXS_qHM&4(<6XeMIjCU!b-Dku$ z`6GUq_0l{b?oJWr0dY@?Fi$#hpHUmvf%WoU1*W)_TEZ5P?hC5!gQpq;+6o#&IJ?7s z`~$>idiWS1TVFrKk1-7`4-+`TmiCB4`73SILAKIXcEw2Pc){CT3tbCQ2-ib>%Rafj z2GO-_$59la2p$)xKZq+ua4`a@*9C<|3kWoNn7ufwm6DLj24pgLAAWlYT%2f>I;nqc zLjwBdDI~arj57136=h{KnJ;s6V!llF3KuN=>0g=sQj|5>FNfjjy2jHd2!f~21K{a1 zsDJF22bwB*N;F{pAN4tzrwJU@hV={qBY6I-L-{;^PI)KxOO?X|;^rzf7fH;uIBSaY z8^IHs_HQiKJ1eFL*A(pq!TOW9nxb7QW#CS$DnL^-!A1|Yb^KR4rf8dx6H~Il6*l)T zHmA=iv9Iuj0~kKKJBNM{LpV+bOn~vDF_(i2~#G`dA;mV@8!dB^om3IqVg)JR8&+nw>!*LYA(8% zYjMUD71c$e^15LCC%6)o%OooA+I&X=5*0yDRFp0km2z+A(B)%Df-1+;?B?bajcwX= z6t%@~D21a4dH8|k0V9jBk_SQ0v}xp|rp-hYEF;UH$*h^9GqYxso%=!>a>N@EVvA?j zEZKQ{rZec>pA&>E3;~dZtBhrD@`L8|?V4{nl;51cbSS?$-v*U7r!qmgijlBtRY9*+l_+o3Ru;vx%fV_=GxwwGuxYA` z^JY8I92JR8GkJ*3%CxeY?3p<_wP&XLNdIYHV3DO*E_0qu6ppZA^2S;=>~Y6~2+61E zZS4Ckyyk8%ZSZSCXoGhFobNxdSnGw#_z(OnM$UZ)f2<5JDg4&(zccsu=F%#y!C1qX zGG`e3qd@&PxEjVT7aI>OM#3_T33$Vpk|e{JBcM^51Vy#Y(5t}}B+Ujc%iMBh;M$yE zX9vvDm0%07(^Gz3pMTp%q_hNEDm0xYVDo$5u7-{n* z2kIBKMb#{yg5d>jb3eh2MKXrG&jD*pC4I5BnBskAF`TQ|@t<+Z5vv`N3mpkz6}GRf zXV2{}e_2R3lzzVYB_q;s-=)9i$8bLLNcc1 z$h^X`fF>cz=Zt^Shiq8p>_5N%@1MlX3;B@reFOc|yn;P%qqMjrIX$_k$RkfKDyv3m zs-kTTtBTRTL2w5EH@phD8L^+`W-JmHS=Rq6SpPe&EbCVjeP|OkOQZnleL+v}D;>u5 z#W9>tZ0%~Db*FuK)@q|B>NG>aNTZ@!el+9dz~}Ntgq~rAAr}N?3TNO<3gf8 zPf}a81lzmJ^^S||J3JtXUM{qw14{izb#sTfh~FLGTAhhwa)2U7YIvF3zD{~7e_+t1 z;8KCo<<=UFPTmb?m7qbcRj|GQt_Hbl5S!tnxPQQJ4RV5>sYZ#&#=Rhr6XxC$sI7|b z?5waxWp+S5vhUemofTz8G+j>1(do&s^gQAd&u4245tf|=>!WAPa-CoBV^K%<9Qlv`IQ37O0#lR-Xe>DF~(L8CNH zdSVBpr*ED@g6qgA8y>AFE2C*$m!rR6T_?(#)^!gf>zf!)7a|Cr_5$E(vaWM#rb{%8 zwXT~$;HdNWi3E(`dACFPJnvE73C~pyS2@g8>SCk>yt&X#RWbM@nC)3f>a z)@|a-eCxJNt=$(Ph<5)9fOc{jZ>~~vdBR+aGo~o0E)s>M1nZ09N)*uL>AFn;5(Pm|6qL>rg>2n6zbL43+$b^LURo&}MF_-A5(w69!b%_nz46<~8L@7&j4Xqubz6@9((5*{rD@%!^C;Ml z(W}c4ge*)4APd)8@0rGr5$m>P9m;Rcz@hx+Tn<#)oXR9wx0%YY^ zQ^QsT>wII;8un&2>`6wY0@SdAUc)L~-mqG_ZBP!@mnJz;HsfUdroPVC7KrPpX$m@L9z|5ozn06!IKGBJgpq5x&8=Mq2%%m0E*9;gwcPRy5b>(~u zL)Nb7(%@E-W`mVwZn-j8?MAR$%jM`w@Kvy*t>r{YOYk*?I=k*t$mtfwkJSl+A2R{) zV?zv(cOwNo4?91IqIrEBBg|2>;;sm3kAAue2VQk_R)|DArnE8E#+G~68_FsAN-CpiIX1;Nw}Nt%=dQ|}T}zsWQHV8v9yMh_#M z8}qFkUKuBw@#Xed%{kn~fpG3Ps&2_0$CTYxgjE7|pDT7x)UqN+2j_{K>E87cR8TVx zEf~54lRvEy+Q3cZ-y4&~oR;AHyggX%7PhkX*oYu+?^q}0cr<9Z!_9TYn}gXMX$_!dE>n&qs?enW?Q^vUM558Jt}h^jN+KL%4Ad( z^j0`lrhBqPFHNbaWOcJ13M1jH_-k8n@?>irJDz6cS{)gI^Cz8hbh_3_#{r#L&^Oxk zCYIOkYLXFqakF@teJjw(mwHL3Gc5lh9d`uipIZ|~|J)A1xcr%FS(~XLU`H^VB{z3l z+*rAkqZg6o!FAJXRDIn6R<4fzalxOds6LrkI@azo)mBFM#!or(SBPw1@7%pu>0jA5 z$>nGC1bfm^uI!sU_Zt?pNU^>>`Rgn29d_RA%DzSCo~@wZGf*5r)Dc1Qix;kgk(-mFC_J(cyh$II-MS9;mp$;d*U` zs-e>^-4HynqdKLlBkYlPT-Uh%1-mL;&>Ps2J_WZ$1)%%dYy@07*;jjyF6) zBkEpatNQ=mc#^f3GSj^5>w@)Na5dq+UoG_`Ybgb&{|h#HxC!I*u+ov!zT{M6s?jk% z++Vr3RafO$(KqngrAj*nW!EStmb#ds?LjKnMf%6Lck#V1-k}H?AhNSaPt1Q7sa-s` zqa;SF(a2Gf)ia*w=xp7*7&2D)VPU=Bq^w@B@ZKlcII3EDYv_vI@!8gRyC*^Lc5lGA z(lo56SBnYV@c|&p(M!hYeel~+~9u3>u@iPmO8<+w?<26N)%;9o>Dtdu)Y`W1WxcgpoTItbff@v8o@>n z(@xh=N=HunC`-L=yGXIz#YsmiY)9x19wZB^AD@^YZs+hi6U%zst0Qn9uoTth=%-Yn z=?tPf9%fV-H5?DyI%|>~T?w`U%X~&-to}5v>X;L}Df9uCa^^X+!z}?$Z|r#eKoxBX zwsm<-S)pR@?`&snbr3?JKa7q}#tZ5AGEs zD@)Bl%S9p@v-7?*mHRPYTZry4+rlU_ifD^nvbM<4m0(xL#|7vgYKb@AEA%=3FIZ@F zW_wnuR7dq`J9hVj(Kxess!SbXv#cV#gW)uIO*ox&WJ>lVP z>O#!6_`(A}^%*pUR_lBrL&4Js8E+q=DGeS8!cJqTf{oIK()Ihn{=Rt%2_B|Z(S1q2 zH<53NY#r4~-QIoIj!fD&(5!Ywy>4z=Ei_8eB9Y4TzJX>x?Hg$8lgADj@7y=g z)FSye&@9)E>crmKMii}YpozV)9Q?=1FMitQ+Lpw~1YqxC6cfNvd~=V(4R%F_dGn@J zVDVVsU>R*a4?Kc=0)RPBFa4Tj9#?niqg#_*{2fc}Tt$jqrVeqU5H5d*I7tW>lz8HGgktQwK_;G#83+dzNqHl77Y(kZ*}O5JMGs@29=TTwaHEkp7Jg&yrN2D@N6gwgX zXp9x~)>KNz80*$lqB6XQBBm}_jySZzz8fOon2{J}#20tyw?R;iD^Ike)ywF6lmf&3 zH4bQK;YNvu7Tq>>^l{06^a*_oEsu#lCO*)o01YjIjUHAwZfG(3O2Gl-#5V>vZ4Wo5 z#DV2gj*1+Q)Y_|r`oE828rwO1m`gT!<@U+7nql^E4=TK6&0y8Olxll5oqOdX+RZ9{nL?f*^6+-WNLcJ30*xLfjjp+r zgiH=5lW9z;(2R}Pva$47ck%^tj&=)V>X6zaPB41@