Add console progress bars with Spectre & add more parallelism

This commit is contained in:
Caelan Sayler
2024-01-08 16:57:52 +00:00
parent 22f9ede340
commit 0427a8848b
31 changed files with 914 additions and 568 deletions

View File

@@ -79,19 +79,19 @@ public class OsxPackCommandRunner
var nuget = new NugetConsole(_logger);
var nupkgPath = nuget.CreatePackageFromNuspecPath(tmp, appBundlePath, nuspecPath);
var rp = new ReleasePackageBuilder(_logger, nupkgPath);
var suggestedName = new ReleaseEntryName(packId, SemanticVersion.Parse(packVersion), false, options.TargetRuntime).ToFileName();
var newPkgPath = rp.CreateReleasePackage((i, pkg) => Path.Combine(releaseDir.FullName, suggestedName));
entryHelper.AddNewRelease(newPkgPath, channel);
//var rp = new ReleasePackage(nupkgPath);
//var suggestedName = new ReleaseEntryName(packId, SemanticVersion.Parse(packVersion), false, options.TargetRuntime).ToFileName();
//var newPkgPath = rp.CreateReleasePackage((i, pkg) => Path.Combine(releaseDir.FullName, suggestedName));
//entryHelper.AddNewRelease(newPkgPath, channel);
var prev = entryHelper.GetPreviousFullRelease(rp.Version, channel);
if (prev != null && options.DeltaMode != DeltaMode.None) {
_logger.Info("Creating Delta Packages");
var deltaBuilder = new DeltaPackageBuilder(_logger);
var deltaFile = rp.ReleasePackageFile.Replace("-full", "-delta");
var dp = deltaBuilder.CreateDeltaPackage(prev, rp, deltaFile, options.DeltaMode);
entryHelper.AddNewRelease(deltaFile, channel);
}
//var prev = entryHelper.GetPreviousFullRelease(rp.Version, channel);
//if (prev != null && options.DeltaMode != DeltaMode.None) {
// _logger.Info("Creating Delta Packages");
// var deltaBuilder = new DeltaPackageBuilder(_logger);
// var deltaFile = rp.ReleasePackageFile.Replace("-full", "-delta");
// var dp = deltaBuilder.CreateDeltaPackage(prev, rp, deltaFile, options.DeltaMode);
// entryHelper.AddNewRelease(deltaFile, channel);
//}
_logger.Info("Updating RELEASES files");
entryHelper.SaveReleasesFiles();

View File

@@ -98,7 +98,7 @@ public class HelperExe : HelperFile
// https://stackoverflow.com/questions/35619036/open-app-after-installation-from-pkg-file-in-mac
var postinstall = Path.Combine(tmpScripts, "postinstall");
File.WriteAllText(postinstall, $"#!/bin/sh\nsudo -u \"$USER\" open \"$2/{bundleName}/\"\nexit 0");
ChmodFileAsExecutable(postinstall);
Chmod.ChmodFileAsExecutable(postinstall);
// generate non-relocatable component pkg. this will be included into a product archive
var pkgPlistPath = Path.Combine(tmp, "tmp.plist");
@@ -232,36 +232,4 @@ public class HelperExe : HelperFile
Log.Info($"Creating ditto bundle '{outputZip}'");
InvokeAndThrowIfNonZero("ditto", args, null);
}
private const string OSX_CSTD_LIB = "libSystem.dylib";
private const string NIX_CSTD_LIB = "libc";
[SupportedOSPlatform("osx")]
[DllImport(OSX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int osx_chmod(string pathname, int mode);
[SupportedOSPlatform("linux")]
[DllImport(NIX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int nix_chmod(string pathname, int mode);
protected static void ChmodFileAsExecutable(string filePath)
{
Func<string, int, int> chmod;
if (VelopackRuntimeInfo.IsOSX) chmod = osx_chmod;
else if (VelopackRuntimeInfo.IsLinux) chmod = nix_chmod;
else return; // no-op on windows, all .exe files can be executed.
var filePermissionOctal = Convert.ToInt32("777", 8);
const int EINTR = 4;
int chmodReturnCode;
do {
chmodReturnCode = chmod(filePath, filePermissionOctal);
} while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
if (chmodReturnCode == -1) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {filePath}.");
}
}
}

View File

@@ -1,23 +1,194 @@
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Spectre.Console;
using Velopack.Compression;
using Velopack.NuGet;
using Velopack.Windows;
namespace Velopack.Packaging.Windows.Commands;
public class WindowsPackCommandRunner
public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
{
private readonly ILogger _logger;
public WindowsPackCommandRunner(ILogger logger)
: base(RuntimeOs.Windows, logger)
{
_logger = logger;
}
public void Pack(WindowsPackOptions options)
protected override Task CodeSign(Action<int> progress, string packDir)
{
using (Utility.GetTempDirectory(out var tmp)) {
var nupkgPath = new NugetConsole(_logger).CreatePackageFromOptions(tmp, options);
options.Package = nupkgPath;
var runner = new WindowsReleasifyCommandRunner(_logger);
runner.Releasify(options);
var filesToSign = new DirectoryInfo(packDir).GetAllFilesRecursively()
.Where(x => Options.SignSkipDll ? Utility.PathPartEndsWith(x.Name, ".exe") : Utility.FileIsLikelyPEImage(x.Name))
.Select(x => x.FullName)
.ToArray();
SignFilesImpl(Options, packDir, filesToSign);
return Task.CompletedTask;
}
protected override Task<string> PreprocessPackDir(Action<int> progress, string packDir, string nuspecText)
{
// fail the release if this is a clickonce application
if (Directory.EnumerateFiles(packDir, "*.application").Any(f => File.ReadAllText(f).Contains("clickonce"))) {
throw new ArgumentException(
"Velopack does not support building releases for ClickOnce applications. " +
"Please publish your application to a folder without ClickOnce.");
}
// copy files to temp dir, so we can modify them
var dir = TempDir.CreateSubdirectory("PreprocessPackDirWin");
CopyFiles(new DirectoryInfo(packDir), dir, progress);
File.WriteAllText(Path.Combine(dir.FullName, "sq.version"), nuspecText);
packDir = dir.FullName;
var helper = new HelperExe(Log);
var updatePath = Path.Combine(TempDir.FullName, "Update.exe");
File.Copy(helper.UpdatePath, updatePath, true);
// update icon for Update.exe if requested
if (Options.Icon != null && VelopackRuntimeInfo.IsWindows) {
helper.SetExeIcon(updatePath, Options.Icon);
} else if (Options.Icon != null) {
Log.Warn("Unable to set icon for Update.exe (only supported on windows).");
}
File.Copy(updatePath, Path.Combine(packDir, "Squirrel.exe"), true);
return Task.FromResult(packDir);
}
protected override void ProcessNuspecFile(string nuspecFilePath, string packDir)
{
base.ProcessNuspecFile(nuspecFilePath, packDir);
IEnumerable<Runtimes.RuntimeInfo> requiredFrameworks = Enumerable.Empty<Runtimes.RuntimeInfo>();
if (!string.IsNullOrWhiteSpace(Options.Runtimes)) {
requiredFrameworks = Options.Runtimes
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(Runtimes.GetRuntimeByName);
}
if (requiredFrameworks.Where(f => f == null).Any())
throw new ArgumentException("Invalid target frameworks string.");
var mainExeName = Options.EntryExecutableName ?? Options.PackId + ".exe";
var mainExe = Path.Combine(packDir, mainExeName);
if (!File.Exists(mainExe))
throw new ArgumentException($"--exeName '{mainExeName}' does not exist in package. Searched at: '{mainExe}'");
try {
var psi = new ProcessStartInfo(mainExe);
psi.AppendArgumentListSafe(new[] { "--veloapp-version" }, out var _);
var output = psi.Output(3000);
if (String.IsNullOrWhiteSpace(output)) {
throw new Exception("Process exited with no output.");
}
var version = SemanticVersion.Parse(output.Trim());
if (version != VelopackRuntimeInfo.VelopackNugetVersion) {
Log.Warn($"VelopackApp version '{version}' does not match CLI version '{VelopackRuntimeInfo.VelopackNugetVersion}'.");
}
} catch {
Log.Error("Failed to verify VelopackApp. Ensure you have added the startup code to your Program.Main(): VelopackApp.Build().Run();");
throw;
}
NuspecManifest.SetMetadata(nuspecFilePath, mainExeName, requiredFrameworks.Select(r => r.Id), Options.TargetRuntime, null);
}
protected override Task CreateSetupPackage(Action<int> progress, string releasePkg, string targetSetupExe)
{
var helper = new HelperExe(Log);
var bundledzp = new ZipPackage(releasePkg);
File.Copy(helper.SetupPath, targetSetupExe, true);
progress(10);
if (VelopackRuntimeInfo.IsWindows) {
helper.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, Options.Icon);
} else {
Log.Warn("Unable to set Setup.exe icon (only supported on windows)");
}
progress(30);
Log.Info($"Creating Setup bundle");
SetupBundle.CreatePackageBundle(targetSetupExe, releasePkg);
progress(70);
Log.Info("Signing Setup bundle");
var targetDir = Path.GetDirectoryName(targetSetupExe);
SignFilesImpl(Options, targetDir, targetSetupExe);
Log.Debug($"Setup bundle created '{Path.GetFileName(targetSetupExe)}'.");
progress(100);
return Task.CompletedTask;
}
protected override async Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
{
var dir = TempDir.CreateSubdirectory("CreatePortablePackage");
File.Copy(Path.Combine(packDir, "Squirrel.exe"), Path.Combine(dir.FullName, "Update.exe"), true);
var current = dir.CreateSubdirectory("current");
CopyFiles(new DirectoryInfo(packDir), current, Utility.CreateProgressDelegate(progress, 0, 30));
var mainExeName = Options.EntryExecutableName ?? Options.PackId + ".exe";
var mainExe = Path.Combine(packDir, mainExeName);
CreateExecutableStubForExe(mainExe, dir.FullName);
await EasyZip.CreateZipFromDirectoryAsync(Log, outputPath, dir.FullName, Utility.CreateProgressDelegate(progress, 40, 100));
progress(100);
}
protected override Dictionary<string, string> GetReleaseMetadataFiles()
{
var dict = new Dictionary<string, string>();
if (Options.Icon != null) dict["setup.ico"] = Options.Icon;
if (Options.SplashImage != null) dict["splashimage" + Path.GetExtension(Options.SplashImage)] = Options.SplashImage;
return dict;
}
private void CreateExecutableStubForExe(string exeToCopy, string targetDir)
{
try {
var target = Path.Combine(targetDir, Path.GetFileName(exeToCopy));
var helper = new HelperExe(Log);
Utility.Retry(() => File.Copy(helper.StubExecutablePath, target, true));
Utility.Retry(() => {
if (VelopackRuntimeInfo.IsWindows) {
using var writer = new Microsoft.NET.HostModel.ResourceUpdater(target, true);
writer.AddResourcesFromPEImage(exeToCopy);
writer.Update();
} else {
Log.Warn($"Cannot set resources/icon for {target} (only supported on windows).");
}
});
} catch (Exception ex) {
Log.Error(ex, $"Error creating StubExecutable and copying resources for '{exeToCopy}'. This stub may or may not work properly.");
}
}
private void SignFilesImpl(WindowsSigningOptions options, string rootDir, params string[] filePaths)
{
var signParams = options.SignParameters;
var signTemplate = options.SignTemplate;
var signParallel = options.SignParallel;
var helper = new HelperExe(Log);
if (string.IsNullOrEmpty(signParams) && string.IsNullOrEmpty(signTemplate)) {
Log.Warn($"No signing paramaters provided, {filePaths.Length} file(s) will not be signed.");
return;
}
if (!string.IsNullOrEmpty(signTemplate)) {
Log.Info($"Preparing to sign {filePaths.Length} files with custom signing template");
foreach (var f in filePaths) {
helper.SignPEFileWithTemplate(f, signTemplate);
}
return;
}
// signtool.exe does not work if we're not on windows.
if (!VelopackRuntimeInfo.IsWindows) return;
if (!string.IsNullOrEmpty(signParams)) {
Log.Info($"Preparing to sign {filePaths.Length} files with embedded signtool.exe with parallelism of {signParallel}");
helper.SignPEFilesWithSignTool(rootDir, filePaths, signParams, signParallel);
}
}
}

View File

@@ -1,6 +1,6 @@
namespace Velopack.Packaging.Windows.Commands;
public class WindowsPackOptions : WindowsReleasifyOptions, INugetPackCommand
public class WindowsPackOptions : WindowsReleasifyOptions, INugetPackCommand, IPackOptions
{
public string PackId { get; set; }

View File

@@ -1,197 +0,0 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Velopack.NuGet;
using Velopack.Windows;
namespace Velopack.Packaging.Windows.Commands;
public class WindowsReleasifyCommandRunner
{
private readonly ILogger _logger;
public WindowsReleasifyCommandRunner(ILogger logger)
{
_logger = logger;
}
public void Releasify(WindowsReleasifyOptions options)
{
if (options.TargetRuntime?.BaseRID != RuntimeOs.Windows)
throw new ArgumentException("Target runtime must be Windows.", nameof(options.TargetRuntime));
var targetDir = options.ReleaseDir.FullName;
var package = options.Package;
var backgroundGif = options.SplashImage;
var setupIcon = options.Icon;
var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(RuntimeOs.Windows);
// normalize and validate that the provided frameworks are supported
IEnumerable<Runtimes.RuntimeInfo> requiredFrameworks = Enumerable.Empty<Runtimes.RuntimeInfo>();
if (!string.IsNullOrWhiteSpace(options.Runtimes)) {
requiredFrameworks = options.Runtimes
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(Runtimes.GetRuntimeByName);
}
if (requiredFrameworks.Where(f => f == null).Any())
throw new ArgumentException("Invalid target frameworks string.");
using var ud = Utility.GetTempDirectory(out var tempDir);
var helper = new HelperExe(_logger);
var updatePath = Path.Combine(tempDir, "Update.exe");
File.Copy(helper.UpdatePath, updatePath, true);
// update icon for Update.exe if requested
if (setupIcon != null && VelopackRuntimeInfo.IsWindows) {
helper.SetExeIcon(updatePath, setupIcon);
} else if (setupIcon != null) {
_logger.Warn("Unable to set icon for Update.exe (only supported on windows).");
}
// copy input package to target output directory
var fileToProcess = Path.Combine(tempDir, Path.GetFileName(package));
File.Copy(package, fileToProcess, true);
_logger.Info("Creating release for package: " + fileToProcess);
var rp = new ReleasePackageBuilder(_logger, fileToProcess);
var entryHelper = new ReleaseEntryHelper(targetDir, _logger);
entryHelper.ValidateChannelForPackaging(rp.Version, channel, options.TargetRuntime);
rp.CreateReleasePackage(contentsPostProcessHook: (pkgPath, zpkg) => {
var nuspecPath = Directory.GetFiles(pkgPath, "*.nuspec", SearchOption.TopDirectoryOnly)
.ContextualSingle("package", "*.nuspec", "top level directory");
var libDir = Directory.GetDirectories(Path.Combine(pkgPath, "lib"))
.ContextualSingle("package", "'lib' folder");
var mainExeName = options.EntryExecutableName ?? zpkg.Id + ".exe";
var mainExe = Path.Combine(libDir, mainExeName);
if (!File.Exists(mainExe))
throw new ArgumentException($"--exeName '{mainExeName}' does not exist in package. Searched at: '{mainExe}'");
try {
var psi = new ProcessStartInfo(mainExe);
psi.AppendArgumentListSafe(new[] { "--veloapp-version" }, out var _);
var output = psi.Output(3000);
if (String.IsNullOrWhiteSpace(output)) {
throw new Exception("Process exited with no output.");
}
var version = SemanticVersion.Parse(output.Trim());
if (version != VelopackRuntimeInfo.VelopackNugetVersion) {
_logger.Warn($"VelopackApp version '{version}' does not match CLI version '{VelopackRuntimeInfo.VelopackNugetVersion}'.");
}
} catch {
_logger.Error("Failed to verify VelopackApp. Ensure you have added the startup code to your Program.Main(): VelopackApp.Build().Run();");
throw;
}
var spec = NuspecManifest.ParseFromFile(nuspecPath);
// warning if there are long paths (>200 char) in this package. 260 is max path
// but with the %localappdata% + user name + app name this can add up quickly.
// eg. 'C:\Users\SamanthaJones\AppData\Local\Application\app-1.0.1\' is 60 characters.
Directory.EnumerateFiles(libDir, "*", SearchOption.AllDirectories)
.Select(f => f.Substring(libDir.Length).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
.Where(f => f.Length >= 200)
.ForEach(f => _logger.Warn($"File path in package exceeds 200 characters ({f.Length}) and may cause issues on Windows: '{f}'."));
// fail the release if this is a clickonce application
if (Directory.EnumerateFiles(libDir, "*.application").Any(f => File.ReadAllText(f).Contains("clickonce"))) {
throw new ArgumentException(
"Velopack does not support building releases for ClickOnce applications. " +
"Please publish your application to a folder without ClickOnce.");
}
var versionSuffix = ReleaseEntryHelper.GetPkgSuffix(RuntimeOs.Windows, options.Channel);
var versionOverride = String.IsNullOrWhiteSpace(versionSuffix)
? zpkg.Version : SemanticVersion.Parse(zpkg.Version.ToFullString() + versionSuffix);
NuspecManifest.SetMetadata(nuspecPath, mainExeName, requiredFrameworks.Select(r => r.Id), options.TargetRuntime, versionOverride.ToFullString());
// copy Update.exe into package, so it can also be updated in both full/delta packages
// and do it before signing so that Update.exe will also be signed. It is renamed to
// 'Squirrel.exe' only because Squirrel.Windows and Clowd.Squirrel expects it to be called this.
File.Copy(updatePath, Path.Combine(libDir, "Squirrel.exe"), true);
// sign all exe's in this package
var filesToSign = new DirectoryInfo(libDir).GetAllFilesRecursively()
.Where(x => options.SignSkipDll ? Utility.PathPartEndsWith(x.Name, ".exe") : Utility.FileIsLikelyPEImage(x.Name))
.Select(x => x.FullName)
.ToArray();
signFiles(options, libDir, filesToSign);
// copy other images to root (used by setup)
if (setupIcon != null) File.Copy(setupIcon, Path.Combine(pkgPath, "setup.ico"), true);
if (backgroundGif != null) File.Copy(backgroundGif, Path.Combine(pkgPath, "splashimage" + Path.GetExtension(backgroundGif)));
var releaseName = new ReleaseEntryName(spec.Id, versionOverride, false, options.TargetRuntime);
return Path.Combine(targetDir, releaseName.ToFileName());
});
File.Delete(fileToProcess);
entryHelper.AddNewRelease(rp.ReleasePackageFile, channel);
var prev = entryHelper.GetPreviousFullRelease(rp.Version, channel);
if (prev != null && options.DeltaMode != DeltaMode.None) {
_logger.Info($"Creating delta package between {prev.Version} and {rp.Version}");
var deltaBuilder = new DeltaPackageBuilder(_logger);
var deltaOutputPath = rp.ReleasePackageFile.Replace("-full", "-delta");
var dp = deltaBuilder.CreateDeltaPackage(prev, rp, deltaOutputPath, options.DeltaMode);
entryHelper.AddNewRelease(dp.InputPackageFile, channel);
}
_logger.Info("Updating RELEASES files");
entryHelper.SaveReleasesFiles();
var bundledzp = new ZipPackage(package);
var targetSetupExe = entryHelper.GetSuggestedSetupPath(bundledzp.Id, channel, options.TargetRuntime);
File.Copy(helper.SetupPath, targetSetupExe, true);
if (VelopackRuntimeInfo.IsWindows) {
helper.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, setupIcon);
} else {
_logger.Warn("Unable to set Setup.exe icon (only supported on windows)");
}
_logger.Info($"Creating Setup bundle");
var bundleOffset = SetupBundle.CreatePackageBundle(targetSetupExe, rp.ReleasePackageFile);
_logger.Info("Signing Setup bundle");
signFiles(options, targetDir, targetSetupExe);
_logger.Info($"Setup bundle created at '{targetSetupExe}'.");
_logger.Info("Done");
}
private void signFiles(WindowsSigningOptions options, string rootDir, params string[] filePaths)
{
var signParams = options.SignParameters;
var signTemplate = options.SignTemplate;
var signParallel = options.SignParallel;
var helper = new HelperExe(_logger);
if (string.IsNullOrEmpty(signParams) && string.IsNullOrEmpty(signTemplate)) {
_logger.Debug($"No signing paramaters provided, {filePaths.Length} file(s) will not be signed.");
return;
}
if (!string.IsNullOrEmpty(signTemplate)) {
_logger.Info($"Preparing to sign {filePaths.Length} files with custom signing template");
foreach (var f in filePaths) {
helper.SignPEFileWithTemplate(f, signTemplate);
}
return;
}
// signtool.exe does not work if we're not on windows.
if (!VelopackRuntimeInfo.IsWindows) return;
if (!string.IsNullOrEmpty(signParams)) {
_logger.Info($"Preparing to sign {filePaths.Length} files with embedded signtool.exe with parallelism of {signParallel}");
helper.SignPEFilesWithSignTool(rootDir, filePaths, signParams, signParallel);
}
}
}

View File

@@ -14,7 +14,7 @@ public class HelperExe : HelperFile
public string SetupPath => FindHelperFile("Setup.exe");
public string StubExecutablePath => FindHelperFile("StubExecutable.exe");
public string StubExecutablePath => FindHelperFile("stub.exe");
private string SignToolPath => FindHelperFile("signtool.exe");
@@ -115,7 +115,7 @@ public class HelperExe : HelperFile
[SupportedOSPlatform("windows")]
public void SetExeIcon(string exePath, string iconPath)
{
Log.Info("Updating PE icon for: " + exePath);
Log.Debug("Updating PE icon for: " + exePath);
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
Utility.Retry(() => InvokeAndThrowIfNonZero(RceditPath, args, null));
}
@@ -123,7 +123,7 @@ public class HelperExe : HelperFile
[SupportedOSPlatform("windows")]
public void SetPEVersionBlockFromPackageInfo(string exePath, NuGet.IPackage package, string iconPath = null)
{
Log.Info("Updating StringTable resources for: " + exePath);
Log.Debug("Updating StringTable resources for: " + exePath);
var realExePath = Path.GetFullPath(exePath);
List<string> args = new List<string>() {

View File

@@ -0,0 +1,42 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Velopack.Packaging;
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
public class Chmod
{
private const string OSX_CSTD_LIB = "libSystem.dylib";
private const string NIX_CSTD_LIB = "libc";
[SupportedOSPlatform("osx")]
[DllImport(OSX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int osx_chmod(string pathname, int mode);
[SupportedOSPlatform("linux")]
[DllImport(NIX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int nix_chmod(string pathname, int mode);
public static void ChmodFileAsExecutable(string filePath)
{
Func<string, int, int> chmod;
if (VelopackRuntimeInfo.IsOSX) chmod = osx_chmod;
else if (VelopackRuntimeInfo.IsLinux) chmod = nix_chmod;
else return; // no-op on windows, all .exe files can be executed.
var filePermissionOctal = Convert.ToInt32("777", 8);
const int EINTR = 4;
int chmodReturnCode;
do {
chmodReturnCode = chmod(filePath, filePermissionOctal);
} while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
if (chmodReturnCode == -1) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {filePath}.");
}
}
}

View File

@@ -4,12 +4,19 @@ namespace Velopack.Packaging.Commands
{
public class DeltaGenCommandRunner : ICommand<DeltaGenOptions>
{
public Task Run(DeltaGenOptions options, ILogger logger)
private readonly ILogger _logger;
public DeltaGenCommandRunner(ILogger logger)
{
var pold = new ReleasePackageBuilder(logger, options.BasePackage);
var pnew = new ReleasePackageBuilder(logger, options.NewPackage);
var delta = new DeltaPackageBuilder(logger);
delta.CreateDeltaPackage(pnew, pold, options.OutputFile, options.DeltaMode);
_logger = logger;
}
public Task Run(DeltaGenOptions options)
{
var pold = new ReleasePackage(options.BasePackage);
var pnew = new ReleasePackage(options.NewPackage);
var delta = new DeltaPackageBuilder(_logger);
delta.CreateDeltaPackage(pnew, pold, options.OutputFile, options.DeltaMode, (x) => { });
return Task.CompletedTask;
}
}

View File

@@ -5,7 +5,14 @@ namespace Velopack.Packaging.Commands
{
public class DeltaPatchCommandRunner : ICommand<DeltaPatchOptions>
{
public Task Run(DeltaPatchOptions options, ILogger logger)
private readonly ILogger _logger;
public DeltaPatchCommandRunner(ILogger logger)
{
_logger = logger;
}
public Task Run(DeltaPatchOptions options)
{
if (options.PatchFiles.Length == 0) {
throw new ArgumentException("Must specify at least one patch file.");
@@ -17,7 +24,7 @@ namespace Velopack.Packaging.Commands
var tmp = Utility.GetDefaultTempBaseDirectory();
using var _1 = Utility.GetTempDirectory(out var workDir);
var helper = new HelperFile(logger);
var helper = new HelperFile(_logger);
string updateExe;
if (VelopackRuntimeInfo.IsWindows)
@@ -27,15 +34,15 @@ namespace Velopack.Packaging.Commands
else
throw new NotSupportedException("This platform is not supported.");
var delta = new DeltaPackage(logger, tmp, updateExe);
EasyZip.ExtractZipToDirectory(logger, options.BasePackage, workDir);
var delta = new DeltaPackage(_logger, tmp, updateExe);
EasyZip.ExtractZipToDirectory(_logger, options.BasePackage, workDir);
foreach (var f in options.PatchFiles) {
logger.Info($"Applying delta patch {f.Name}");
_logger.Info($"Applying delta patch {f.Name}");
delta.ApplyDeltaPackageFast(workDir, f.FullName);
}
EasyZip.CreateZipFromDirectory(logger, options.OutputFile, workDir);
EasyZip.CreateZipFromDirectory(_logger, options.OutputFile, workDir);
return Task.CompletedTask;
}

View File

@@ -1,4 +1,6 @@
using System.IO.MemoryMappedFiles;
using System.Runtime.ConstrainedExecution;
using System.Runtime.Intrinsics.Arm;
using System.Text;
using Microsoft.Extensions.Logging;
using Velopack.Compression;
@@ -14,7 +16,17 @@ public class DeltaPackageBuilder
_logger = logger;
}
public ReleasePackageBuilder CreateDeltaPackage(ReleasePackageBuilder basePackage, ReleasePackageBuilder newPackage, string outputFile, DeltaMode mode)
public class DeltaStats
{
public int New { get; set; }
public int Same { get; set; }
public int Changed { get; set; }
public int Warnings { get; set; }
public int Processed { get; set; }
public int Removed { get; set; }
}
public (ReleasePackage package, DeltaStats stats) CreateDeltaPackage(ReleasePackage basePackage, ReleasePackage newPackage, string outputFile, DeltaMode mode, Action<int> progress)
{
if (basePackage == null) throw new ArgumentNullException(nameof(basePackage));
if (newPackage == null) throw new ArgumentNullException(nameof(newPackage));
@@ -40,18 +52,20 @@ public class DeltaPackageBuilder
throw new InvalidOperationException(message);
}
if (basePackage.ReleasePackageFile == null) {
if (basePackage.PackageFile == null) {
throw new ArgumentException("The base package's release file is null", "basePackage");
}
if (!File.Exists(basePackage.ReleasePackageFile)) {
throw new FileNotFoundException("The base package release does not exist", basePackage.ReleasePackageFile);
if (!File.Exists(basePackage.PackageFile)) {
throw new FileNotFoundException("The base package release does not exist", basePackage.PackageFile);
}
if (!File.Exists(newPackage.ReleasePackageFile)) {
throw new FileNotFoundException("The new package release does not exist", newPackage.ReleasePackageFile);
if (!File.Exists(newPackage.PackageFile)) {
throw new FileNotFoundException("The new package release does not exist", newPackage.PackageFile);
}
int fNew = 0, fSame = 0, fChanged = 0, fWarnings = 0, fProcessed = 0, fRemoved = 0;
using (Utility.GetTempDirectory(out var baseTempPath))
using (Utility.GetTempDirectory(out var tempPath)) {
var baseTempInfo = new DirectoryInfo(baseTempPath);
@@ -61,10 +75,10 @@ public class DeltaPackageBuilder
int numParallel = Math.Min(Math.Max(Environment.ProcessorCount - 1, 1), 8);
_logger.Info($"Creating delta for {basePackage.Version} -> {newPackage.Version} with {numParallel} parallel threads.");
_logger.Debug($"Extracting {Path.GetFileName(basePackage.ReleasePackageFile)} and {Path.GetFileName(newPackage.ReleasePackageFile)} into {tempPath}");
_logger.Debug($"Extracting {Path.GetFileName(basePackage.PackageFile)} and {Path.GetFileName(newPackage.PackageFile)} into {tempPath}");
EasyZip.ExtractZipToDirectory(_logger, basePackage.ReleasePackageFile, baseTempInfo.FullName);
EasyZip.ExtractZipToDirectory(_logger, newPackage.ReleasePackageFile, tempInfo.FullName);
EasyZip.ExtractZipToDirectory(_logger, basePackage.PackageFile, baseTempInfo.FullName);
EasyZip.ExtractZipToDirectory(_logger, newPackage.PackageFile, tempInfo.FullName);
// Collect a list of relative paths under 'lib' and map them
// to their full name. We'll use this later to determine in
@@ -73,12 +87,9 @@ public class DeltaPackageBuilder
var baseLibFiles = baseTempInfo.GetAllFilesRecursively()
.Where(x => x.FullName.ToLowerInvariant().Contains("lib" + Path.DirectorySeparatorChar))
.ToDictionary(k => k.FullName.Replace(baseTempInfo.FullName, ""), v => v.FullName);
var newLibDir = tempInfo.GetDirectories().First(x => x.Name.ToLowerInvariant() == "lib");
var newLibFiles = newLibDir.GetAllFilesRecursively().ToArray();
int fNew = 0, fSame = 0, fChanged = 0, fWarnings = 0;
void createDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory)
{
// NB: There are three cases here that we'll handle:
@@ -106,7 +117,7 @@ public class DeltaPackageBuilder
if (AreFilesEqualFast(oldFilePath, targetFile.FullName)) {
// 2. exists in both, keep it the same
_logger.Debug($"{relativePath} hasn't changed, writing dummy file");
File.Create(targetFile.FullName + ".zsdiff").Dispose();
File.Create(targetFile.FullName + ".diff").Dispose();
File.Create(targetFile.FullName + ".shasum").Dispose();
fSame++;
} else {
@@ -129,6 +140,8 @@ public class DeltaPackageBuilder
}
targetFile.Delete();
baseLibFiles.Remove(relativePath);
fProcessed++;
progress(Utility.CalculateProgress((int) ((double) fProcessed / newLibFiles.Length), 0, 70));
} catch (Exception ex) {
_logger.Debug(ex, String.Format("Failed to create a delta for {0}", targetFile.Name));
Utility.DeleteFileOrDirectoryHard(targetFile.FullName + ".bsdiff", throwOnFailure: false);
@@ -139,53 +152,26 @@ public class DeltaPackageBuilder
}
}
void printProcessed(int cur, int? removed = null)
{
string rem = removed.HasValue ? removed.Value.ToString("D4") : "????";
_logger.Info($"Processed {cur.ToString("D4")}/{newLibFiles.Length.ToString("D4")} files. " +
$"{fChanged.ToString("D4")} patched, {fSame.ToString("D4")} unchanged, {fNew.ToString("D4")} new, {rem} removed");
}
printProcessed(0);
var tResult = Task.Run(() => {
Parallel.ForEach(newLibFiles, new ParallelOptions() { MaxDegreeOfParallelism = numParallel }, (f) => {
Utility.Retry(() => createDeltaForSingleFile(f, tempInfo));
});
Parallel.ForEach(newLibFiles, new ParallelOptions() { MaxDegreeOfParallelism = numParallel }, (f) => {
Utility.Retry(() => createDeltaForSingleFile(f, tempInfo));
});
int prevCount = 0;
while (!tResult.IsCompleted) {
// sleep for 2 seconds (in 100ms intervals)
for (int i = 0; i < 20 && !tResult.IsCompleted; i++)
Thread.Sleep(100);
EasyZip.CreateZipFromDirectory(_logger, outputFile, tempInfo.FullName, Utility.CreateProgressDelegate(progress, 70, 100));
progress(100);
fRemoved = baseLibFiles.Count;
int processed = fNew + fChanged + fSame;
if (prevCount == processed) {
// if there has been no progress, do not print another message
continue;
}
_logger.Info($"Delta processed {fProcessed.ToString("D4")} files. "
+ $"{fChanged.ToString("D4")} patched, {fSame.ToString("D4")} unchanged, {fNew.ToString("D4")} new, {fRemoved.ToString("D4")} removed");
if (processed < newLibFiles.Length)
printProcessed(processed);
prevCount = processed;
}
if (tResult.Exception != null)
throw new Exception("Unable to create delta package.", tResult.Exception);
printProcessed(newLibFiles.Length, baseLibFiles.Count);
ReleasePackageBuilder.addDeltaFilesToContentTypes(tempInfo.FullName);
EasyZip.CreateZipFromDirectory(_logger, outputFile, tempInfo.FullName);
_logger.Info(
_logger.Debug(
$"Successfully created delta package for {basePackage.Version} -> {newPackage.Version}" +
(fWarnings > 0 ? $" (with {fWarnings} retries)" : "") +
".");
}
return new ReleasePackageBuilder(_logger, outputFile);
return (new ReleasePackage(outputFile), new DeltaStats {
New = fNew, Same = fSame, Changed = fChanged, Warnings = fWarnings, Processed = fProcessed, Removed = fRemoved,
});
}
public unsafe static bool AreFilesEqualFast(string filePath1, string filePath2)

View File

@@ -4,6 +4,6 @@ namespace Velopack.Packaging
{
internal interface ICommand<TOpt> where TOpt : class
{
Task Run(TOpt options, ILogger logger);
Task Run(TOpt options);
}
}

View File

@@ -0,0 +1,12 @@
namespace Velopack.Packaging;
public interface INugetPackCommand
{
string PackId { get; }
string PackVersion { get; }
string PackDirectory { get; }
string PackAuthors { get; }
string PackTitle { get; }
bool IncludePdb { get; }
string ReleaseNotes { get; }
}

View File

@@ -4,17 +4,6 @@ using NuGet.Commands;
namespace Velopack.Packaging;
public interface INugetPackCommand
{
string PackId { get; }
string PackVersion { get; }
string PackDirectory { get; }
string PackAuthors { get; }
string PackTitle { get; }
bool IncludePdb { get; }
string ReleaseNotes { get; }
}
public class NugetConsole
{
private readonly ILogger Log;
@@ -84,7 +73,7 @@ public class NugetConsole
public void Pack(string nuspecPath, string baseDirectory, string outputDirectory)
{
Log.Info($"Starting to package '{nuspecPath}'");
Log.Debug($"Starting to package '{nuspecPath}'");
var args = new PackArgs() {
Deterministic = true,
BasePath = baseDirectory,

View File

@@ -0,0 +1,315 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Spectre.Console;
using Velopack.Compression;
using Velopack.NuGet;
namespace Velopack.Packaging
{
public interface IPackOptions : INugetPackCommand
{
RID TargetRuntime { get; }
DirectoryInfo ReleaseDir { get; }
string Channel { get; }
DeltaMode DeltaMode { get; }
}
public abstract class PackageBuilder<T> : ICommand<T>
where T : class, IPackOptions
{
protected RuntimeOs SupportedTargetOs { get; }
protected ILogger Log { get; }
protected DirectoryInfo TempDir { get; private set; }
protected T Options { get; private set; }
public PackageBuilder(RuntimeOs supportedOs, ILogger logger)
{
SupportedTargetOs = supportedOs;
Log = logger;
}
public async Task Run(T options)
{
if (options.TargetRuntime?.BaseRID != SupportedTargetOs)
throw new ArgumentException($"Target runtime must be {SupportedTargetOs}.", nameof(options.TargetRuntime));
Log.Info("Beginning to package release.");
Log.Info("Releases Directory: " + options.ReleaseDir.FullName);
var releaseDir = options.ReleaseDir;
var channel = options.Channel?.ToLower() ?? ReleaseEntryHelper.GetDefaultChannel(SupportedTargetOs);
var entryHelper = new ReleaseEntryHelper(releaseDir.FullName, Log);
entryHelper.ValidateChannelForPackaging(SemanticVersion.Parse(options.PackVersion), channel, options.TargetRuntime);
var packId = options.PackId;
var packTitle = options.PackTitle ?? options.PackId;
var packAuthors = options.PackAuthors ?? options.PackId;
var packDirectory = options.PackDirectory;
var packVersion = options.PackVersion;
var suffix = ReleaseEntryHelper.GetPkgSuffix(SupportedTargetOs, channel);
if (!String.IsNullOrWhiteSpace(suffix)) {
packVersion += suffix;
}
var prev = entryHelper.GetPreviousFullRelease(NuGetVersion.Parse(packVersion), channel);
var nuspecText = NugetConsole.CreateNuspec(
packId, packTitle, packAuthors, packVersion, options.ReleaseNotes, options.IncludePdb);
using var _1 = Utility.GetTempDirectory(out var pkgTempDir);
TempDir = new DirectoryInfo(pkgTempDir);
Options = options;
List<(string from, string to)> filesToCopy = new();
try {
await AnsiConsole.Progress()
.AutoRefresh(true)
.AutoClear(false)
.HideCompleted(false)
.Columns(new ProgressColumn[]
{
new SpinnerColumn(),
new TaskDescriptionColumn(),
new ProgressBarColumn(),
new PercentageColumn(),
new ElapsedTimeColumn(),
})
.StartAsync(async ctx => {
var taskPreProcess = ctx.AddTask($"[italic]Pre-process steps[/]");
taskPreProcess.StartTask();
packDirectory = await PreprocessPackDir((p) => taskPreProcess.Value = p, packDirectory, nuspecText);
taskPreProcess.StopTask();
Log.Info("[bold]Complete: Pre-process steps[/]");
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
var taskSigning = ctx.AddTask($"[italic]Code-sign application[/]");
taskSigning.StartTask();
await CodeSign((p) => taskSigning.Value = p, packDirectory);
taskSigning.StopTask();
Log.Info("[bold]Complete: Code-sign application[/]");
}
var portableTask = Task.Run(async () => {
var taskPortable = ctx.AddTask($"[italic]Building portable package[/]");
taskPortable.StartTask();
var suggestedPortable = entryHelper.GetSuggestedPortablePath(packId, channel, options.TargetRuntime);
var incomplete = suggestedPortable + ".incomplete";
if (File.Exists(incomplete)) File.Delete(incomplete);
filesToCopy.Add((incomplete, suggestedPortable));
await CreatePortablePackage((p) => taskPortable.Value = p, packDirectory, incomplete);
taskPortable.StopTask();
Log.Info("[bold]Complete: Build portable package[/]");
});
var taskNuget = ctx.AddTask($"[italic]Building release {packVersion}[/]");
taskNuget.StartTask();
var releaseName = new ReleaseEntryName(packId, SemanticVersion.Parse(packVersion), false, Options.TargetRuntime);
var releasePath = Path.Combine(releaseDir.FullName, releaseName.ToFileName());
if (File.Exists(releasePath)) File.Delete(releasePath);
await CreateReleasePackage((p) => taskNuget.Value = p, packDirectory, nuspecText, releasePath);
entryHelper.AddNewRelease(releasePath, channel);
taskNuget.StopTask();
Log.Info("[bold]Complete: Build release package[/]");
var setupTask = Task.Run(async () => {
var taskSetup = ctx.AddTask($"[italic]Create setup package[/]");
taskSetup.StartTask();
var suggestedSetup = entryHelper.GetSuggestedSetupPath(packId, channel, options.TargetRuntime);
var incomplete = suggestedSetup + ".incomplete";
if (File.Exists(incomplete)) File.Delete(incomplete);
filesToCopy.Add((incomplete, suggestedSetup));
await CreateSetupPackage((p) => taskSetup.Value = p, releasePath, incomplete);
taskSetup.StopTask();
Log.Info("[bold]Complete: Create setup package[/]");
});
if (prev != null && options.DeltaMode != DeltaMode.None) {
var taskDelta = ctx.AddTask($"[italic]Building delta {prev.Version} -> {packVersion}[/]");
taskDelta.StartTask();
var deltaPkg = await CreateDeltaPackage((p) => taskDelta.Value = p, releasePath, prev.PackageFile, options.DeltaMode);
taskDelta.StopTask();
entryHelper.AddNewRelease(deltaPkg, channel);
Log.Info("[bold]Complete: Building delta package[/]");
}
await portableTask;
await setupTask;
var taskFinish = ctx.AddTask($"[italic]Finishing up[/]");
taskFinish.IsIndeterminate = true;
taskFinish.StartTask();
entryHelper.SaveReleasesFiles();
foreach (var f in filesToCopy) {
File.Move(f.from, f.to, true);
}
taskFinish.Value = 100;
taskFinish.StopTask();
});
Log.Info("[bold]Done.[/]");
} catch {
try {
foreach (var f in filesToCopy) {
File.Delete(f.from);
}
entryHelper.RollbackNewReleases();
} catch (Exception ex) {
Log.Error("Failed to remove incomplete releases: " + ex.Message);
}
throw;
}
}
protected virtual Task<string> PreprocessPackDir(Action<int> progress, string packDir, string nuspecText)
{
var dir = TempDir.CreateSubdirectory("PreprocessPackDir");
CopyFiles(new DirectoryInfo(packDir), dir, progress);
File.WriteAllText(Path.Combine(dir.FullName, "sq.version"), nuspecText);
return Task.FromResult(packDir);
}
protected virtual Task CodeSign(Action<int> progress, string packDir)
{
throw new NotImplementedException();
}
protected virtual Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
{
return Task.CompletedTask;
}
protected virtual Task<string> CreateDeltaPackage(Action<int> progress, string releasePkg, string prevReleasePkg, DeltaMode mode)
{
var deltaBuilder = new DeltaPackageBuilder(Log);
var deltaOutputPath = releasePkg.Replace("-full", "-delta");
var (dp, stats) = deltaBuilder.CreateDeltaPackage(new ReleasePackage(prevReleasePkg), new ReleasePackage(releasePkg), deltaOutputPath, mode, progress);
return Task.FromResult(dp.PackageFile);
}
protected virtual Task CreateSetupPackage(Action<int> progress, string releasePkg, string outputPath)
{
return Task.CompletedTask;
}
protected virtual async Task CreateReleasePackage(Action<int> progress, string packDir, string nuspecText, string outputPath)
{
var stagingDir = TempDir.CreateSubdirectory("CreateReleasePackage");
var nuspecPath = Path.Combine(stagingDir.FullName, Options.PackId + ".nuspec");
File.WriteAllText(nuspecPath, nuspecText);
ProcessNuspecFile(nuspecPath, packDir);
progress(10);
var appDir = stagingDir.CreateSubdirectory("lib").CreateSubdirectory("app");
CopyFiles(new DirectoryInfo(packDir), appDir, Utility.CreateProgressDelegate(progress, 10, 30));
var metadataFiles = GetReleaseMetadataFiles();
foreach (var kvp in metadataFiles) {
File.Copy(kvp.Value, Path.Combine(stagingDir.FullName, kvp.Key), true);
}
await EasyZip.CreateZipFromDirectoryAsync(Log, outputPath, stagingDir.FullName, Utility.CreateProgressDelegate(progress, 30, 100));
progress(100);
}
protected virtual Dictionary<string, string> GetReleaseMetadataFiles()
{
return new Dictionary<string, string>();
}
protected virtual void ProcessNuspecFile(string nuspecFilePath, string packDir)
{
//RemoveDependenciesFromPackageSpec(nuspecFilePath);
//AddDeltaFilesToContentTypes(nuspecFilePath);
RenderReleaseNotesMarkdown(nuspecFilePath);
}
protected virtual void CopyFiles(DirectoryInfo source, DirectoryInfo target, Action<int> progress)
{
var numFiles = source.EnumerateFiles("*", SearchOption.AllDirectories).Count();
int currentFile = 0;
void CopyFilesInternal(DirectoryInfo source, DirectoryInfo target)
{
foreach (var fileInfo in source.GetFiles()) {
var path = Path.Combine(target.FullName, fileInfo.Name);
fileInfo.CopyTo(path, true);
currentFile++;
progress((int) ((double) currentFile / numFiles * 100));
}
foreach (var sourceSubDir in source.GetDirectories()) {
var targetSubDir = target.CreateSubdirectory(sourceSubDir.Name);
CopyFilesInternal(sourceSubDir, targetSubDir);
}
}
CopyFilesInternal(source, target);
}
protected virtual void RenderReleaseNotesMarkdown(string specPath)
{
var doc = new XmlDocument();
doc.Load(specPath);
var metadata = doc.DocumentElement.ChildNodes
.OfType<XmlElement>()
.First(x => x.Name.ToLowerInvariant() == "metadata");
var releaseNotes = metadata.ChildNodes
.OfType<XmlElement>()
.FirstOrDefault(x => x.Name.ToLowerInvariant() == "releasenotes");
if (releaseNotes == null || String.IsNullOrWhiteSpace(releaseNotes.InnerText)) {
Log.Debug($"No release notes found in {specPath}");
return;
}
var releaseNotesHtml = doc.CreateElement("releaseNotesHtml");
releaseNotesHtml.InnerText = String.Format("<![CDATA[\n" + "{0}\n" + "]]>",
new Markdown().Transform(releaseNotes.InnerText));
metadata.AppendChild(releaseNotesHtml);
doc.Save(specPath);
}
protected virtual void RemoveDependenciesFromPackageSpec(string specPath)
{
var xdoc = new XmlDocument();
xdoc.Load(specPath);
var metadata = xdoc.DocumentElement.FirstChild;
var dependenciesNode = metadata.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.Name.ToLowerInvariant() == "dependencies");
if (dependenciesNode != null) {
metadata.RemoveChild(dependenciesNode);
}
xdoc.Save(specPath);
}
protected virtual void AddDeltaFilesToContentTypes(string rootDirectory)
{
var doc = new XmlDocument();
var path = Path.Combine(rootDirectory, ContentType.ContentTypeFileName);
doc.Load(path);
ContentType.Merge(doc);
ContentType.Clean(doc);
using (var sw = new StreamWriter(path, false, Encoding.UTF8)) {
doc.Save(sw);
}
}
}
}

View File

@@ -9,6 +9,7 @@ namespace Velopack.Packaging
private readonly string _outputDir;
private readonly ILogger _logger;
private Dictionary<string, List<ReleaseEntry>> _releases;
private List<string> _new;
private const string BLANK_CHANNEL = "default";
@@ -17,11 +18,12 @@ namespace Velopack.Packaging
_outputDir = outputDir;
_logger = logger;
_releases = new Dictionary<string, List<ReleaseEntry>>(StringComparer.OrdinalIgnoreCase);
_new = new List<string>();
foreach (var releaseFile in Directory.EnumerateFiles(outputDir, "RELEASES*")) {
var fn = Path.GetFileName(releaseFile);
var channel = fn.StartsWith("RELEASES-", StringComparison.OrdinalIgnoreCase) ? fn.Substring(9) : BLANK_CHANNEL;
var releases = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFile)).ToList();
_logger.Info($"Loaded {releases.Count} entries from: {releaseFile}");
_logger.Debug($"Loaded {releases.Count} entries from: {releaseFile}");
// this allows us to collapse RELEASES files with the same channel but different case on file systems
// which are case sensitive.
if (_releases.ContainsKey(channel)) {
@@ -48,7 +50,7 @@ namespace Velopack.Packaging
}
}
public ReleasePackageBuilder GetPreviousFullRelease(SemanticVersion version, string channel)
public ReleasePackage GetPreviousFullRelease(SemanticVersion version, string channel)
{
channel ??= GetDefaultChannel(VelopackRuntimeInfo.SystemOs);
var releases = _releases.ContainsKey(channel) ? _releases[channel] : null;
@@ -60,7 +62,7 @@ namespace Velopack.Packaging
.FirstOrDefault();
if (entry == null) return null;
var file = Path.Combine(_outputDir, entry.OriginalFilename);
return new ReleasePackageBuilder(_logger, file, true);
return new ReleasePackage(file);
}
public ReleaseEntry GetLatestFullRelease(string channel)
@@ -98,9 +100,17 @@ namespace Velopack.Packaging
_releases[channel].Remove(collision);
}
_new.Add(nupkgPath);
_releases[channel].Add(newReleaseEntry);
}
public void RollbackNewReleases()
{
foreach (var n in _new) {
Utility.Retry(() => File.Delete(n));
}
}
public void SaveReleasesFiles()
{
foreach (var releaseFile in Directory.EnumerateFiles(_outputDir, "RELEASES*")) {
@@ -111,7 +121,7 @@ namespace Velopack.Packaging
var path = GetReleasePath(ch.Key);
using var fs = File.Create(path);
ReleaseEntry.WriteReleaseFile(ch.Value, fs);
_logger.Info("Wrote RELEASES file: " + path);
_logger.Debug("Wrote RELEASES file: " + path);
}
}

View File

@@ -0,0 +1,26 @@
using System.IO.Compression;
using System.Text;
using System.Xml;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Velopack.Compression;
using Velopack.NuGet;
namespace Velopack.Packaging;
public class ReleasePackage
{
private Lazy<ZipPackage> _package;
public ReleasePackage(string inputPackageFile)
{
PackageFile = inputPackageFile;
_package = new Lazy<ZipPackage>(() => new ZipPackage(inputPackageFile));
}
public string PackageFile { get; protected set; }
public SemanticVersion Version => _package.Value.Version;
public bool IsDelta => ReleaseEntryName.FromEntryFileName(Path.GetFileName(PackageFile)).IsDelta;
}

View File

@@ -1,183 +0,0 @@
using System.IO.Compression;
using System.Text;
using System.Xml;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Velopack.Compression;
using Velopack.NuGet;
namespace Velopack.Packaging;
public class ReleasePackageBuilder
{
private Lazy<ZipPackage> _package;
private readonly ILogger _logger;
public ReleasePackageBuilder(ILogger logger, string inputPackageFile, bool isReleasePackage = false)
{
_logger = logger;
InputPackageFile = inputPackageFile;
_package = new Lazy<ZipPackage>(() => new ZipPackage(inputPackageFile));
if (isReleasePackage) {
ReleasePackageFile = inputPackageFile;
}
}
public string InputPackageFile { get; protected set; }
public string ReleasePackageFile { get; protected set; }
public SemanticVersion Version => _package.Value.Version;
public string CreateReleasePackage(string outputFile, Func<string, string> releaseNotesProcessor = null, Action<string, ZipPackage> contentsPostProcessHook = null)
{
return CreateReleasePackage((i, p) => {
contentsPostProcessHook?.Invoke(i, p);
return outputFile;
}, releaseNotesProcessor);
}
public string CreateReleasePackage(Func<string, ZipPackage, string> contentsPostProcessHook, Func<string, string> releaseNotesProcessor = null)
{
releaseNotesProcessor = releaseNotesProcessor ?? (x => (new Markdown()).Transform(x));
if (ReleasePackageFile != null) {
return ReleasePackageFile;
}
var package = _package.Value;
// just in-case our parsing is more-strict than nuget.exe and
// the 'releasify' command was used instead of 'pack'.
NugetUtil.ThrowIfInvalidNugetId(package.Id);
// we can tell from here what platform(s) the package targets but given this is a
// simple package we only ever expect one entry here (crash hard otherwise)
var frameworks = package.Frameworks;
if (frameworks.Count() > 1) {
var platforms = frameworks
.Aggregate(new StringBuilder(), (sb, f) => sb.Append(f.ToString() + "; "));
throw new InvalidOperationException(String.Format(
"The input package file {0} targets multiple platforms - {1} - and cannot be transformed into a release package.", InputPackageFile, platforms));
} else if (!frameworks.Any()) {
throw new InvalidOperationException(String.Format(
"The input package file {0} targets no platform and cannot be transformed into a release package.", InputPackageFile));
}
// CS - docs say we don't support dependencies. I can't think of any reason allowing this is useful.
if (package.DependencySets.Any()) {
throw new InvalidOperationException(String.Format(
"The input package file {0} must have no dependencies.", InputPackageFile));
}
_logger.Info($"Creating release from input package {InputPackageFile}");
using (Utility.GetTempDirectory(out var tempPath)) {
var tempDir = new DirectoryInfo(tempPath);
extractZipWithEscaping(InputPackageFile, tempPath).Wait();
var specPath = tempDir.GetFiles("*.nuspec").First().FullName;
_logger.Info("Removing unnecessary data");
removeDependenciesFromPackageSpec(specPath);
if (releaseNotesProcessor != null) {
renderReleaseNotesMarkdown(specPath, releaseNotesProcessor);
}
addDeltaFilesToContentTypes(tempDir.FullName);
var outputFile = contentsPostProcessHook.Invoke(tempPath, package);
EasyZip.CreateZipFromDirectory(_logger, outputFile, tempPath);
ReleasePackageFile = outputFile;
_logger.Info($"Package created at {outputFile}");
return ReleasePackageFile;
}
}
static Task extractZipWithEscaping(string zipFilePath, string outFolder)
{
return Task.Run(() => {
using (var fs = File.OpenRead(zipFilePath))
using (var za = new ZipArchive(fs))
foreach (var entry in za.Entries) {
var parts = entry.FullName.Split('\\', '/').Select(x => Uri.UnescapeDataString(x));
var decoded = String.Join(Path.DirectorySeparatorChar.ToString(), parts);
var fullTargetFile = Path.Combine(outFolder, decoded);
var fullTargetDir = Path.GetDirectoryName(fullTargetFile);
Directory.CreateDirectory(fullTargetDir);
var isDirectory = entry.IsDirectory();
Utility.Retry(() => {
if (isDirectory) {
Directory.CreateDirectory(fullTargetFile);
} else {
entry.ExtractToFile(fullTargetFile, true);
}
}, 5);
}
});
}
void renderReleaseNotesMarkdown(string specPath, Func<string, string> releaseNotesProcessor)
{
var doc = new XmlDocument();
doc.Load(specPath);
var metadata = doc.DocumentElement.ChildNodes
.OfType<XmlElement>()
.First(x => x.Name.ToLowerInvariant() == "metadata");
var releaseNotes = metadata.ChildNodes
.OfType<XmlElement>()
.FirstOrDefault(x => x.Name.ToLowerInvariant() == "releasenotes");
if (releaseNotes == null || String.IsNullOrWhiteSpace(releaseNotes.InnerText)) {
_logger.Info($"No release notes found in {specPath}");
return;
}
var releaseNotesHtml = doc.CreateElement("releaseNotesHtml");
releaseNotesHtml.InnerText = String.Format("<![CDATA[\n" + "{0}\n" + "]]>",
releaseNotesProcessor(releaseNotes.InnerText));
metadata.AppendChild(releaseNotesHtml);
doc.Save(specPath);
}
void removeDependenciesFromPackageSpec(string specPath)
{
var xdoc = new XmlDocument();
xdoc.Load(specPath);
var metadata = xdoc.DocumentElement.FirstChild;
var dependenciesNode = metadata.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.Name.ToLowerInvariant() == "dependencies");
if (dependenciesNode != null) {
metadata.RemoveChild(dependenciesNode);
}
xdoc.Save(specPath);
}
static internal void addDeltaFilesToContentTypes(string rootDirectory)
{
var doc = new XmlDocument();
var path = Path.Combine(rootDirectory, ContentType.ContentTypeFileName);
doc.Load(path);
ContentType.Merge(doc);
ContentType.Clean(doc);
using (var sw = new StreamWriter(path, false, Encoding.UTF8)) {
doc.Save(sw);
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Velopack.Packaging
{
/// <summary>
/// Denotes that an error has occurred for which a stack trace should not be printed.
/// </summary>
public class UserErrorException : Exception
{
public UserErrorException()
{
}
public UserErrorException(string message) : base(message)
{
}
public UserErrorException(string message, Exception innerException) : base(message, innerException)
{
}
protected UserErrorException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -14,6 +14,7 @@
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="NuGet.Commands" Version="6.8.0" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
</ItemGroup>
</Project>

View File

@@ -90,30 +90,29 @@ public class EmbeddedRunner : ICommandRunner
SignTemplate = command.SignTemplate,
SplashImage = command.SplashImage,
};
new WindowsPackCommandRunner(_logger).Pack(options);
return Task.CompletedTask;
return new WindowsPackCommandRunner(_logger).Run(options);
}
public virtual Task ExecuteReleasifyWindows(WindowsReleasifyCommand command)
{
var options = new WindowsReleasifyOptions {
TargetRuntime = command.GetRid(),
ReleaseDir = command.GetReleaseDirectory(),
Package = command.Package,
Icon = command.Icon,
DeltaMode = command.Delta,
SignParameters = command.SignParameters,
EntryExecutableName = command.EntryExecutableName,
Runtimes = command.Runtimes,
Channel = command.Channel,
SignParallel = command.SignParallel,
SignSkipDll = command.SignSkipDll,
SignTemplate = command.SignTemplate,
SplashImage = command.SplashImage,
};
new WindowsReleasifyCommandRunner(_logger).Releasify(options);
return Task.CompletedTask;
}
//public virtual Task ExecuteReleasifyWindows(WindowsReleasifyCommand command)
//{
// var options = new WindowsReleasifyOptions {
// TargetRuntime = command.GetRid(),
// ReleaseDir = command.GetReleaseDirectory(),
// Package = command.Package,
// Icon = command.Icon,
// DeltaMode = command.Delta,
// SignParameters = command.SignParameters,
// EntryExecutableName = command.EntryExecutableName,
// Runtimes = command.Runtimes,
// Channel = command.Channel,
// SignParallel = command.SignParallel,
// SignSkipDll = command.SignSkipDll,
// SignTemplate = command.SignTemplate,
// SplashImage = command.SplashImage,
// };
// new WindowsPackCommandRunner(_logger).Releasify(options);
// return Task.CompletedTask;
//}
public virtual Task ExecuteGithubDownload(GitHubDownloadCommand command)
{
@@ -190,7 +189,7 @@ public class EmbeddedRunner : ICommandRunner
OutputFile = command.OutputFile,
DeltaMode = command.Delta,
};
return new DeltaGenCommandRunner().Run(options, _logger);
return new DeltaGenCommandRunner(_logger).Run(options);
}
public virtual Task ExecuteDeltaPatch(DeltaPatchCommand command)
@@ -200,6 +199,6 @@ public class EmbeddedRunner : ICommandRunner
PatchFiles = command.PatchFiles,
OutputFile = command.OutputFile,
};
return new DeltaPatchCommandRunner().Run(options, _logger);
return new DeltaPatchCommandRunner(_logger).Run(options);
}
}

View File

@@ -11,7 +11,7 @@ public interface ICommandRunner
public Task ExecuteS3Upload(S3UploadCommand command);
public Task ExecuteBundleOsx(OsxBundleCommand command);
public Task ExecutePackOsx(OsxPackCommand command);
public Task ExecuteReleasifyWindows(WindowsReleasifyCommand command);
//public Task ExecuteReleasifyWindows(WindowsReleasifyCommand command);
public Task ExecutePackWindows(WindowsPackCommand command);
public Task ExecuteDeltaGen(DeltaGenCommand command);
public Task ExecuteDeltaPatch(DeltaPatchCommand command);

View File

@@ -21,7 +21,11 @@ public class RunnerFactory
_logger.LogInformation(Program.INTRO);
var runner = await CreateAsync(options);
var method = typeof(ICommandRunner).GetMethod(commandName);
await (Task) method.Invoke(runner, new object[] { options });
try {
await (Task) method.Invoke(runner, new object[] { options });
} catch (Exception ex) {
_logger.Error(ex, $"Command {commandName} failed.");
}
}
private async Task<ICommandRunner> CreateAsync<T>(T options)

View File

@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Serilog.Configuration;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting.Display;
using Serilog.Parsing;
using Serilog.Sinks.Spectre.Extensions;
using Serilog.Sinks.Spectre.Renderers;
using Spectre.Console;
using Spectre.Console.Rendering;
namespace Velopack.Vpk
{
public class MySpectreConsoleSink : ILogEventSink
{
readonly ITemplateTokenRenderer[] renderers;
public MySpectreConsoleSink(string outputTemplate)
{
this.renderers = InitializeRenders(outputTemplate).ToArray();
}
public void Emit(LogEvent logEvent)
{
// Create renderable objects for each
// defined token
IRenderable[] items = this.renderers
.SelectMany(r => r.Render(logEvent))
.ToArray();
// Join all renderable objects
RenderableCollection collection = new RenderableCollection(items);
// Write them to the console
global::Spectre.Console.AnsiConsole.Write(collection);
}
private static IEnumerable<ITemplateTokenRenderer> InitializeRenders(string outputTemplate)
{
var template = new MessageTemplateParser().Parse(outputTemplate);
foreach (MessageTemplateToken token in template.Tokens) {
ITemplateTokenRenderer renderer;
if (TryInitializeRender(token, out renderer)) {
yield return renderer;
}
}
}
private static bool TryInitializeRender(
MessageTemplateToken token,
out ITemplateTokenRenderer renderer)
{
if (token is TextToken tt) {
renderer = new TextTokenRenderer(tt.Text);
return true;
}
if (token is PropertyToken pt) {
return TryInitializePropertyRender(pt, out renderer);
}
renderer = null;
return false;
}
private static bool TryInitializePropertyRender(
PropertyToken propertyToken,
out ITemplateTokenRenderer renderer)
{
renderer = GetPropertyRender(propertyToken);
return renderer != null;
}
private static ITemplateTokenRenderer GetPropertyRender(PropertyToken propertyToken)
{
switch (propertyToken.PropertyName) {
case OutputProperties.LevelPropertyName: {
return new LevelTokenRenderer(propertyToken);
}
case OutputProperties.NewLinePropertyName: {
return new NewLineTokenRenderer();
}
case OutputProperties.ExceptionPropertyName: {
return new MyExceptionTokenRenderer();
}
case OutputProperties.MessagePropertyName: {
return new MessageTemplateOutputTokenRenderer(propertyToken);
}
case OutputProperties.TimestampPropertyName: {
return new TimestampTokenRenderer(propertyToken);
}
case OutputProperties.PropertiesPropertyName: {
return new PropertyTemplateRenderer(propertyToken);
}
default: {
return new EventPropertyTokenRenderer(propertyToken);
}
}
}
}
public class MyExceptionTokenRenderer : ITemplateTokenRenderer
{
public IEnumerable<IRenderable> Render(LogEvent logEvent)
{
if (logEvent.Exception != null) {
yield return logEvent.Exception.GetRenderable(ExceptionFormats.ShortenEverything);
}
}
}
public static class MySpectreConsoleSinkExtensions
{
const string DefaultConsoleOutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}";
/// <summary>
/// Write log events to the console using Spectre.Console.
/// </summary>
/// <param name="loggerConfiguration">Logger sink configuration.</param>
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
/// The default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}".</param>
/// <param name="restrictedToMinimumLevel">The minimum level for
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
/// to be changed at runtime.</param>
/// <returns>Configuration object allowing method chaining.</returns>
public static LoggerConfiguration SpectreShortenedExceptions(
this LoggerSinkConfiguration loggerConfiguration,
string outputTemplate = DefaultConsoleOutputTemplate,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
{
return loggerConfiguration.Sink(new MySpectreConsoleSink(outputTemplate), restrictedToMinimumLevel, levelSwitch);
}
}
}

View File

@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.Spectre;
using Velopack.Vpk.Commands;
using Velopack.Vpk.Compat;
@@ -42,7 +43,7 @@ public class Program
.MinimumLevel.Is(minLevel)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.SpectreShortenedExceptions()
.CreateLogger();
builder.Logging.AddSerilog();

View File

@@ -17,11 +17,17 @@ public class UpdateChecker
var cancel = new CancellationTokenSource(3000);
var myVer = VelopackRuntimeInfo.VelopackNugetVersion;
var dl = new NugetDownloader(new NullNugetLogger());
var package = await dl.GetPackageMetadata("vpk", (myVer.IsPrerelease || myVer.HasMetadata) ? "pre" : "latest", cancel.Token).ConfigureAwait(false);
if (package.Identity.Version > myVer)
_logger.Warn($"There is a newer version of vpk available ({package.Identity.Version})");
else
var isPre = myVer.IsPrerelease || myVer.HasMetadata;
var package = await dl.GetPackageMetadata("vpk", isPre ? "pre" : "latest", cancel.Token).ConfigureAwait(false);
if (package.Identity.Version > myVer) {
if (isPre) {
_logger.Warn($"There is a newer version of vpk available ({package.Identity.Version}). Run 'dotnet tool update -g vpk'");
} else {
_logger.Warn($"There is a newer version of vpk available. Run 'dotnet tool update -g vpk --version {package.Identity.Version}'");
}
} else {
_logger.Debug($"vpk is up to date (latest online = {package.Identity.Version})");
}
} catch (Exception ex) {
_logger.Debug(ex, "Failed to check for updates.");
}

View File

@@ -27,6 +27,7 @@
<None Include="..\Rust\target\debug\update" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('OSX'))" />
<None Include="..\Rust\target\debug\update.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<None Include="..\Rust\target\debug\setup.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<None Include="..\Rust\target\debug\stub.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<None Include="..\..\vendor\rcedit.exe" CopyToOutputDirectory="Always" />
<None Include="..\..\vendor\zstd.exe" CopyToOutputDirectory="Always" />
<None Include="..\..\vendor\signtool.exe" CopyToOutputDirectory="Always" />
@@ -37,6 +38,7 @@
<None Include="..\Rust\target\release\updatemac" Pack="true" PackagePath="vendor" />
<None Include="..\Rust\target\release\update.exe" Pack="true" PackagePath="vendor" />
<None Include="..\Rust\target\release\setup.exe" Pack="true" PackagePath="vendor" />
<None Include="..\Rust\target\release\stub.exe" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\rcedit.exe" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\zstd.exe" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\signtool.exe" Pack="true" PackagePath="vendor" />
@@ -46,7 +48,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.Spectre" Version="0.4.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.23407.1" />
</ItemGroup>

View File

@@ -137,7 +137,7 @@ namespace Velopack.Compression
if (VelopackRuntimeInfo.IsWindows) {
MsDeltaCompression.ApplyDelta(inputFile, finalTarget, tempTargetFile);
} else {
throw new InvalidOperationException("msdiff is not supported on non-windows platforms.");
throw new PlatformNotSupportedException("msdelta is not supported on non-windows platforms.");
}
verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile);

View File

@@ -13,7 +13,7 @@ namespace Velopack.Compression
{
public static void ExtractZipToDirectory(ILogger logger, string inputFile, string outputDirectory)
{
logger.Info($"Extracting '{inputFile}' to '{outputDirectory}' using System.IO.Compression...");
logger.Debug($"Extracting '{inputFile}' to '{outputDirectory}' using System.IO.Compression...");
Utility.DeleteFileOrDirectoryHard(outputDirectory);
ZipFile.ExtractToDirectory(inputFile, outputDirectory);
}
@@ -21,7 +21,7 @@ namespace Velopack.Compression
public static void CreateZipFromDirectory(ILogger logger, string outputFile, string directoryToCompress, Action<int> progress = null)
{
progress ??= (x => { });
logger.Info($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression...");
logger.Debug($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression...");
// we have stopped using ZipFile so we can add async and determinism.
// ZipFile.CreateFromDirectory(directoryToCompress, outputFile);
@@ -31,7 +31,7 @@ namespace Velopack.Compression
public static async Task CreateZipFromDirectoryAsync(ILogger logger, string outputFile, string directoryToCompress, Action<int> progress = null)
{
progress ??= (x => { });
logger.Info($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression...");
logger.Debug($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression...");
// we have stopped using ZipFile so we can add async and determinism.
// ZipFile.CreateFromDirectory(directoryToCompress, outputFile);

View File

@@ -38,6 +38,13 @@ namespace Velopack
return (int) totalPercentage;
}
public static Action<int> CreateProgressDelegate(Action<int> rootProgress, int stepStartPercentage, int stepEndPercentage)
{
return percentage => {
rootProgress(CalculateProgress(percentage, stepStartPercentage, stepEndPercentage));
};
}
public static string RemoveByteOrderMarkerIfPresent(string content)
{
return string.IsNullOrEmpty(content)

View File

@@ -139,7 +139,7 @@ This is just a _test_!
ReleaseNotes = releaseNotes,
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsOSX) {
var options = new OsxPackOptions {
EntryExecutableName = "TestApp",

View File

@@ -56,7 +56,7 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
var nupkgPath = Path.Combine(tmpReleaseDir, $"{id}-{version}-asd123-win-x64-full.nupkg");
Assert.True(File.Exists(nupkgPath));
@@ -121,7 +121,7 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
var nupkgPath1 = Path.Combine(tmpReleaseDir, $"{id}-{version}-win-x64-full.nupkg");
@@ -137,7 +137,7 @@ public class WindowsPackTests
Assert.Equal(1, rel1.Count());
options.Channel = "hello";
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
var nupkgPath2 = Path.Combine(tmpReleaseDir, $"{id}-{version}-hello-win-x64-full.nupkg");
Assert.True(File.Exists(nupkgPath2));
@@ -184,9 +184,9 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
Assert.Throws<ArgumentException>(() => runner.Pack(options));
Assert.Throws<ArgumentException>(() => runner.Run(options).GetAwaiterResult());
}
[SkippableFact]
@@ -222,10 +222,10 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
options.TargetRuntime = RID.Parse("win10.0.19043-x86");
Assert.Throws<ArgumentException>(() => runner.Pack(options));
Assert.Throws<ArgumentException>(() => runner.Run(options).GetAwaiterResult());
}
[SkippableFact]
@@ -256,7 +256,7 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
Assert.Throws<Exception>(() => runner.Pack(options));
Assert.Throws<Exception>(() => runner.Run(options).GetAwaiterResult());
}
[SkippableFact]
@@ -288,7 +288,7 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
var setupPath1 = Path.Combine(tmpReleaseDir, $"{id}-win-x64-Setup.exe");
Assert.True(File.Exists(setupPath1));
@@ -396,11 +396,11 @@ public class WindowsPackTests
// apply delta and check package
var output = Path.Combine(releaseDir, "delta.patched");
new DeltaPatchCommandRunner().Run(new DeltaPatchOptions {
new DeltaPatchCommandRunner(logger).Run(new DeltaPatchOptions {
BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-win-x64-full.nupkg"),
OutputFile = output,
PatchFiles = new[] { new FileInfo(deltaPath) },
}, logger);
}).GetAwaiterResult();
// are the packages the same?
Assert.True(File.Exists(output));
@@ -762,7 +762,7 @@ public class WindowsPackTests
};
var runner = new WindowsPackCommandRunner(logger);
runner.Pack(options);
runner.Run(options).GetAwaiterResult();
} finally {
File.WriteAllText(testStringFile, oldText);
}