mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add console progress bars with Spectre & add more parallelism
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>() {
|
||||
|
||||
42
src/Velopack.Packaging/Chmod.cs
Normal file
42
src/Velopack.Packaging/Chmod.cs
Normal 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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace Velopack.Packaging
|
||||
{
|
||||
internal interface ICommand<TOpt> where TOpt : class
|
||||
{
|
||||
Task Run(TOpt options, ILogger logger);
|
||||
Task Run(TOpt options);
|
||||
}
|
||||
}
|
||||
|
||||
12
src/Velopack.Packaging/INugetPackCommand.cs
Normal file
12
src/Velopack.Packaging/INugetPackCommand.cs
Normal 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; }
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
315
src/Velopack.Packaging/PackageBuilder.cs
Normal file
315
src/Velopack.Packaging/PackageBuilder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/Velopack.Packaging/ReleasePackage.cs
Normal file
26
src/Velopack.Packaging/ReleasePackage.cs
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Velopack.Packaging/UserErrorException.cs
Normal file
31
src/Velopack.Packaging/UserErrorException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
142
src/Velopack.Vpk/MySpectreConsoleSink.cs
Normal file
142
src/Velopack.Vpk/MySpectreConsoleSink.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user