From 4226aa8fe7cb58238a9a94e478410796077e4008 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Fri, 15 Dec 2023 11:59:43 +0000 Subject: [PATCH] Csq is compiling --- .../Commands/SystemCommandLineExtensions.cs | 11 +- src/Squirrel.Csq/Commands/WindowsCommands.cs | 2 + src/Squirrel.Csq/Compat/EmbeddedRunner.cs | 112 +++++++ src/Squirrel.Csq/Compat/ICommandRunner.cs | 17 + src/Squirrel.Csq/Compat/IRunnerFactory.cs | 9 + src/Squirrel.Csq/Compat/RunnerFactory.cs | 105 ++++++ .../Compat/SquirrelVersionLocator.cs | 85 +++++ src/Squirrel.Csq/Compat/V2CompatRunner.cs | 275 ++++++++++++++++ .../Compatibility/NugetDownloader.cs | 76 ----- .../Compatibility/NullNugetLogger.cs | 56 ---- src/Squirrel.Csq/Compatibility/oldProgram.cs | 306 ------------------ src/Squirrel.Csq/GlobalUsings.cs | 1 + src/Squirrel.Csq/Program.cs | 78 ++--- src/Squirrel.Csq/Squirrel.Csq.csproj | 2 + src/Squirrel.Csq/SquirrelHost.cs | 114 ------- src/Squirrel.Csq/Updates/NugetDownloader.cs | 69 ++++ src/Squirrel.Csq/Updates/NullNugetLogger.cs | 54 ++++ src/Squirrel.Csq/Updates/UpdateChecker.cs | 28 ++ version.json | 2 +- 19 files changed, 806 insertions(+), 596 deletions(-) create mode 100644 src/Squirrel.Csq/Compat/EmbeddedRunner.cs create mode 100644 src/Squirrel.Csq/Compat/ICommandRunner.cs create mode 100644 src/Squirrel.Csq/Compat/IRunnerFactory.cs create mode 100644 src/Squirrel.Csq/Compat/RunnerFactory.cs create mode 100644 src/Squirrel.Csq/Compat/SquirrelVersionLocator.cs create mode 100644 src/Squirrel.Csq/Compat/V2CompatRunner.cs delete mode 100644 src/Squirrel.Csq/Compatibility/NugetDownloader.cs delete mode 100644 src/Squirrel.Csq/Compatibility/NullNugetLogger.cs delete mode 100644 src/Squirrel.Csq/Compatibility/oldProgram.cs delete mode 100644 src/Squirrel.Csq/SquirrelHost.cs create mode 100644 src/Squirrel.Csq/Updates/NugetDownloader.cs create mode 100644 src/Squirrel.Csq/Updates/NullNugetLogger.cs create mode 100644 src/Squirrel.Csq/Updates/UpdateChecker.cs diff --git a/src/Squirrel.Csq/Commands/SystemCommandLineExtensions.cs b/src/Squirrel.Csq/Commands/SystemCommandLineExtensions.cs index a3abed7a..d6aee0cc 100644 --- a/src/Squirrel.Csq/Commands/SystemCommandLineExtensions.cs +++ b/src/Squirrel.Csq/Commands/SystemCommandLineExtensions.cs @@ -1,5 +1,7 @@ using System.Text.RegularExpressions; +using NuGet.Common; using NuGet.Versioning; +using Squirrel.NuGet; namespace Squirrel.Csq.Commands; @@ -36,7 +38,7 @@ internal static class SystemCommandLineExtensions public static CliOption SetDefault(this CliOption option, T defaultValue) { - option.SetDefault(defaultValue); + option.DefaultValueFactory = (r) => defaultValue; return option; } @@ -273,10 +275,9 @@ internal static class SystemCommandLineExtensions { for (var i = 0; i < result.Tokens.Count; i++) { var framework = result.Tokens[i].Value; - try { - Runtimes.ParseDependencyString(framework); - } catch (Exception e) { - result.AddError(e.Message); + bool valid = framework.Split(",").Select(Runtimes.GetRuntimeByName).All(x => x != null); + if (!valid) { + result.AddError($"Invalid target dependency string: {framework}."); } } } diff --git a/src/Squirrel.Csq/Commands/WindowsCommands.cs b/src/Squirrel.Csq/Commands/WindowsCommands.cs index f8718a55..c06f01c5 100644 --- a/src/Squirrel.Csq/Commands/WindowsCommands.cs +++ b/src/Squirrel.Csq/Commands/WindowsCommands.cs @@ -1,4 +1,6 @@  +using Squirrel.Packaging; + namespace Squirrel.Csq.Commands; public class SigningCommand : BaseCommand diff --git a/src/Squirrel.Csq/Compat/EmbeddedRunner.cs b/src/Squirrel.Csq/Compat/EmbeddedRunner.cs new file mode 100644 index 00000000..60d95101 --- /dev/null +++ b/src/Squirrel.Csq/Compat/EmbeddedRunner.cs @@ -0,0 +1,112 @@ +using System.Runtime.Versioning; +using Squirrel.Csq.Commands; +using Squirrel.Deployment; +using Squirrel.Packaging.OSX; + +namespace Squirrel.Csq.Compat; + +public class EmbeddedRunner : ICommandRunner +{ + private readonly ILogger _logger; + + public EmbeddedRunner(ILogger logger) + { + _logger = logger; + } + + [SupportedOSPlatform("osx")] + public Task ExecuteBundleOsx(BundleOsxCommand command) + { + var options = new BundleOsxOptions { + BundleId = command.BundleId, + PackAuthors = command.PackAuthors, + EntryExecutableName = command.EntryExecutableName, + Icon = command.Icon, + PackDirectory = command.PackDirectory, + PackId = command.PackId, + PackTitle = command.PackTitle, + PackVersion = command.PackVersion, + ReleaseDir = command.GetReleaseDirectory(), + }; + new OsxCommands(_logger).Bundle(options); + return Task.CompletedTask; + } + + public Task ExecuteGithubDownload(GitHubDownloadCommand command) + { + var options = new GitHubDownloadOptions { + Pre = command.Pre, + ReleaseDir = command.GetReleaseDirectory(), + RepoUrl = command.RepoUrl, + Token = command.Token, + }; + return new GitHubRepository(_logger).DownloadRecentPackages(options); + } + + public Task ExecuteGithubUpload(GitHubUploadCommand command) + { + var options = new GitHubUploadOptions { + ReleaseDir = command.GetReleaseDirectory(), + RepoUrl = command.RepoUrl, + Token = command.Token, + Publish = command.Publish, + ReleaseName = command.ReleaseName, + }; + return new GitHubRepository(_logger).UploadMissingPackages(options); + } + + public Task ExecuteHttpDownload(HttpDownloadCommand command) + { + var options = new HttpDownloadOptions { + ReleaseDir = command.GetReleaseDirectory(), + Url = command.Url, + }; + return new SimpleWebRepository(_logger).DownloadRecentPackages(options); + } + + public Task ExecutePackWindows(PackWindowsCommand command) + { + throw new NotImplementedException(); + } + + [SupportedOSPlatform("osx")] + public Task ExecuteReleasifyOsx(ReleasifyOsxCommand command) + { + throw new NotImplementedException(); + } + + public Task ExecuteReleasifyWindows(ReleasifyWindowsCommand command) + { + throw new NotImplementedException(); + } + + public Task ExecuteS3Download(S3DownloadCommand command) + { + var options = new S3Options { + Bucket = command.Bucket, + Endpoint = command.Endpoint, + KeyId = command.KeyId, + PathPrefix = command.PathPrefix, + Region = command.Region, + ReleaseDir = command.GetReleaseDirectory(), + Secret = command.Secret, + }; + return new S3Repository(_logger).DownloadRecentPackages(options); + } + + public Task ExecuteS3Upload(S3UploadCommand command) + { + var options = new S3UploadOptions { + Bucket = command.Bucket, + Endpoint = command.Endpoint, + KeyId = command.KeyId, + PathPrefix = command.PathPrefix, + Region = command.Region, + ReleaseDir = command.GetReleaseDirectory(), + Secret = command.Secret, + KeepMaxReleases = command.KeepMaxReleases, + Overwrite = command.Overwrite, + }; + return new S3Repository(_logger).UploadMissingPackages(options); + } +} diff --git a/src/Squirrel.Csq/Compat/ICommandRunner.cs b/src/Squirrel.Csq/Compat/ICommandRunner.cs new file mode 100644 index 00000000..020183ff --- /dev/null +++ b/src/Squirrel.Csq/Compat/ICommandRunner.cs @@ -0,0 +1,17 @@ +using System.Runtime.Versioning; +using Squirrel.Csq.Commands; + +namespace Squirrel.Csq.Compat; + +public interface ICommandRunner +{ + public Task ExecuteGithubDownload(GitHubDownloadCommand command); + public Task ExecuteGithubUpload(GitHubUploadCommand command); + public Task ExecuteHttpDownload(HttpDownloadCommand command); + public Task ExecuteS3Download(S3DownloadCommand command); + public Task ExecuteS3Upload(S3UploadCommand command); + public Task ExecuteBundleOsx(BundleOsxCommand command); + public Task ExecuteReleasifyOsx(ReleasifyOsxCommand command); + public Task ExecuteReleasifyWindows(ReleasifyWindowsCommand command); + public Task ExecutePackWindows(PackWindowsCommand command); +} diff --git a/src/Squirrel.Csq/Compat/IRunnerFactory.cs b/src/Squirrel.Csq/Compat/IRunnerFactory.cs new file mode 100644 index 00000000..7c5f7c5e --- /dev/null +++ b/src/Squirrel.Csq/Compat/IRunnerFactory.cs @@ -0,0 +1,9 @@ +using Squirrel.Csq.Commands; + +namespace Squirrel.Csq.Compat; + +public interface IRunnerFactory +{ + public Task CreateAndExecuteAsync(string commandName, T options) where T : BaseCommand; + public Task CreateAsync(); +} diff --git a/src/Squirrel.Csq/Compat/RunnerFactory.cs b/src/Squirrel.Csq/Compat/RunnerFactory.cs new file mode 100644 index 00000000..87c7996c --- /dev/null +++ b/src/Squirrel.Csq/Compat/RunnerFactory.cs @@ -0,0 +1,105 @@ +using Microsoft.Extensions.Configuration; +using Squirrel.Csq.Commands; +using Squirrel.Csq.Updates; + +namespace Squirrel.Csq.Compat; + +public class RunnerFactory : IRunnerFactory +{ + private const string CLOWD_PACKAGE_NAME = "Clowd.Squirrel"; + private readonly ILogger _logger; + private readonly FileSystemInfo _solution; + private readonly IConfiguration _config; + + public RunnerFactory(ILogger logger, FileSystemInfo solution, IConfiguration config) + { + _logger = logger; + _solution = solution; + this._config = config; + } + + public async Task CreateAndExecuteAsync(string commandName, T options) where T : BaseCommand + { + var runner = await CreateAsync(); + var method = typeof(ICommandRunner).GetMethod(commandName); + await (Task) method.Invoke(runner, new object[] { options }); + } + + public async Task CreateAsync() + { + if (_config.GetValue("SKIP_UPDATE_CHECK") != true) { + var updateCheck = new UpdateChecker(_logger); + await updateCheck.CheckForUpdates(); + } + + var solutionDir = FindSolutionDirectory(_solution?.FullName); + + if (solutionDir is null) { + throw new Exception($"Could not find '.sln'. Specify solution or solution directory with '--solution='."); + } + + var version = new SquirrelVersionLocator(_logger).Search(solutionDir, CLOWD_PACKAGE_NAME); + + if (version.Major == 4) { + var myVer = SquirrelRuntimeInfo.SquirrelNugetVersion; + if (version != myVer) { + _logger.Warn($"Installed SDK is {version}, while csq is {myVer}, this is not recommended."); + } + return new EmbeddedRunner(_logger); + } + + if (version.Major == 2 && version.Minor > 7) { + _logger.Warn("Running in V2 compatibility mode. Not all features may be available."); + + Dictionary packageSearchPaths = new(); + var nugetPackagesDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + packageSearchPaths.Add("nuget user profile cache", Path.Combine(nugetPackagesDir, CLOWD_PACKAGE_NAME.ToLower(), "{0}", "tools")); + packageSearchPaths.Add("visual studio packages cache", Path.Combine(solutionDir, "packages", CLOWD_PACKAGE_NAME + ".{0}", "tools")); + string squirrelExe = null; + + foreach (var kvp in packageSearchPaths) { + var path = String.Format(kvp.Value, version); + if (Directory.Exists(path)) { + _logger.Debug($"Found {CLOWD_PACKAGE_NAME} {version} from {kvp.Key}"); + var toolExePath = Path.Combine(path, "Squirrel.exe"); + if (File.Exists(toolExePath)) { + squirrelExe = toolExePath; + break; + } + } + } + + if (squirrelExe is null) { + throw new Exception($"Could not find {CLOWD_PACKAGE_NAME} {version} Squirrel.exe"); + } + + return new V2CompatRunner(_logger, squirrelExe); + } + + throw new NotSupportedException($"Squirrel {version} is installed in this project, but not supported by this version of Csq. Supported versions are [> v2.8] and [> v4.0]"); + } + + private string FindSolutionDirectory(string slnArgument) + { + if (!String.IsNullOrWhiteSpace(slnArgument)) { + if (File.Exists(slnArgument) && slnArgument.EndsWith(".sln", StringComparison.InvariantCultureIgnoreCase)) { + // we were given a sln file as argument + return Path.GetDirectoryName(Path.GetFullPath(slnArgument)); + } + + if (Directory.Exists(slnArgument) && Directory.EnumerateFiles(slnArgument, "*.sln").Any()) { + return Path.GetFullPath(slnArgument); + } + } + + // try to find the solution directory from cwd + var cwd = Environment.CurrentDirectory; + var slnSearchDirs = new string[] { + cwd, + Path.Combine(cwd, ".."), + Path.Combine(cwd, "..", ".."), + }; + + return slnSearchDirs.FirstOrDefault(d => Directory.EnumerateFiles(d, "*.sln").Any()); + } +} diff --git a/src/Squirrel.Csq/Compat/SquirrelVersionLocator.cs b/src/Squirrel.Csq/Compat/SquirrelVersionLocator.cs new file mode 100644 index 00000000..c67ba5ee --- /dev/null +++ b/src/Squirrel.Csq/Compat/SquirrelVersionLocator.cs @@ -0,0 +1,85 @@ +using System.Xml.Linq; +using Microsoft.Build.Construction; +using NuGet.Versioning; + +namespace Squirrel.Csq.Compat; + +public class SquirrelVersionLocator +{ + private readonly ILogger _logger; + + public SquirrelVersionLocator(ILogger logger) + { + _logger = logger; + } + + public NuGetVersion Search(string solutionDir, string packageName) + { + var dependencies = GetPackageVersionsFromDir(solutionDir, packageName).Distinct().ToArray(); + + if (dependencies.Length == 0) { + throw new Exception($"{packageName} nuget package was not found installed in solution."); + } + + if (dependencies.Length > 1) { + throw new Exception($"Found multiple versions of {packageName} installed in solution ({string.Join(", ", dependencies)}). " + + $"Please consolidate to a single version.'"); + } + + var targetVersion = dependencies.Single(); + return NuGetVersion.Parse(targetVersion); + } + + IEnumerable GetPackageVersionsFromDir(string rootDir, string packageName) + { + // old-style framework packages.config + foreach (var packagesFile in EnumerateFilesUntilSpecificDepth(rootDir, "packages.config", 3)) { + using var xmlStream = File.OpenRead(packagesFile); + var xdoc = XDocument.Load(xmlStream); + + var sqel = xdoc.Root?.Elements().FirstOrDefault(e => e.Attribute("id")?.Value == packageName); + var ver = sqel?.Attribute("version"); + if (ver == null) continue; + + _logger.Debug($"{packageName} {ver.Value} referenced in {packagesFile}"); + + if (ver.Value.Contains('*')) + throw new Exception( + $"Wildcard versions are not supported in packages.config. Remove wildcard or upgrade csproj format to use PackageReference."); + + yield return ver.Value; + } + + // new-style csproj PackageReference + foreach (var projFile in EnumerateFilesUntilSpecificDepth(rootDir, "*.csproj", 3)) { + var proj = ProjectRootElement.Open(projFile); + if (proj == null) continue; + + ProjectItemElement item = proj.Items.FirstOrDefault(i => i.ItemType == "PackageReference" && i.Include == packageName); + if (item == null) continue; + + var version = item.Children.FirstOrDefault(x => x.ElementName == "Version") as ProjectMetadataElement; + if (version?.Value == null) continue; + + _logger.Debug($"{packageName} {version.Value} referenced in {projFile}"); + + yield return version.Value; + } + } + + static IEnumerable EnumerateFilesUntilSpecificDepth(string rootPath, string searchPattern, int maxDepth, int currentDepth = 0) + { + var files = Directory.EnumerateFiles(rootPath, searchPattern, SearchOption.TopDirectoryOnly); + foreach (var f in files) { + yield return f; + } + + if (currentDepth < maxDepth) { + foreach (var dir in Directory.EnumerateDirectories(rootPath)) { + foreach (var file in EnumerateFilesUntilSpecificDepth(dir, searchPattern, maxDepth, currentDepth + 1)) { + yield return file; + } + } + } + } +} diff --git a/src/Squirrel.Csq/Compat/V2CompatRunner.cs b/src/Squirrel.Csq/Compat/V2CompatRunner.cs new file mode 100644 index 00000000..2e5a950d --- /dev/null +++ b/src/Squirrel.Csq/Compat/V2CompatRunner.cs @@ -0,0 +1,275 @@ +using System.Diagnostics; +using Squirrel.Csq.Commands; + +namespace Squirrel.Csq.Compat; + +public class V2CompatRunner : ICommandRunner +{ + private readonly ILogger _logger; + private readonly string _squirrelExePath; + private readonly EmbeddedRunner _embedded; + + public V2CompatRunner(ILogger logger, string squirrelExePath) + { + _logger = logger; + _squirrelExePath = squirrelExePath; + _embedded = new EmbeddedRunner(logger); + } + + public async Task ExecutePackWindows(PackWindowsCommand command) + { + if (!SquirrelRuntimeInfo.IsWindows || command.TargetRuntime.BaseRID != RuntimeOs.Windows) { + throw new NotSupportedException("Squirrel v2.x is only supported on/for Windows."); + } + + string msi = null; + if (command.BuildMsi) { + msi = command.TargetRuntime.Architecture.ToString(); + } + + var options = new PackOptions { + releaseDir = command.GetReleaseDirectory().FullName, + package = command.Package, + baseUrl = command.BaseUrl, + framework = command.Runtimes, + splashImage = command.SplashImage, + icon = command.Icon, + appIcon = command.AppIcon, + noDelta = command.NoDelta, + allowUnaware = false, + msi = msi, + signParams = command.SignParameters, + signTemplate = command.SignTemplate, + packId = command.PackId, + includePdb = command.IncludePdb, + packAuthors = command.PackAuthors, + packDirectory = command.PackDirectory, + packTitle = command.PackTitle, + packVersion = command.PackVersion, + releaseNotes = command.ReleaseNotes, + }; + + var args = new List { "pack" }; + options.AddArgs(args); + _logger.Debug($"Running V2 Squirrel.exe: '{_squirrelExePath} {String.Join(" ", args)}'"); + await Process.Start(_squirrelExePath, args).WaitForExitAsync(); + } + + public async Task ExecuteReleasifyWindows(ReleasifyWindowsCommand command) + { + if (!SquirrelRuntimeInfo.IsWindows || command.TargetRuntime.BaseRID != RuntimeOs.Windows) { + throw new NotSupportedException("Squirrel v2.x is only supported on/for Windows."); + } + + string msi = null; + if (command.BuildMsi) { + msi = command.TargetRuntime.Architecture.ToString(); + } + + var options = new ReleasifyOptions { + releaseDir = command.GetReleaseDirectory().FullName, + package = command.Package, + baseUrl = command.BaseUrl, + framework = command.Runtimes, + splashImage = command.SplashImage, + icon = command.Icon, + appIcon = command.AppIcon, + noDelta = command.NoDelta, + allowUnaware = false, + msi = msi, + signParams = command.SignParameters, + signTemplate = command.SignTemplate, + }; + + var args = new List { "releasify" }; + options.AddArgs(args); + _logger.Debug($"Running V2 Squirrel.exe: '{_squirrelExePath} {String.Join(" ", args)}'"); + await Process.Start(_squirrelExePath, args).WaitForExitAsync(); + } + + public Task ExecuteBundleOsx(BundleOsxCommand command) + { + throw new NotSupportedException("Squirrel v2.x is only supported on/for Windows."); + } + + public Task ExecuteReleasifyOsx(ReleasifyOsxCommand command) + { + throw new NotSupportedException("Squirrel v2.x is only supported on/for Windows."); + } + + public Task ExecuteGithubDownload(GitHubDownloadCommand command) + { + return ((ICommandRunner) _embedded).ExecuteGithubDownload(command); + } + + public Task ExecuteGithubUpload(GitHubUploadCommand command) + { + return ((ICommandRunner) _embedded).ExecuteGithubUpload(command); + } + + public Task ExecuteHttpDownload(HttpDownloadCommand command) + { + return ((ICommandRunner) _embedded).ExecuteHttpDownload(command); + } + + public Task ExecuteS3Download(S3DownloadCommand command) + { + return ((ICommandRunner) _embedded).ExecuteS3Download(command); + } + + public Task ExecuteS3Upload(S3UploadCommand command) + { + return ((ICommandRunner) _embedded).ExecuteS3Upload(command); + } + + private abstract class BaseOptions + { + public string releaseDir { get; set; } + + public virtual void AddArgs(List args) + { + if (!String.IsNullOrWhiteSpace(releaseDir)) { + args.Add("--releaseDir"); + args.Add(releaseDir); + } + } + } + + private class SigningOptions : BaseOptions + { + public string signParams { get; set; } + public string signTemplate { get; set; } + + public override void AddArgs(List args) + { + base.AddArgs(args); + + if (!String.IsNullOrWhiteSpace(signParams)) { + args.Add("--signParams"); + args.Add(signParams); + } + + if (!String.IsNullOrWhiteSpace(signTemplate)) { + args.Add("--signTemplate"); + args.Add(signTemplate); + } + } + } + + private class ReleasifyOptions : SigningOptions + { + public string package { get; set; } + public string baseUrl { get; set; } + public string framework { get; set; } + public string splashImage { get; set; } + public string icon { get; set; } + public string appIcon { get; set; } + public bool noDelta { get; set; } + public bool allowUnaware { get; set; } + public string msi { get; set; } + public string debugSetupExe { get; set; } + + public override void AddArgs(List args) + { + base.AddArgs(args); + + if (!String.IsNullOrWhiteSpace(package)) { + args.Add("--package"); + args.Add(package); + } + + if (!String.IsNullOrWhiteSpace(baseUrl)) { + args.Add("--baseUrl"); + args.Add(baseUrl); + } + + if (!String.IsNullOrWhiteSpace(framework)) { + args.Add("--framework"); + args.Add(framework); + } + + if (!String.IsNullOrWhiteSpace(splashImage)) { + args.Add("--splashImage"); + args.Add(splashImage); + } + + if (!String.IsNullOrWhiteSpace(icon)) { + args.Add("--icon"); + args.Add(icon); + } + + if (!String.IsNullOrWhiteSpace(appIcon)) { + args.Add("--appIcon"); + args.Add(appIcon); + } + + if (noDelta) { + args.Add("--noDelta"); + } + + if (allowUnaware) { + args.Add("--allowUnaware"); + } + + if (!String.IsNullOrWhiteSpace(msi)) { + args.Add("--msi"); + args.Add(msi); + } + + if (!String.IsNullOrWhiteSpace(debugSetupExe)) { + args.Add("--debugSetupExe"); + args.Add(debugSetupExe); + } + } + } + + private class PackOptions : ReleasifyOptions + { + public string packId { get; set; } + public string packTitle { get; set; } + public string packVersion { get; set; } + public string packAuthors { get; set; } + public string packDirectory { get; set; } + public bool includePdb { get; set; } + public string releaseNotes { get; set; } + + public override void AddArgs(List args) + { + base.AddArgs(args); + + if (!String.IsNullOrWhiteSpace(packId)) { + args.Add("--packId"); + args.Add(packId); + } + + if (!String.IsNullOrWhiteSpace(packTitle)) { + args.Add("--packTitle"); + args.Add(packTitle); + } + + if (!String.IsNullOrWhiteSpace(packVersion)) { + args.Add("--packVersion"); + args.Add(packVersion); + } + + if (!String.IsNullOrWhiteSpace(packAuthors)) { + args.Add("--packAuthors"); + args.Add(packAuthors); + } + + if (!String.IsNullOrWhiteSpace(packDirectory)) { + args.Add("--packDir"); + args.Add(packDirectory); + } + + if (includePdb) { + args.Add("--includePdb"); + } + + if (!String.IsNullOrWhiteSpace(releaseNotes)) { + args.Add("--releaseNotes"); + args.Add(releaseNotes); + } + } + } +} diff --git a/src/Squirrel.Csq/Compatibility/NugetDownloader.cs b/src/Squirrel.Csq/Compatibility/NugetDownloader.cs deleted file mode 100644 index 30acadcd..00000000 --- a/src/Squirrel.Csq/Compatibility/NugetDownloader.cs +++ /dev/null @@ -1,76 +0,0 @@ -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.Configuration; -using NuGet.Packaging.Core; -using NuGet.Protocol.Core.Types; -using NuGet.Versioning; - -namespace Squirrel.Tool -{ - public class NugetDownloader - { - private readonly ILogger _logger; - private readonly PackageSource _packageSource; - private readonly SourceRepository _sourceRepository; - private readonly SourceCacheContext _sourceCacheContext; - - public NugetDownloader(ILogger logger) - { - _logger = logger; - _packageSource = new PackageSource("https://api.nuget.org/v3/index.json", "NuGet.org"); - _sourceRepository = new SourceRepository(_packageSource, Repository.Provider.GetCoreV3()); - _sourceCacheContext = new SourceCacheContext(); - } - - public async Task GetPackageMetadata(string packageName, string version, CancellationToken cancellationToken) - { - PackageMetadataResource packageMetadataResource = _sourceRepository.GetResource(); - FindPackageByIdResource packageByIdResource = _sourceRepository.GetResource(); - IPackageSearchMetadata package = null; - - var prerelease = version?.Equals("pre", StringComparison.InvariantCultureIgnoreCase) == true; - if (version is null || version.Equals("latest", StringComparison.InvariantCultureIgnoreCase) || prerelease) { - // get latest (or prerelease) version - IEnumerable metadata = await packageMetadataResource - .GetMetadataAsync(packageName, true, true, _sourceCacheContext, _logger, cancellationToken) - .ConfigureAwait(false); - package = metadata - .Where(x => x.IsListed) - .Where(x => prerelease || !x.Identity.Version.IsPrerelease) - .OrderByDescending(x => x.Identity.Version) - .FirstOrDefault(); - } else { - // resolve version ranges and wildcards - var versions = await packageByIdResource.GetAllVersionsAsync(packageName, _sourceCacheContext, _logger, cancellationToken) - .ConfigureAwait(false); - var resolved = versions.FindBestMatch(VersionRange.Parse(version), version => version); - - // get exact version - var packageIdentity = new PackageIdentity(packageName, resolved); - package = await packageMetadataResource - .GetMetadataAsync(packageIdentity, _sourceCacheContext, _logger, cancellationToken) - .ConfigureAwait(false); - } - - if (package is null) { - throw new Exception($"Unable to locate {packageName} {version} on NuGet.org"); - } - - return package; - } - - public async Task DownloadPackageToStream(IPackageSearchMetadata package, Stream targetStream, CancellationToken cancellationToken) - { - FindPackageByIdResource packageByIdResource = _sourceRepository.GetResource(); - await packageByIdResource - .CopyNupkgToStreamAsync(package.Identity.Id, package.Identity.Version, targetStream, _sourceCacheContext, _logger, cancellationToken) - .ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/Squirrel.Csq/Compatibility/NullNugetLogger.cs b/src/Squirrel.Csq/Compatibility/NullNugetLogger.cs deleted file mode 100644 index e0a115db..00000000 --- a/src/Squirrel.Csq/Compatibility/NullNugetLogger.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Threading.Tasks; -using NugetLevel = NuGet.Common.LogLevel; -using NugetLogger = NuGet.Common.ILogger; -using NugetMessage = NuGet.Common.ILogMessage; - -namespace Squirrel.CommandLine -{ - class NullNugetLogger : NugetLogger - { - void NugetLogger.LogDebug(string data) - { - } - - void NugetLogger.LogVerbose(string data) - { - } - - void NugetLogger.LogInformation(string data) - { - } - - void NugetLogger.LogMinimal(string data) - { - } - - void NugetLogger.LogWarning(string data) - { - } - - void NugetLogger.LogError(string data) - { - } - - void NugetLogger.LogInformationSummary(string data) - { - } - - void NugetLogger.Log(NugetLevel level, string data) - { - } - - Task NugetLogger.LogAsync(NugetLevel level, string data) - { - return Task.CompletedTask; - } - - void NugetLogger.Log(NugetMessage message) - { - } - - Task NugetLogger.LogAsync(NugetMessage message) - { - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/Squirrel.Csq/Compatibility/oldProgram.cs b/src/Squirrel.Csq/Compatibility/oldProgram.cs deleted file mode 100644 index e2eae054..00000000 --- a/src/Squirrel.Csq/Compatibility/oldProgram.cs +++ /dev/null @@ -1,306 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.CommandLine; -//using System.CommandLine.Builder; -//using System.CommandLine.Invocation; -//using System.CommandLine.Parsing; -//using System.Diagnostics; -//using System.IO; -//using System.Linq; -//using System.Threading; -//using System.Threading.Tasks; -//using System.Xml; -//using System.Xml.Linq; -//using Microsoft.Build.Construction; -//using Microsoft.Extensions.Configuration; -//using Microsoft.Extensions.Hosting; -//using Squirrel.CommandLine; - -//namespace Squirrel.Tool -//{ -// class Program -// { -// const string CLOWD_PACKAGE_NAME = "Clowd.Squirrel"; - -// private static ConsoleLogger _logger; - -// private static CliOption CsqVersion { get; } -// = new CliOption("--csq-version"); -// private static CliOption CsqSolutionPath { get; } -// = new CliOption(new[] { "--csq-sln", "--csq-solution" }).ExistingOnly(); -// private static CliOption Verbose { get; } -// = new CliOption("--verbose"); - -// static Task Main(string[] args) -// { - -// HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); -// builder.Environment.ContentRootPath = Directory.GetCurrentDirectory(); -// builder.Configuration.AddJsonFile("hostsettings.json", optional: true); -// builder.Configuration.AddEnvironmentVariables(prefix: "PREFIX_"); -// builder.Configuration.AddCommandLine(args); -// _logger = ConsoleLogger.RegisterLogger(); - -// System.CommandLine.Hosting.HostingExtensions. - -// CliRootCommand rootCommand = new CliRootCommand() { -// CsqVersion, -// CsqSolutionPath, -// Verbose -// }; -// rootCommand.TreatUnmatchedTokensAsErrors = false; - -// rootCommand.SetHandler(MainInner); - -// ParseResult parseResult = rootCommand.Parse(inargs); - -// CommandLineBuilder builder = new CommandLineBuilder(rootCommand); - -// if (parseResult.Directives.Contains("local")) { -// builder.UseDefaults(); -// } else { -// builder -// .UseParseErrorReporting() -// .UseExceptionHandler() -// .CancelOnProcessTermination(); -// } - -// return builder.Build().InvokeAsync(inargs); -// } - -// static async Task MainInner(InvocationContext context) -// { -// bool verbose = context.ParseResult.GetValueForOption(Verbose); -// FileSystemInfo explicitSolutionPath = context.ParseResult.GetValueForOption(CsqSolutionPath); -// string explicitSquirrelVersion = context.ParseResult.GetValueForOption(CsqVersion); - -// // we want to forward the --verbose argument to Squirrel, too. -// var verboseArgs = verbose ? new string[] { "--verbose" } : Array.Empty(); -// string[] restArgs = context.ParseResult.UnmatchedTokens -// .Concat(verboseArgs) -// .ToArray(); - -// if (verbose) { -// _logger.Level = LogLevel.Debug; -// } - -// context.Console.WriteLine($"Squirrel Locator 'csq' {SquirrelRuntimeInfo.SquirrelDisplayVersion}"); -// _logger.Write($"Entry EXE: {SquirrelRuntimeInfo.EntryExePath}", LogLevel.Debug); -// CancellationToken cancellationToken = context.GetCancellationToken(); - -// await CheckForUpdates(cancellationToken).ConfigureAwait(false); - -//#if DEBUG && false -// var devcsproj = Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "src", "Squirrel.CommandLine", "Squirrel.CommandLine.csproj"); -// var devargs = new[] { "run", "--no-build", "-v", "q", "--project", devcsproj, "--" }.Concat(restArgs).ToArray(); -// context.ExitCode = RunProcess("dotnet", devargs); -// return; -//#endif - -// var solutionDir = FindSolutionDirectory(explicitSolutionPath?.FullName); -// var nugetPackagesDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); -// var cacheDir = Path.GetFullPath(solutionDir is null ? ".squirrel" : Path.Combine(solutionDir, ".squirrel")); - -// Dictionary packageSearchPaths = new(); -// packageSearchPaths.Add("nuget user profile cache", Path.Combine(nugetPackagesDir, CLOWD_PACKAGE_NAME.ToLower(), "{0}", "tools")); -// if (solutionDir != null) -// packageSearchPaths.Add("visual studio packages cache", Path.Combine(solutionDir, "packages", CLOWD_PACKAGE_NAME + ".{0}", "tools")); -// packageSearchPaths.Add("squirrel cache", Path.Combine(cacheDir, "{0}", "tools")); - -// async Task runSquirrel(string version, CancellationToken cancellationToken) -// { -// foreach (var kvp in packageSearchPaths) { -// var path = String.Format(kvp.Value, version); -// if (Directory.Exists(path)) { -// _logger.Write($"Running {CLOWD_PACKAGE_NAME} {version} from {kvp.Key}", LogLevel.Info); -// return RunCsqFromPath(path, restArgs); -// } -// } - -// // we did not find it locally on first pass, search for the package online -// var dl = new NugetDownloader(_logger); -// var package = await dl.GetPackageMetadata(CLOWD_PACKAGE_NAME, version, cancellationToken).ConfigureAwait(false); - -// // search one more time now that we've potentially resolved the nuget version -// foreach (var kvp in packageSearchPaths) { -// var path = String.Format(kvp.Value, package.Identity.Version); -// if (Directory.Exists(path)) { -// _logger.Write($"Running {CLOWD_PACKAGE_NAME} {package.Identity.Version} from {kvp.Key}", LogLevel.Info); -// return RunCsqFromPath(path, restArgs); -// } -// } - -// // let's try to download it from NuGet.org -// var versionDir = Path.Combine(cacheDir, package.Identity.Version.ToString()); -// Directory.CreateDirectory(cacheDir); -// Directory.CreateDirectory(versionDir); - -// _logger.Write($"Downloading {package.Identity} from NuGet.", LogLevel.Info); - -// var filePath = Path.Combine(versionDir, package.Identity + ".nupkg"); -// using (var fs = File.Create(filePath)) { -// await dl.DownloadPackageToStream(package, fs, cancellationToken).ConfigureAwait(false); -// } - -// EasyZip.ExtractZipToDirectory(filePath, versionDir); - -// var toolsPath = Path.Combine(versionDir, "tools"); -// return RunCsqFromPath(toolsPath, restArgs); -// } - -// if (explicitSquirrelVersion != null) { -// context.ExitCode = await runSquirrel(explicitSquirrelVersion, cancellationToken).ConfigureAwait(false); -// return; -// } - -// if (solutionDir is null) { -// throw new Exception($"Could not find '.sln'. Specify solution with '{CsqSolutionPath.Aliases.First()}=', or specify version of squirrel to use with '{CsqVersion.Aliases.First()}='."); -// } - -// _logger.Write("Solution dir found at: " + solutionDir, LogLevel.Debug); - -// // TODO actually read the SLN file rather than just searching for all .csproj files -// var dependencies = GetPackageVersionsFromDir(solutionDir, CLOWD_PACKAGE_NAME).Distinct().ToArray(); - -// if (dependencies.Length == 0) { -// throw new Exception($"{CLOWD_PACKAGE_NAME} nuget package was not found installed in solution."); -// } - -// if (dependencies.Length > 1) { -// throw new Exception($"Found multiple versions of {CLOWD_PACKAGE_NAME} installed in solution ({string.Join(", ", dependencies)}). " + -// $"Please consolidate the following to a single version, or specify the version to use with '{CsqVersion.Aliases.First()}='"); -// } - -// var targetVersion = dependencies.Single(); - -// context.ExitCode = await runSquirrel(targetVersion, cancellationToken).ConfigureAwait(false); -// } - -// static async Task CheckForUpdates(CancellationToken cancellationToken) -// { -// try { -// var myVer = SquirrelRuntimeInfo.SquirrelNugetVersion; -// var dl = new NugetDownloader(new NullNugetLogger()); -// var package = await dl.GetPackageMetadata("csq", (myVer.IsPrerelease || myVer.HasMetadata) ? "pre" : "latest", cancellationToken).ConfigureAwait(false); -// if (package.Identity.Version > myVer) -// _logger.Write($"There is a new version of csq available ({package.Identity.Version})", LogLevel.Warn); -// } catch { } -// } - -// static string FindSolutionDirectory(string slnArgument) -// { -// if (!String.IsNullOrWhiteSpace(slnArgument)) { -// if (File.Exists(slnArgument) && slnArgument.EndsWith(".sln", StringComparison.InvariantCultureIgnoreCase)) { -// // we were given a sln file as argument -// return Path.GetDirectoryName(Path.GetFullPath(slnArgument)); -// } - -// if (Directory.Exists(slnArgument) && Directory.EnumerateFiles(slnArgument, "*.sln").Any()) { -// return Path.GetFullPath(slnArgument); -// } -// } - -// // try to find the solution directory from cwd -// var cwd = Environment.CurrentDirectory; -// var slnSearchDirs = new string[] { -// cwd, -// Path.Combine(cwd, ".."), -// Path.Combine(cwd, "..", ".."), -// }; - -// return slnSearchDirs.FirstOrDefault(d => Directory.EnumerateFiles(d, "*.sln").Any()); -// } - -// static int RunCsqFromPath(string toolRootPath, string[] args) -// { -// // > v3.0.170 -// if (File.Exists(Path.Combine(toolRootPath, "Squirrel.CommandLine.runtimeconfig.json"))) { -// var cliPath = Path.Combine(toolRootPath, "Squirrel.CommandLine.dll"); -// var dnargs = new[] { cliPath }.Concat(args).ToArray(); -// _logger.Write("running dotnet " + String.Join(" ", dnargs), LogLevel.Debug); -// return RunProcess("dotnet", dnargs); -// } - -// // v3.0 - v3.0.170 -// var toolDllPath = Path.Combine(toolRootPath, "csq.dll"); -// if (File.Exists(toolDllPath)) { -// var dnargs = new[] { toolDllPath, "--csq-embedded" }.Concat(args).ToArray(); -// _logger.Write("running dotnet " + String.Join(" ", dnargs), LogLevel.Debug); -// return RunProcess("dotnet", dnargs); -// } - -// // < v3.0 -// var toolExePath = Path.Combine(toolRootPath, "Squirrel.exe"); -// if (File.Exists(toolExePath)) { -// if (!SquirrelRuntimeInfo.IsWindows) -// throw new NotSupportedException( -// $"Squirrel at '{toolRootPath}' does not support this operating system. Please update the package version to >= 3.0"); -// _logger.Write("running " + toolExePath + " " + String.Join(" ", args), LogLevel.Debug); -// return RunProcess(toolExePath, args); -// } - -// throw new Exception("Unable to locate Squirrel at: " + toolRootPath); -// } - -// static int RunProcess(string path, string[] args) -// { -// var p = Process.Start(path, args); -// p.WaitForExit(); -// return p.ExitCode; -// } - -// static IEnumerable GetPackageVersionsFromDir(string rootDir, string packageName) -// { -// // old-style framework packages.config -// foreach (var packagesFile in EnumerateFilesUntilSpecificDepth(rootDir, "packages.config", 3)) { -// using var xmlStream = File.OpenRead(packagesFile); -// using var xmlReader = new XmlTextReader(xmlStream); -// var xdoc = XDocument.Load(xmlReader); - -// var sqel = xdoc.Root?.Elements().FirstOrDefault(e => e.Attribute("id")?.Value == packageName); -// var ver = sqel?.Attribute("version"); -// if (ver == null) continue; - -// _logger.Write($"{packageName} {ver.Value} referenced in {packagesFile}", LogLevel.Debug); - -// if (ver.Value.Contains('*')) -// throw new Exception( -// $"Wildcard versions are not supported in packages.config. Remove wildcard or upgrade csproj format to use PackageReference."); - -// yield return ver.Value; -// } - -// // new-style csproj PackageReference -// foreach (var projFile in EnumerateFilesUntilSpecificDepth(rootDir, "*.csproj", 3)) { -// var proj = ProjectRootElement.Open(projFile); -// if (proj == null) continue; - -// ProjectItemElement item = proj.Items.FirstOrDefault(i => i.ItemType == "PackageReference" && i.Include == packageName); -// if (item == null) continue; - -// var version = item.Children.FirstOrDefault(x => x.ElementName == "Version") as ProjectMetadataElement; -// if (version?.Value == null) continue; - -// _logger.Write($"{packageName} {version.Value} referenced in {projFile}", LogLevel.Debug); - -// yield return version.Value; -// } -// } - -// static IEnumerable EnumerateFilesUntilSpecificDepth(string rootPath, string searchPattern, int maxDepth, int currentDepth = 0) -// { -// var files = Directory.EnumerateFiles(rootPath, searchPattern, SearchOption.TopDirectoryOnly); -// foreach (var f in files) { -// yield return f; -// } - -// if (currentDepth < maxDepth) { -// foreach (var dir in Directory.EnumerateDirectories(rootPath)) { -// foreach (var file in EnumerateFilesUntilSpecificDepth(dir, searchPattern, maxDepth, currentDepth + 1)) { -// yield return file; -// } -// } -// } -// } -// } -//} \ No newline at end of file diff --git a/src/Squirrel.Csq/GlobalUsings.cs b/src/Squirrel.Csq/GlobalUsings.cs index bfce7dc9..64393e67 100644 --- a/src/Squirrel.Csq/GlobalUsings.cs +++ b/src/Squirrel.Csq/GlobalUsings.cs @@ -5,3 +5,4 @@ global using System.CommandLine.Parsing; global using System.IO; global using System.Linq; global using System.Threading.Tasks; +global using Microsoft.Extensions.Logging; diff --git a/src/Squirrel.Csq/Program.cs b/src/Squirrel.Csq/Program.cs index a53a7a19..7a2ec788 100644 --- a/src/Squirrel.Csq/Program.cs +++ b/src/Squirrel.Csq/Program.cs @@ -3,33 +3,44 @@ using Serilog.Events; using Serilog; using Microsoft.Extensions.Configuration; using Squirrel.Csq.Commands; -using Squirrel.Deployment; using Microsoft.Extensions.DependencyInjection; +using Squirrel.Csq.Updates; +using Squirrel.Csq.Compat; namespace Squirrel.Csq; public class Program { public static CliOption TargetRuntime { get; } - = new CliOption("runtime", "-r", "--runtime", "The target runtime to build packages for.") + = new CliOption("--runtime", "-r") + .SetDescription("The target runtime to build packages for.") .SetArgumentHelpName("RID") .MustBeSupportedRid() .SetRequired(); public static CliOption VerboseOption { get; } - = new CliOption("--verbose", "Print diagnostic messages."); + = new CliOption("--verbose") + .SetDescription("Print diagnostic messages."); - public static Task Main(string[] args) + private static CliOption CsqSolutionPath { get; } + = new CliOption("--solution") + .SetDescription("Explicit path to project solution (.sln)") + .AcceptExistingOnly(); + + private static IServiceProvider Provider { get; set; } + + public static async Task Main(string[] args) { CliRootCommand platformRootCommand = new CliRootCommand() { TargetRuntime, VerboseOption, + CsqSolutionPath, }; platformRootCommand.TreatUnmatchedTokensAsErrors = false; ParseResult parseResult = platformRootCommand.Parse(args); var runtime = RID.Parse(parseResult.GetValue(TargetRuntime) ?? SquirrelRuntimeInfo.SystemOs.GetOsShortName()); - + var solutionPath = parseResult.GetValue(CsqSolutionPath); bool verbose = parseResult.GetValue(VerboseOption); var builder = Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings { @@ -39,77 +50,68 @@ public class Program Configuration = new ConfigurationManager(), }); - var minLevel = verbose ? LogEventLevel.Debug : LogEventLevel.Information; + builder.Services.AddSingleton(s => new RunnerFactory(s.GetRequiredService(), solutionPath, s.GetRequiredService())); + builder.Configuration.AddEnvironmentVariables("CSQ_"); + var minLevel = verbose ? LogEventLevel.Debug : LogEventLevel.Information; Log.Logger = new LoggerConfiguration() .MinimumLevel.Is(minLevel) .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .WriteTo.Console() .CreateLogger(); - builder.Logging.AddSerilog(); var host = builder.Build(); + var logger = host.Services.GetRequiredService>(); + Provider = host.Services; - var logger = host.Services.GetRequiredService>(); - - CliRootCommand rootCommand = new CliRootCommand($"Squirrel {SquirrelRuntimeInfo.SquirrelDisplayVersion} for creating and distributing Squirrel releases."); - rootCommand.Options.Add(TargetRuntime); - rootCommand.Options.Add(VerboseOption); + CliRootCommand rootCommand = new CliRootCommand($"Squirrel {SquirrelRuntimeInfo.SquirrelDisplayVersion} for creating and distributing Squirrel releases.") { + TargetRuntime, + VerboseOption, + CsqSolutionPath, + }; switch (runtime.BaseRID) { case RuntimeOs.Windows: if (!SquirrelRuntimeInfo.IsWindows) logger.Warn("Cross-compiling will cause some commands and options of Squirrel to be unavailable."); - Add(rootCommand, new PackWindowsCommand(), Windows.Commands.Pack); - Add(rootCommand, new ReleasifyWindowsCommand(), Windows.Commands.Releasify); + Add(rootCommand, new PackWindowsCommand(), nameof(ICommandRunner.ExecutePackWindows)); + Add(rootCommand, new ReleasifyWindowsCommand(), nameof(ICommandRunner.ExecuteReleasifyWindows)); break; case RuntimeOs.OSX: if (!SquirrelRuntimeInfo.IsOSX) - throw new InvalidOperationException("Cannot create OSX packages on non-OSX platforms."); - Add(rootCommand, new BundleOsxCommand(), OSX.Commands.Bundle); - Add(rootCommand, new ReleasifyOsxCommand(), OSX.Commands.Releasify); + throw new NotSupportedException("Cannot create OSX packages on non-OSX platforms."); + Add(rootCommand, new BundleOsxCommand(), nameof(ICommandRunner.ExecuteBundleOsx)); + Add(rootCommand, new ReleasifyOsxCommand(), nameof(ICommandRunner.ExecuteReleasifyOsx)); break; default: throw new NotSupportedException("Unsupported OS platform: " + runtime.BaseRID.GetOsLongName()); } CliCommand downloadCommand = new CliCommand("download", "Download's the latest release from a remote update source."); - Add(downloadCommand, new HttpDownloadCommand(), options => SimpleWebRepository.DownloadRecentPackages(options)); - Add(downloadCommand, new S3DownloadCommand(), options => S3Repository.DownloadRecentPackages(options)); - Add(downloadCommand, new GitHubDownloadCommand(), options => GitHubRepository.DownloadRecentPackages(options)); + Add(downloadCommand, new HttpDownloadCommand(), nameof(ICommandRunner.ExecuteHttpDownload)); + Add(downloadCommand, new S3DownloadCommand(), nameof(ICommandRunner.ExecuteS3Download)); + Add(downloadCommand, new GitHubDownloadCommand(), nameof(ICommandRunner.ExecuteGithubDownload)); rootCommand.Add(downloadCommand); var uploadCommand = new CliCommand("upload", "Upload local package(s) to a remote update source."); - Add(uploadCommand, new S3UploadCommand(), options => S3Repository.UploadMissingPackages(options)); - Add(uploadCommand, new GitHubUploadCommand(), options => GitHubRepository.UploadMissingPackages(options)); + Add(uploadCommand, new S3UploadCommand(), nameof(ICommandRunner.ExecuteS3Upload)); + Add(uploadCommand, new GitHubUploadCommand(), nameof(ICommandRunner.ExecuteGithubUpload)); rootCommand.Add(uploadCommand); var cli = new CliConfiguration(rootCommand); - - return cli.InvokeAsync(args); + return await cli.InvokeAsync(args); } - private static CliCommand Add(CliCommand parent, T command, Action execute) - where T : BaseCommand - { - command.SetAction((ctx) => { - command.SetProperties(ctx); - command.TargetRuntime = RID.Parse(ctx.GetValue(TargetRuntime)); - execute(command); - }); - parent.Subcommands.Add(command); - return command; - } - - private static CliCommand Add(CliCommand parent, T command, Func execute) + private static CliCommand Add(CliCommand parent, T command, string commandName) where T : BaseCommand { command.SetAction((ctx, token) => { command.SetProperties(ctx); command.TargetRuntime = RID.Parse(ctx.GetValue(TargetRuntime)); - return execute(command); + var factory = Provider.GetRequiredService(); + return factory.CreateAndExecuteAsync(commandName, command); }); parent.Subcommands.Add(command); return command; diff --git a/src/Squirrel.Csq/Squirrel.Csq.csproj b/src/Squirrel.Csq/Squirrel.Csq.csproj index f9f98038..825ab521 100644 --- a/src/Squirrel.Csq/Squirrel.Csq.csproj +++ b/src/Squirrel.Csq/Squirrel.Csq.csproj @@ -14,6 +14,7 @@ A .NET Core Tool that uses the Squirrel framework to create installers and update packages for dotnet applications. Clowd_200.png latest + $(NoWarn);CA2007;CS8002 @@ -27,6 +28,7 @@ + diff --git a/src/Squirrel.Csq/SquirrelHost.cs b/src/Squirrel.Csq/SquirrelHost.cs deleted file mode 100644 index b432bb8a..00000000 --- a/src/Squirrel.Csq/SquirrelHost.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.CommandLine; -using System.CommandLine.Parsing; -using System.Threading.Tasks; -using Squirrel.CommandLine.Commands; -using Squirrel.CommandLine.Sync; -using Squirrel.SimpleSplat; - -namespace Squirrel.CommandLine -{ - public class SquirrelHost - { - public static Option TargetRuntime { get; } - = new Option(new[] { "-r", "--runtime" }, "The target runtime to build packages for.") - .SetArgumentHelpName("RID") - .MustBeSupportedRid() - .SetRequired(); - - public static Option VerboseOption { get; } - = new Option("--verbose", "Print diagnostic messages."); - - public static Option AddSearchPathOption { get; } - = new Option("--addSearchPath", "Add additional search directories when looking for helper exe's.") - .SetArgumentHelpName("DIR"); - - public static int Main(string[] args) - { - var logger = ConsoleLogger.RegisterLogger(); - - RootCommand platformRootCommand = new RootCommand() { - TargetRuntime, - VerboseOption, - AddSearchPathOption, - }; - platformRootCommand.TreatUnmatchedTokensAsErrors = false; - - ParseResult parseResult = platformRootCommand.Parse(args); - - bool verbose = parseResult.GetValueForOption(VerboseOption); - if (parseResult.GetValueForOption(AddSearchPathOption) is { } searchPath) { - foreach (var v in searchPath) { - HelperFile.AddSearchPath(v); - } - } - - RootCommand rootCommand = new RootCommand($"Squirrel {SquirrelRuntimeInfo.SquirrelDisplayVersion} for creating and distributing Squirrel releases."); - rootCommand.AddGlobalOption(TargetRuntime); - rootCommand.AddGlobalOption(VerboseOption); - rootCommand.AddGlobalOption(AddSearchPathOption); - - var runtime = RID.Parse(parseResult.GetValueForOption(TargetRuntime) ?? SquirrelRuntimeInfo.SystemOs.GetOsShortName()); - - switch (runtime.BaseRID) { - case RuntimeOs.Windows: - if (!SquirrelRuntimeInfo.IsWindows) - logger.Write("Cross-compiling will cause some commands and options of Squirrel to be unavailable.", LogLevel.Warn); - Add(rootCommand, new PackWindowsCommand(), Windows.Commands.Pack); - Add(rootCommand, new ReleasifyWindowsCommand(), Windows.Commands.Releasify); - - // temporarily, upload only supports windows. - Command uploadCommand = new Command("upload", "Upload local package(s) to a remote update source."); - Add(uploadCommand, new S3UploadCommand(), options => S3Repository.UploadMissingPackages(options)); - Add(uploadCommand, new GitHubUploadCommand(), options => GitHubRepository.UploadMissingPackages(options)); - rootCommand.Add(uploadCommand); - - break; - case RuntimeOs.OSX: - if (!SquirrelRuntimeInfo.IsOSX) - logger.Write("Cross-compiling will cause some commands and options of Squirrel to be unavailable.", LogLevel.Warn); - Add(rootCommand, new BundleOsxCommand(), OSX.Commands.Bundle); - Add(rootCommand, new ReleasifyOsxCommand(), OSX.Commands.Releasify); - break; - default: - throw new NotSupportedException("Unsupported OS platform: " + runtime.BaseRID.GetOsLongName()); - } - - if (verbose) { - logger.Level = LogLevel.Debug; - } - - Command downloadCommand = new Command("download", "Download's the latest release from a remote update source."); - Add(downloadCommand, new HttpDownloadCommand(), options => SimpleWebRepository.DownloadRecentPackages(options)); - Add(downloadCommand, new S3DownloadCommand(), options => S3Repository.DownloadRecentPackages(options)); - Add(downloadCommand, new GitHubDownloadCommand(), options => GitHubRepository.DownloadRecentPackages(options)); - rootCommand.Add(downloadCommand); - - return rootCommand.Invoke(args); - } - - private static Command Add(Command parent, T command, Action execute) - where T : BaseCommand - { - command.SetHandler((ctx) => { - command.SetProperties(ctx.ParseResult); - command.TargetRuntime = RID.Parse(ctx.ParseResult.GetValueForOption(TargetRuntime)); - execute(command); - }); - parent.AddCommand(command); - return command; - } - - private static Command Add(Command parent, T command, Func execute) - where T : BaseCommand - { - command.SetHandler((ctx) => { - command.SetProperties(ctx.ParseResult); - command.TargetRuntime = RID.Parse(ctx.ParseResult.GetValueForOption(TargetRuntime)); - return execute(command); - }); - parent.AddCommand(command); - return command; - } - } -} \ No newline at end of file diff --git a/src/Squirrel.Csq/Updates/NugetDownloader.cs b/src/Squirrel.Csq/Updates/NugetDownloader.cs new file mode 100644 index 00000000..ddc32633 --- /dev/null +++ b/src/Squirrel.Csq/Updates/NugetDownloader.cs @@ -0,0 +1,69 @@ +using System.Threading; +using NuGet.Configuration; +using NuGet.Packaging.Core; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using NugetLogger = NuGet.Common.ILogger; + +namespace Squirrel.Csq.Updates; + +public class NugetDownloader +{ + private readonly NugetLogger _logger; + private readonly PackageSource _packageSource; + private readonly SourceRepository _sourceRepository; + private readonly SourceCacheContext _sourceCacheContext; + + public NugetDownloader(NugetLogger logger) + { + _logger = logger; + _packageSource = new PackageSource("https://api.nuget.org/v3/index.json", "NuGet.org"); + _sourceRepository = new SourceRepository(_packageSource, Repository.Provider.GetCoreV3()); + _sourceCacheContext = new SourceCacheContext(); + } + + public async Task GetPackageMetadata(string packageName, string version, CancellationToken cancellationToken) + { + PackageMetadataResource packageMetadataResource = _sourceRepository.GetResource(); + FindPackageByIdResource packageByIdResource = _sourceRepository.GetResource(); + IPackageSearchMetadata package = null; + + var prerelease = version?.Equals("pre", StringComparison.InvariantCultureIgnoreCase) == true; + if (version is null || version.Equals("latest", StringComparison.InvariantCultureIgnoreCase) || prerelease) { + // get latest (or prerelease) version + IEnumerable metadata = await packageMetadataResource + .GetMetadataAsync(packageName, true, true, _sourceCacheContext, _logger, cancellationToken) + .ConfigureAwait(false); + package = metadata + .Where(x => x.IsListed) + .Where(x => prerelease || !x.Identity.Version.IsPrerelease) + .OrderByDescending(x => x.Identity.Version) + .FirstOrDefault(); + } else { + // resolve version ranges and wildcards + var versions = await packageByIdResource.GetAllVersionsAsync(packageName, _sourceCacheContext, _logger, cancellationToken) + .ConfigureAwait(false); + var resolved = versions.FindBestMatch(VersionRange.Parse(version), version => version); + + // get exact version + var packageIdentity = new PackageIdentity(packageName, resolved); + package = await packageMetadataResource + .GetMetadataAsync(packageIdentity, _sourceCacheContext, _logger, cancellationToken) + .ConfigureAwait(false); + } + + if (package is null) { + throw new Exception($"Unable to locate {packageName} {version} on NuGet.org"); + } + + return package; + } + + public async Task DownloadPackageToStream(IPackageSearchMetadata package, Stream targetStream, CancellationToken cancellationToken) + { + FindPackageByIdResource packageByIdResource = _sourceRepository.GetResource(); + await packageByIdResource + .CopyNupkgToStreamAsync(package.Identity.Id, package.Identity.Version, targetStream, _sourceCacheContext, _logger, cancellationToken) + .ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/Squirrel.Csq/Updates/NullNugetLogger.cs b/src/Squirrel.Csq/Updates/NullNugetLogger.cs new file mode 100644 index 00000000..5afd4e0d --- /dev/null +++ b/src/Squirrel.Csq/Updates/NullNugetLogger.cs @@ -0,0 +1,54 @@ +using NugetLevel = NuGet.Common.LogLevel; +using NugetLogger = NuGet.Common.ILogger; +using NugetMessage = NuGet.Common.ILogMessage; + +namespace Squirrel.Csq.Updates; + +class NullNugetLogger : NugetLogger +{ + void NugetLogger.LogDebug(string data) + { + } + + void NugetLogger.LogVerbose(string data) + { + } + + void NugetLogger.LogInformation(string data) + { + } + + void NugetLogger.LogMinimal(string data) + { + } + + void NugetLogger.LogWarning(string data) + { + } + + void NugetLogger.LogError(string data) + { + } + + void NugetLogger.LogInformationSummary(string data) + { + } + + void NugetLogger.Log(NugetLevel level, string data) + { + } + + Task NugetLogger.LogAsync(NugetLevel level, string data) + { + return Task.CompletedTask; + } + + void NugetLogger.Log(NugetMessage message) + { + } + + Task NugetLogger.LogAsync(NugetMessage message) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Squirrel.Csq/Updates/UpdateChecker.cs b/src/Squirrel.Csq/Updates/UpdateChecker.cs new file mode 100644 index 00000000..59459fa4 --- /dev/null +++ b/src/Squirrel.Csq/Updates/UpdateChecker.cs @@ -0,0 +1,28 @@ +using System.Threading; +using Squirrel.Packaging; + +namespace Squirrel.Csq.Updates; + +public class UpdateChecker +{ + private readonly ILogger _logger; + + public UpdateChecker(ILogger logger) + { + _logger = logger; + } + + public async Task CheckForUpdates() + { + try { + var cancel = new CancellationTokenSource(3000); + var myVer = SquirrelRuntimeInfo.SquirrelNugetVersion; + var dl = new NugetDownloader(new NugetLoggingWrapper(_logger)); + var package = await dl.GetPackageMetadata("csq", (myVer.IsPrerelease || myVer.HasMetadata) ? "pre" : "latest", cancel.Token).ConfigureAwait(false); + if (package.Identity.Version > myVer) + _logger.Warn($"There is a new version of csq available ({package.Identity.Version})"); + } catch (Exception ex) { + _logger.Debug(ex, "Failed to check for updates."); + } + } +} diff --git a/version.json b/version.json index 8718ce6a..2ba8b2e1 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.0", + "version": "4.0", "gitCommitIdShortFixedLength": 7, "publicReleaseRefSpec": [ "^refs/heads/master$"