Exe installer verification and better console output

This commit is contained in:
Ben Olden-Cooligan 2022-07-09 20:57:10 -07:00
parent 4b6cfb6ffe
commit a29e69505d
19 changed files with 143 additions and 40 deletions

View File

@ -13,6 +13,7 @@ OutputDir=../publish/{#AppVersion}
OutputBaseFilename=naps2-{#AppVersion}-{#AppPlatform}
Compression=lzma
SolidCompression=yes
; !arch
LicenseFile=..\..\LICENSE
UninstallDisplayIcon={app}\scanner-app.ico
@ -54,11 +55,11 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
; !files
; Delete files from old locations in case of upgrade
; TODO: Delete from Program Files (x86)?
[InstallDelete]
Type: files; Name: "{app}\*.exe"
Type: files; Name: "{app}\*.exe.config"
Type: filesandordirs; Name: "{app}\lib"
; !clean32
[Icons]
Name: "{group}\NAPS2"; Filename: "{app}\NAPS2.exe"

View File

@ -2,7 +2,7 @@ namespace NAPS2.Tools;
public static class Cli
{
public static void Run(string command, string args, Dictionary<string, string>? env = null)
public static void Run(string command, string args, bool verbose, Dictionary<string, string>? env = null)
{
var startInfo = new ProcessStartInfo
{
@ -29,23 +29,26 @@ public static class Cli
// TODO: Maybe we forward Console.CancelKeyPress
while (!proc.WaitForExit(100))
{
PrintAll(proc.StandardOutput);
PrintAll(proc.StandardError);
PrintAll(proc.StandardOutput, verbose);
PrintAll(proc.StandardError, true);
}
PrintAll(proc.StandardOutput);
PrintAll(proc.StandardError);
PrintAll(proc.StandardOutput, verbose);
PrintAll(proc.StandardError, true);
if (proc.ExitCode != 0)
{
throw new Exception($"Command failed: {command} {args}");
}
}
private static void PrintAll(StreamReader stream)
private static void PrintAll(StreamReader stream, bool forwardToStdout)
{
string? line;
while ((line = stream.ReadLine()) != null)
{
Console.WriteLine(line);
if (forwardToStdout)
{
Console.WriteLine(line);
}
}
}
}

View File

@ -7,7 +7,7 @@ public static class BuildCommand
foreach (var config in GetConfigs(opts.What))
{
Console.WriteLine($"---------- BUILDING CONFIGURATION: {config} ----------");
Cli.Run("dotnet", $"build -c {config}");
Cli.Run("dotnet", $"build -c {config}", opts.Verbose);
}
return 0;
}

View File

@ -7,4 +7,7 @@ public class BuildOptions
{
[Value(0, MetaName = "what", Required = true, HelpText = "all|debug|exe|msi|zip")]
public string? What { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Show full output")]
public bool Verbose { get; set; }
}

View File

@ -4,13 +4,18 @@ namespace NAPS2.Tools.Project.Packaging;
public static class InnoSetupPackager
{
public static void PackageExe(PackageInfo packageInfo)
public static void PackageExe(PackageInfo packageInfo, bool verbose)
{
var exePath = packageInfo.GetPath("exe");
Console.WriteLine($"Packaging exe installer: {exePath}");
var innoDefPath = GenerateInnoDef(packageInfo);
// TODO: Use https://github.com/DomGries/InnoDependencyInstaller for .net dependency
var iscc = Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%/Inno Setup 6/iscc.exe");
Cli.Run(iscc, $"\"{innoDefPath}\"");
Cli.Run(iscc, $"\"{innoDefPath}\"", verbose);
Console.WriteLine(verbose ? $"Packaged exe installer: {exePath}" : "Done.");
}
private static string GenerateInnoDef(PackageInfo packageInfo)
@ -22,6 +27,15 @@ public static class InnoSetupPackager
defLines.AppendLine($"#define AppPlatform \"{packageInfo.Platform.PackageName()}\"");
template = template.Replace("; !defs", defLines.ToString());
if (packageInfo.Platform == Platform.Win64)
{
var arch = new StringBuilder();
arch.AppendLine("ArchitecturesInstallIn64BitMode=x64");
arch.AppendLine("ArchitecturesAllowed=x64");
template = template.Replace("; !arch", arch.ToString());
template = template.Replace("; !clean32", @"Type: filesandordirs; Name: ""{commonpf32}\NAPS2""");
}
var fileLines = new StringBuilder();
foreach (var pkgFile in packageInfo.Files)
{

View File

@ -13,24 +13,24 @@ public static class PackageCommand
// TODO: Allow customizing net version, platform, etc
// TODO: The fact that we only have one project config for the app but multiple for the SDK is problematic; things will overwrite each other unless we either pull them explicitly from the right project or have a separate config or normalize things somehow to avoid needing multiple configs
var pkgInfo = GetPackageInfo(platform, "InstallerEXE");
InnoSetupPackager.PackageExe(pkgInfo);
InnoSetupPackager.PackageExe(pkgInfo, opts.Verbose);
}
if (opts.What == "msi" || opts.What == "all")
{
var pkgInfo = GetPackageInfo(platform, "InstallerMSI");
WixToolsetPackager.PackageMsi(pkgInfo);
WixToolsetPackager.PackageMsi(pkgInfo, opts.Verbose);
}
if (opts.What == "zip" || opts.What == "all")
{
var pkgInfo = GetPackageInfo(platform, "Standalone");
ZipArchivePackager.PackageZip(pkgInfo);
ZipArchivePackager.PackageZip(pkgInfo, opts.Verbose);
}
return 0;
}
private static PackageInfo GetPackageInfo(Platform platform, string preferredConfig)
{
var pkgInfo = new PackageInfo(platform, VersionHelper.GetProjectVersion("NAPS2.App.WinForms"));
var pkgInfo = new PackageInfo(platform, ProjectHelper.GetProjectVersion("NAPS2.App.WinForms"));
foreach (var project in new[]
{ "NAPS2.Sdk", "NAPS2.Lib.Common", "NAPS2.App.Worker", "NAPS2.App.Console", "NAPS2.App.WinForms" })
{

View File

@ -15,7 +15,10 @@ public class PackageInfo
public string Version { get; }
public string FileName => $"naps2-{Version}-{Platform.PackageName()}";
public string GetPath(string ext)
{
return ProjectHelper.GetPackagePath(ext, Platform, Version);
}
public IEnumerable<PackageFile> Files => _files;

View File

@ -11,6 +11,9 @@ public class PackageOptions
// TODO: Allow platform combos (e.g. win32+win64)
[Option('p', "platform", Required = false, HelpText = "win32|win64|mac|macarm|linux")]
public string? Platform { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Show full output")]
public bool Verbose { get; set; }
// TODO: Add net target (net462/net5/net5-windows etc.)
}

View File

@ -5,18 +5,20 @@ namespace NAPS2.Tools.Project.Packaging;
public static class WixToolsetPackager
{
public static void PackageMsi(PackageInfo pkgInfo)
public static void PackageMsi(PackageInfo pkgInfo, bool verbose)
{
var msiPath = pkgInfo.GetPath("msi");
Console.WriteLine($"Packaging msi installer: {msiPath}");
var wxsPath = GenerateWxs(pkgInfo);
var candle = Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%/WiX Toolset v3.11/bin/candle.exe");
Cli.Run(candle, $"\"{wxsPath}\" -o \"{Paths.SetupObj}/\"");
Cli.Run(candle, $"\"{wxsPath}\" -o \"{Paths.SetupObj}/\"", verbose);
var wixobjPath = wxsPath.Replace(".wxs", ".wixobj");
var light = Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%/WiX Toolset v3.11/bin/light.exe");
var publishFile = Path.Combine(Paths.Publish, pkgInfo.Version, $"{pkgInfo.FileName}.msi");
Cli.Run(light, $"\"{wixobjPath}\" -spdb -ext WixUIExtension -o \"{publishFile}\"");
Cli.Run(light, $"\"{wixobjPath}\" -spdb -ext WixUIExtension -o \"{msiPath}\"", verbose);
Console.WriteLine(verbose ? $"Packaged msi installer: {msiPath}" : "Done.");
}
private static string GenerateWxs(PackageInfo packageInfo)

View File

@ -4,9 +4,10 @@ namespace NAPS2.Tools.Project.Packaging;
public static class ZipArchivePackager
{
public static void PackageZip(PackageInfo pkgInfo)
public static void PackageZip(PackageInfo pkgInfo, bool verbose)
{
var zipPath = Path.Combine(Paths.Publish, pkgInfo.Version, $"{pkgInfo.FileName}.zip");
var zipPath = pkgInfo.GetPath("zip");
Console.WriteLine($"Packaging zip archive: {zipPath}");
if (File.Exists(zipPath))
{
File.Delete(zipPath);
@ -22,9 +23,24 @@ public static class ZipArchivePackager
using var archive = ZipFile.Open(zipPath, ZipArchiveMode.Create);
foreach (var file in pkgInfo.Files)
{
archive.CreateEntryFromFile(file.SourcePath, Path.Combine("App", file.DestPath));
var destPath = Path.Combine("App", file.DestPath);
if (verbose)
{
Console.WriteLine($"Compressing {destPath}");
}
archive.CreateEntryFromFile(file.SourcePath, destPath);
}
if (verbose)
{
Console.WriteLine($"Creating Data/");
}
archive.CreateEntry("Data/");
if (verbose)
{
Console.WriteLine($"Compressing NAPS2.Portable.exe");
}
archive.CreateEntryFromFile(portableExe, "NAPS2.Portable.exe");
Console.WriteLine(verbose ? $"Packaged zip archive: {zipPath}" : "Done.");
}
}

View File

@ -2,7 +2,7 @@ using System.Text.RegularExpressions;
namespace NAPS2.Tools.Project;
public static class VersionHelper
public static class ProjectHelper
{
public static string GetProjectVersion(string projectName)
{
@ -19,4 +19,15 @@ public static class VersionHelper
}
return version;
}
public static string GetDefaultProjectVersion()
{
return GetProjectVersion("NAPS2.App.WinForms");
}
public static string GetPackagePath(string ext, Platform platform, string? version = null)
{
version ??= GetProjectVersion("NAPS2.App.WinForms");
return Path.Combine(Paths.Publish, version, $"naps2-{version}-{platform.PackageName()}.{ext}");
}
}

View File

@ -5,7 +5,7 @@ public static class TestCommand
public static int Run(TestOptions opts)
{
// TODO: Do we want to test on .net core too?
Cli.Run("dotnet", "test -f net462", new()
Cli.Run("dotnet", "test -f net462", opts.Verbose, new()
{
{"NAPS2_TEST_ROOT", Path.Combine(Paths.SolutionRoot, "NAPS2.App.Tests", "bin", "Debug", "net462")}
});

View File

@ -5,4 +5,6 @@ namespace NAPS2.Tools.Project;
[Verb("test", HelpText = "Runs the project tests")]
public class TestOptions
{
[Option('v', "verbose", Required = false, HelpText = "Show full output")]
public bool Verbose { get; set; }
}

View File

@ -4,19 +4,21 @@ public class AppDriverRunner : IDisposable
{
private readonly Process _process;
public static AppDriverRunner Start()
public static AppDriverRunner Start(bool verbose)
{
return new AppDriverRunner();
return new AppDriverRunner(verbose);
}
private AppDriverRunner()
private AppDriverRunner(bool verbose)
{
var path = @"C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe";
// TODO: Wait for successful starting and handle errors (e.g. if the dev doesn't have developer mode on)
_process = Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = false
UseShellExecute = false,
// TODO: Fix (use Cli.Run from a thread? Or similar)
// RedirectStandardOutput = !verbose
}) ?? throw new Exception($"Could not start WinAppDriver: {path}");
}

View File

@ -0,0 +1,25 @@
namespace NAPS2.Tools.Project.Verification;
public static class ExeSetupVerifier
{
public static void Verify(Platform platform, string version, bool verbose)
{
var exePath = ProjectHelper.GetPackagePath("exe", platform, version);
Console.WriteLine($"Starting exe installer: {exePath}");
var process = Process.Start(new ProcessStartInfo
{
FileName = exePath,
Arguments = "/SILENT /CLOSEAPPLICATIONS"
});
if (process == null)
{
throw new Exception($"Could not start installer: {exePath}");
}
process.WaitForExit();
var pfVar = platform == Platform.Win64 ? "%PROGRAMFILES%" : "%PROGRAMFILES(X86)%";
var pfPath = Environment.ExpandEnvironmentVariables(pfVar);
Verifier.RunVerificationTests(Path.Combine(pfPath, "NAPS2"), verbose);
Console.WriteLine(verbose ? $"Verified exe installer: {exePath}" : "Done.");
}
}

View File

@ -0,0 +1,17 @@
namespace NAPS2.Tools.Project.Verification;
public static class Verifier
{
public static void RunVerificationTests(string testRoot, bool verbose)
{
Console.WriteLine($"Running verification tests in: {testRoot}");
Cli.Run("dotnet", "test NAPS2.App.Tests -f net462", verbose, new()
{
{ "NAPS2_TEST_ROOT", testRoot }
});
if (verbose)
{
Console.WriteLine($"Ran verification tests in: {testRoot}");
}
}
}

View File

@ -5,14 +5,12 @@ public class VerifyCommand
public static int Run(VerifyOptions opts)
{
var platform = PlatformHelper.FromOption(opts.Platform, Platform.Win64);
var version = ProjectHelper.GetDefaultProjectVersion();
var version = VersionHelper.GetProjectVersion("NAPS2.App.WinForms");
var basePath = Path.Combine(Paths.Publish, version, $"naps2-{version}-{platform.PackageName()}");
using var appDriverRunner = AppDriverRunner.Start();
using var appDriverRunner = AppDriverRunner.Start(opts.Verbose);
if (opts.What == "exe" || opts.What == "all")
{
// ExeSetupVerifier.Verify()
ExeSetupVerifier.Verify(platform, version, opts.Verbose);
}
if (opts.What == "msi" || opts.What == "all")
{
@ -20,7 +18,7 @@ public class VerifyCommand
}
if (opts.What == "zip" || opts.What == "all")
{
ZipArchiveVerifier.Verify(basePath + ".zip", opts.NoCleanup);
ZipArchiveVerifier.Verify(platform, version, opts.NoCleanup, opts.Verbose);
}
return 0;
}

View File

@ -13,4 +13,7 @@ public class VerifyOptions
[Option("nocleanup", Required = false, HelpText = "Skip cleaning up temp files")]
public bool NoCleanup { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Show full output")]
public bool Verbose { get; set; }
}

View File

@ -4,17 +4,17 @@ namespace NAPS2.Tools.Project.Verification;
public class ZipArchiveVerifier
{
public static void Verify(string zipPath, bool noCleanup)
public static void Verify(Platform platform, string version, bool verbose, bool noCleanup)
{
var zipPath = ProjectHelper.GetPackagePath("zip", platform, version);
Console.WriteLine($"Extracting zip archive: {zipPath}");
// TODO: We probably want other commands to use unique paths too
var extractPath = Path.Combine(Paths.SetupObj, Path.GetRandomFileName());
try
{
ZipFile.ExtractToDirectory(zipPath, extractPath);
Cli.Run("dotnet", "test NAPS2.App.Tests -f net462", new()
{
{ "NAPS2_TEST_ROOT", Path.Combine(extractPath, "App") }
});
Verifier.RunVerificationTests(Path.Combine(extractPath, "App"), verbose);
Console.WriteLine(verbose ? $"Verified zip archive: {zipPath}" : "Done.");
}
finally
{