mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add squirrel app builder / hook manager
This commit is contained in:
@@ -21,7 +21,7 @@ pub fn apply<'a>(restart: bool, wait_for_parent: bool, package: Option<&PathBuf>
|
||||
}
|
||||
|
||||
if restart {
|
||||
shared::start_package(&app, &root_path, exe_args)?;
|
||||
shared::start_package(&app, &root_path, exe_args, Some("CLOWD_SQUIRREL_RESTART"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -79,7 +79,7 @@ fn apply_package<'a>(package: Option<&PathBuf>, app: &Manifest, root_path: &Path
|
||||
info!("Applying package to current: {}", found_version);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
crate::windows::run_hook(&app, &root_path, "--squirrel-obsoleted", 15);
|
||||
crate::windows::run_hook(&app, &root_path, "--squirrel-obsolete", 15);
|
||||
|
||||
let current_dir = app.get_current_path(&root_path);
|
||||
shared::replace_dir_with_rollback(current_dir.clone(), || {
|
||||
|
||||
@@ -30,11 +30,11 @@ pub fn start(wait_for_parent: bool, exe_name: Option<&String>, exe_args: Option<
|
||||
info!("About to launch: '{}' in dir '{}'", exe_to_execute.to_string_lossy(), current);
|
||||
|
||||
if let Some(args) = exe_args {
|
||||
crate::shared::run_process(exe_to_execute, args, current)?;
|
||||
crate::windows::run_process(exe_to_execute, args, current)?;
|
||||
} else if let Some(args) = legacy_args {
|
||||
crate::windows::run_process_raw_args(exe_to_execute, args, current)?;
|
||||
} else {
|
||||
crate::shared::run_process(exe_to_execute, vec![], current)?;
|
||||
crate::windows::run_process(exe_to_execute, vec![], current)?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -254,9 +254,8 @@ fn install_app(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::mp
|
||||
app.write_uninstall_entry(root_path)?;
|
||||
|
||||
if !dialogs::get_silent() {
|
||||
info!("Starting app: \"{}\" --squirrel-firstrun", main_exe_path);
|
||||
let args = vec!["--squirrel-firstrun"];
|
||||
let _ = shared::run_process(&main_exe_path, args, ¤t_path);
|
||||
info!("Starting app...");
|
||||
shared::start_package(&app, &root_path, None, Some("CLOWD_SQUIRREL_FIRSTRUN"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -2,11 +2,9 @@ use anyhow::{anyhow, bail, Result};
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
io::{self},
|
||||
path::{Path, PathBuf},
|
||||
process::Command as Process,
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -62,15 +60,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_process<S, P>(exe: S, args: Vec<&str>, work_dir: P) -> Result<()>
|
||||
where
|
||||
S: AsRef<OsStr>,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Process::new(exe).args(args).current_dir(work_dir).spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn retry_io<F, T>(op: F) -> io::Result<T>
|
||||
where
|
||||
F: Fn() -> io::Result<T>,
|
||||
|
||||
@@ -25,21 +25,24 @@ pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_package<P: AsRef<Path>>(_app: &Manifest, root_dir: P, exe_args: Option<Vec<&str>>) -> Result<()> {
|
||||
pub fn start_package<P: AsRef<Path>>(_app: &Manifest, root_dir: P, exe_args: Option<Vec<&str>>, set_env: Option<&str>) -> Result<()> {
|
||||
let root_dir = root_dir.as_ref().to_string_lossy().to_string();
|
||||
let mut args = vec!["-n", &root_dir];
|
||||
if let Some(a) = exe_args {
|
||||
args.push("--args");
|
||||
args.extend(a);
|
||||
}
|
||||
Process::new("/usr/bin/open").args(args).spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?;
|
||||
let mut psi = Process::new("/usr/bin/open").args(args);
|
||||
if let Some(env) = set_env {
|
||||
psi.env(env, "true");
|
||||
}
|
||||
psi.spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_start_and_stop_package()
|
||||
{
|
||||
fn test_start_and_stop_package() {
|
||||
let mani = Manifest::default();
|
||||
let root_dir = "/Applications/Calcbot.app";
|
||||
let _ = force_stop_package(root_dir);
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::{anyhow, bail, Result};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
process::Command as Process,
|
||||
};
|
||||
use winsafe::{self as w, co, prelude::*};
|
||||
|
||||
@@ -118,7 +119,7 @@ pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_package<P: AsRef<Path>>(app: &Manifest, root_dir: P, exe_args: Option<Vec<&str>>) -> Result<()> {
|
||||
pub fn start_package<P: AsRef<Path>>(app: &Manifest, root_dir: P, exe_args: Option<Vec<&str>>, set_env: Option<&str>) -> Result<()> {
|
||||
let root_dir = root_dir.as_ref().to_path_buf();
|
||||
let current = app.get_current_path(&root_dir);
|
||||
let exe = app.get_main_exe_path(&root_dir);
|
||||
@@ -130,11 +131,19 @@ pub fn start_package<P: AsRef<Path>>(app: &Manifest, root_dir: P, exe_args: Opti
|
||||
|
||||
crate::windows::assert_can_run_binary_authenticode(&exe_to_execute)?;
|
||||
|
||||
let mut psi = Process::new(&exe_to_execute);
|
||||
psi.current_dir(¤t);
|
||||
if let Some(args) = exe_args {
|
||||
super::run_process(exe_to_execute, args, current)?;
|
||||
} else {
|
||||
crate::shared::run_process(exe_to_execute, vec![], current)?;
|
||||
};
|
||||
psi.args(args);
|
||||
}
|
||||
if let Some(env) = set_env {
|
||||
debug!("Setting environment variable: {}={}", env, "true");
|
||||
psi.env(env, "true");
|
||||
}
|
||||
|
||||
info!("About to launch: '{}' in dir '{}'", exe_to_execute.to_string_lossy(), current);
|
||||
info!("Args: {:?}", psi.get_args());
|
||||
psi.spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -255,6 +255,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_process<S, P>(exe: S, args: Vec<&str>, work_dir: P) -> Result<()>
|
||||
where
|
||||
S: AsRef<OsStr>,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Process::new(exe).args(args).current_dir(work_dir).spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_process_no_console<S, P>(exe: S, args: Vec<&str>, work_dir: P) -> Result<()>
|
||||
where
|
||||
S: AsRef<OsStr>,
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Squirrel.Locators
|
||||
/// <summary>
|
||||
/// Finds latest .nupkg file in the PackagesDir or null if not found.
|
||||
/// </summary>
|
||||
public ReleaseEntry GetLatestLocalPackage();
|
||||
public ReleaseEntry GetLatestLocalFullPackage();
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for this user which is used to calculate whether this user is eligible for
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace Squirrel.Locators
|
||||
@@ -21,14 +22,16 @@ namespace Squirrel.Locators
|
||||
/// </summary>
|
||||
public static SquirrelLocator GetDefault(ILogger logger)
|
||||
{
|
||||
var log = logger ?? NullLogger.Instance;
|
||||
|
||||
if (_current != null)
|
||||
return _current;
|
||||
|
||||
if (SquirrelRuntimeInfo.IsWindows)
|
||||
return _current ??= new WindowsSquirrelLocator(logger);
|
||||
return _current ??= new WindowsSquirrelLocator(log);
|
||||
|
||||
if (SquirrelRuntimeInfo.IsOSX)
|
||||
return _current ??= new OsxSquirrelLocator(logger);
|
||||
return _current ??= new OsxSquirrelLocator(log);
|
||||
|
||||
throw new NotSupportedException($"OS platform '{SquirrelRuntimeInfo.SystemOs.GetOsLongName()}' is not supported.");
|
||||
}
|
||||
@@ -78,6 +81,9 @@ namespace Squirrel.Locators
|
||||
/// <inheritdoc/>
|
||||
public virtual List<ReleaseEntry> GetLocalPackages()
|
||||
{
|
||||
if (CurrentlyInstalledVersion == null)
|
||||
return new List<ReleaseEntry>(0);
|
||||
|
||||
return Directory.EnumerateFiles(PackagesDir, "*.nupkg")
|
||||
.Select(x => ReleaseEntry.GenerateFromFile(x))
|
||||
.Where(x => x?.Version != null)
|
||||
@@ -85,10 +91,11 @@ namespace Squirrel.Locators
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReleaseEntry GetLatestLocalPackage()
|
||||
public ReleaseEntry GetLatestLocalFullPackage()
|
||||
{
|
||||
return GetLocalPackages()
|
||||
.OrderByDescending(x => x.Version)
|
||||
.Where(x => !x.IsDelta)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
234
src/Squirrel/SquirrelApp.cs
Normal file
234
src/Squirrel/SquirrelApp.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NuGet.Versioning;
|
||||
using Squirrel.Locators;
|
||||
|
||||
namespace Squirrel
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate type for handling Squirrel startup events
|
||||
/// </summary>
|
||||
/// <param name="version">The currently executing version of this application</param>
|
||||
public delegate void SquirrelHook(SemanticVersion version);
|
||||
|
||||
/// <summary>
|
||||
/// SquirrelApp helps you to handle Squirrel app activation events correctly.
|
||||
/// This should be used as early as possible in your application startup code.
|
||||
/// (eg. the beginning of Main() in Program.cs)
|
||||
/// </summary>
|
||||
public sealed class SquirrelApp
|
||||
{
|
||||
ISquirrelLocator _locator;
|
||||
SquirrelHook _install;
|
||||
SquirrelHook _update;
|
||||
SquirrelHook _obsolete;
|
||||
SquirrelHook _uninstall;
|
||||
SquirrelHook _firstrun;
|
||||
SquirrelHook _restarted;
|
||||
string[] _args;
|
||||
bool _autoApply = true;
|
||||
|
||||
private SquirrelApp()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a new Squirrel application builder.
|
||||
/// </summary>
|
||||
public static SquirrelApp Build() => new SquirrelApp();
|
||||
|
||||
/// <summary>
|
||||
/// Override the command line arguments used to determine the Squirrel hook to run.
|
||||
/// If this is not set, the command line arguments passed to the application will be used.
|
||||
/// </summary>
|
||||
public SquirrelApp SetArgs(string[] args)
|
||||
{
|
||||
_args = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set whether to automatically apply downloaded updates on startup. This is ON by default.
|
||||
/// </summary>
|
||||
public SquirrelApp SetAutoApplyOnStartup(bool autoApply)
|
||||
{
|
||||
_autoApply = autoApply;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the default <see cref="ISquirrelLocator"/> used to search for application paths.
|
||||
/// </summary>
|
||||
public SquirrelApp SetLocator(ISquirrelLocator locator)
|
||||
{
|
||||
_locator = locator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This hook is triggered when the application is started for the first time after installation.
|
||||
/// </summary>
|
||||
public SquirrelApp WithFirstRun(SquirrelHook hook)
|
||||
{
|
||||
_firstrun = hook;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This hook is triggered when the application is restarted by Squirrel after installing updates.
|
||||
/// </summary>
|
||||
public SquirrelApp WithRestarted(SquirrelHook hook)
|
||||
{
|
||||
_restarted = hook;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WARNING: FastCallback hooks are run during critical stages of Squirrel operations.
|
||||
/// Your code will be run and then <see cref="Environment.Exit(int)"/> will be called.
|
||||
/// If your code has not completed within 30 seconds, it will be terminated.
|
||||
/// Only supported on windows; On other operating systems, this will never be called.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public SquirrelApp WithAfterInstallFastCallback(SquirrelHook hook)
|
||||
{
|
||||
_install = hook;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WARNING: FastCallback hooks are run during critical stages of Squirrel operations.
|
||||
/// Your code will be run and then <see cref="Environment.Exit(int)"/> will be called.
|
||||
/// If your code has not completed within 15 seconds, it will be terminated.
|
||||
/// Only supported on windows; On other operating systems, this will never be called.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public SquirrelApp WithAfterUpdateFastCallback(SquirrelHook hook)
|
||||
{
|
||||
_update = hook;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WARNING: FastCallback hooks are run during critical stages of Squirrel operations.
|
||||
/// Your code will be run and then <see cref="Environment.Exit(int)"/> will be called.
|
||||
/// If your code has not completed within 15 seconds, it will be terminated.
|
||||
/// Only supported on windows; On other operating systems, this will never be called.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public SquirrelApp WithBeforeUpdateFastCallback(SquirrelHook hook)
|
||||
{
|
||||
_obsolete = hook;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WARNING: FastCallback hooks are run during critical stages of Squirrel operations.
|
||||
/// Your code will be run and then <see cref="Environment.Exit(int)"/> will be called.
|
||||
/// If your code has not completed within 30 seconds, it will be terminated.
|
||||
/// Only supported on windows; On other operating systems, this will never be called.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public SquirrelApp WithBeforeUninstallFastCallback(SquirrelHook hook)
|
||||
{
|
||||
_uninstall = hook;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the Squirrel application startup code and triggers any configured hooks.
|
||||
/// </summary>
|
||||
/// <param name="logger">A logging interface for diagnostic messages.</param>
|
||||
public void Run(ILogger logger = null)
|
||||
{
|
||||
var args = _args ?? Environment.GetCommandLineArgs().Skip(1).ToArray();
|
||||
var log = logger ?? NullLogger.Instance;
|
||||
var locator = _locator ?? SquirrelLocator.GetDefault(log);
|
||||
|
||||
// internal hook run by the Squirrel tooling to check everything is working
|
||||
if (args.Length >= 1 && args[0].Equals("--squirrel-version", StringComparison.OrdinalIgnoreCase)) {
|
||||
Console.WriteLine(SquirrelRuntimeInfo.SquirrelNugetVersion);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
log.Info("Starting Squirrel App (Run).");
|
||||
|
||||
// first, we run any fast exit hooks
|
||||
SquirrelHook defaultBlock = ((v) => { });
|
||||
var fastExitlookup = new[] {
|
||||
new { Key = "--squirrel-install", Value = _install ?? defaultBlock },
|
||||
new { Key = "--squirrel-updated", Value = _update ?? defaultBlock },
|
||||
new { Key = "--squirrel-obsolete", Value = _obsolete ?? defaultBlock },
|
||||
new { Key = "--squirrel-uninstall", Value = _uninstall ?? defaultBlock },
|
||||
}.ToDictionary(k => k.Key, v => v.Value, StringComparer.OrdinalIgnoreCase);
|
||||
if (args.Length >= 2 && fastExitlookup.ContainsKey(args[0])) {
|
||||
try {
|
||||
log.Info("Found fast exit hook: " + args[0]);
|
||||
var version = SemanticVersion.Parse(args[1]);
|
||||
fastExitlookup[args[0]](version);
|
||||
log.Info("Completed hook, exiting...");
|
||||
Environment.Exit(0);
|
||||
} catch (Exception ex) {
|
||||
log.Error(ex, $"Error occurred executing user defined Squirrel hook. ({args[0]})");
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// some initial setup/state
|
||||
var myVersion = locator.CurrentlyInstalledVersion;
|
||||
var firstrun = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CLOWD_SQUIRREL_FIRSTRUN"));
|
||||
var restarted = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CLOWD_SQUIRREL_RESTART"));
|
||||
var localPackages = locator.GetLocalPackages();
|
||||
var latestLocal = locator.GetLatestLocalFullPackage();
|
||||
|
||||
// if we've not just been restarted via Squirrel apply, and there is a local update available,
|
||||
// we should install it first.
|
||||
if (latestLocal != null && latestLocal.Version > myVersion) {
|
||||
log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}");
|
||||
if (!restarted && _autoApply) {
|
||||
log.Info("Auto apply is true, so restarting to apply update...");
|
||||
var um = new UpdateManager(log, locator);
|
||||
um.ApplyUpdatesAndRestart(args);
|
||||
}
|
||||
}
|
||||
|
||||
// clean up old versions of the app
|
||||
var pkgPath = locator.PackagesDir;
|
||||
foreach (var package in localPackages) {
|
||||
if (package.Version == latestLocal.Version || package.Version == myVersion) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
log.Info("Removing old package: " + package.OriginalFilename);
|
||||
var p = Path.Combine(pkgPath, package.OriginalFilename);
|
||||
File.Delete(p);
|
||||
} catch (Exception ex) {
|
||||
log.Error(ex, $"Failed to remove old package '{package.OriginalFilename}'");
|
||||
}
|
||||
}
|
||||
|
||||
// run non-exiting user hooks
|
||||
if (firstrun) {
|
||||
try {
|
||||
_firstrun(myVersion);
|
||||
} catch (Exception ex) {
|
||||
log.Error(ex, $"Error occurred executing user defined Squirrel hook. (firstrun)");
|
||||
}
|
||||
}
|
||||
if (restarted) {
|
||||
try {
|
||||
_restarted(myVersion);
|
||||
} catch (Exception ex) {
|
||||
log.Error(ex, $"Error occurred executing user defined Squirrel hook. (restarted)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Squirrel
|
||||
/// <summary> True if there is a local update prepared that requires a call to <see cref="ApplyUpdatesAndRestart(string[])"/> to be applied. </summary>
|
||||
public virtual bool IsUpdatePendingRestart {
|
||||
get {
|
||||
var latestLocal = Locator.GetLatestLocalPackage();
|
||||
var latestLocal = Locator.GetLatestLocalFullPackage();
|
||||
if (latestLocal != null && latestLocal.Version > CurrentVersion)
|
||||
return true;
|
||||
return false;
|
||||
@@ -70,11 +70,16 @@ namespace Squirrel
|
||||
/// <param name="locator">This should usually be left null. Providing an <see cref="ISquirrelLocator" /> allows you to mock up certain application paths.
|
||||
/// For example, if you wanted to test that updates are working in a unit test, you could provide an instance of <see cref="TestSquirrelLocator"/>. </param>
|
||||
public UpdateManager(IUpdateSource source, ILogger logger = null, ISquirrelLocator locator = null)
|
||||
: this(logger, locator)
|
||||
{
|
||||
if (source == null) {
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
Source = source;
|
||||
}
|
||||
|
||||
internal UpdateManager(ILogger logger, ISquirrelLocator locator)
|
||||
{
|
||||
Log = logger ?? NullLogger.Instance;
|
||||
Locator = locator ?? SquirrelLocator.GetDefault(Log);
|
||||
}
|
||||
@@ -97,7 +102,7 @@ namespace Squirrel
|
||||
EnsureInstalled();
|
||||
var installedVer = CurrentVersion;
|
||||
var betaId = Locator.GetOrCreateStagedUserId();
|
||||
var latestLocalFull = Locator.GetLatestLocalPackage();
|
||||
var latestLocalFull = Locator.GetLatestLocalFullPackage();
|
||||
|
||||
Log.Debug("Retrieving latest release feed.");
|
||||
var feed = await Source.GetReleaseFeed(betaId, latestLocalFull?.Identity).ConfigureAwait(false);
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Win32;
|
||||
using NuGet.Packaging;
|
||||
using Squirrel.Compression;
|
||||
@@ -265,6 +266,55 @@ public class WindowsPackTests
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void TestAllApplicationHooks()
|
||||
{
|
||||
Skip.IfNot(SquirrelRuntimeInfo.IsWindows);
|
||||
using var logger = _output.BuildLoggerFor<WindowsPackTests>();
|
||||
using var _1 = Utility.GetTempDirectory(out var releaseDir);
|
||||
using var _2 = Utility.GetTempDirectory(out var installDir);
|
||||
string id = "SquirrelHookTest";
|
||||
var appPath = Path.Combine(installDir, "current", "TestApp.exe");
|
||||
|
||||
// pack v1
|
||||
PackTestApp(id, "1.0.0", "version 1 test", releaseDir, logger);
|
||||
|
||||
// install app
|
||||
var setupPath1 = Path.Combine(releaseDir, $"{id}-Setup-[win-x64].exe");
|
||||
RunNoCoverage(setupPath1, new string[] { "--nocolor", "--verbose", "--installto", installDir },
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop), logger);
|
||||
|
||||
var argsPath = Path.Combine(installDir, "args.txt");
|
||||
Assert.True(File.Exists(argsPath));
|
||||
Assert.Equal("--squirrel-install 1.0.0", File.ReadAllText(argsPath).Trim());
|
||||
|
||||
var firstRun = Path.Combine(installDir, "firstrun");
|
||||
Assert.True(File.Exists(argsPath));
|
||||
Assert.Equal("1.0.0", File.ReadAllText(firstRun).Trim());
|
||||
|
||||
// pack v2
|
||||
PackTestApp(id, "2.0.0", "version 2 test", releaseDir, logger);
|
||||
|
||||
// install v2
|
||||
RunCoveredDotnet(appPath, new string[] { "download", releaseDir }, installDir, logger);
|
||||
RunCoveredDotnet(appPath, new string[] { "apply", releaseDir }, installDir, logger, exitCode: null);
|
||||
|
||||
Thread.Sleep(2000);
|
||||
|
||||
var logFile = Path.Combine(installDir, "Clowd.Squirrel.log");
|
||||
logger.Info("TEST: update log output - " + Environment.NewLine + File.ReadAllText(logFile));
|
||||
|
||||
Assert.Contains("--squirrel-obsolete 1.0.0", File.ReadAllText(argsPath).Trim());
|
||||
Assert.Contains("--squirrel-updated 2.0.0", File.ReadAllText(argsPath).Trim());
|
||||
|
||||
var restartedPath = Path.Combine(installDir, "restarted");
|
||||
Assert.True(File.Exists(restartedPath));
|
||||
Assert.Equal("2.0.0,test,args !!", File.ReadAllText(restartedPath).Trim());
|
||||
|
||||
var updatePath = Path.Combine(installDir, "Update.exe");
|
||||
RunNoCoverage(updatePath, new string[] { "--nocolor", "--silent", "--uninstall" }, Environment.CurrentDirectory, logger);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void TestPackedAppCanDeltaUpdateToLatest()
|
||||
{
|
||||
@@ -280,7 +330,8 @@ public class WindowsPackTests
|
||||
|
||||
// install app
|
||||
var setupPath1 = Path.Combine(releaseDir, $"{id}-Setup-[win-x64].exe");
|
||||
RunNoCoverage(setupPath1, new string[] { "--nocolor", "--silent", "--installto", installDir }, Environment.GetFolderPath(Environment.SpecialFolder.Desktop), logger);
|
||||
RunNoCoverage(setupPath1, new string[] { "--nocolor", "--silent", "--installto", installDir },
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop), logger);
|
||||
|
||||
// check app installed correctly
|
||||
var appPath = Path.Combine(installDir, "current", "TestApp.exe");
|
||||
@@ -335,7 +386,7 @@ public class WindowsPackTests
|
||||
// check new obsoleted/updated hooks have run
|
||||
var argsContentv3 = File.ReadAllText(argsPath).Trim();
|
||||
Assert.Contains("--squirrel-install 1.0.0", argsContentv3);
|
||||
Assert.Contains("--squirrel-obsoleted 1.0.0", argsContentv3);
|
||||
Assert.Contains("--squirrel-obsolete 1.0.0", argsContentv3);
|
||||
Assert.Contains("--squirrel-updated 3.0.0", argsContentv3);
|
||||
logger.Info("TEST: hooks verified");
|
||||
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
using Squirrel;
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Metadata;
|
||||
using Squirrel;
|
||||
using Squirrel.Locators;
|
||||
|
||||
try {
|
||||
if (args.Length >= 1 && args[0].StartsWith("--squirrel")) {
|
||||
// squirrel hooks
|
||||
File.AppendAllText(Path.Combine(AppContext.BaseDirectory, "..", "args.txt"), String.Join(" ", args) + Environment.NewLine);
|
||||
bool shouldExit = false;
|
||||
SquirrelApp.Build()
|
||||
.SetAutoApplyOnStartup(false)
|
||||
.WithFirstRun((v) => {
|
||||
debugFile("firstrun", v.ToString());
|
||||
Console.WriteLine("was first run");
|
||||
shouldExit = true;
|
||||
})
|
||||
.WithRestarted((v) => {
|
||||
debugFile("restarted", v.ToString() + "," + String.Join(",", args));
|
||||
Console.WriteLine("app just restarted");
|
||||
shouldExit = true;
|
||||
})
|
||||
.WithAfterInstallFastCallback((v) => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithBeforeUpdateFastCallback((v) => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithAfterUpdateFastCallback((v) => debugFile("args.txt", String.Join(" ", args)))
|
||||
.WithBeforeUninstallFastCallback((v) => debugFile("args.txt", String.Join(" ", args)))
|
||||
.Run(new ConsoleLogger());
|
||||
|
||||
if (shouldExit) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -50,15 +70,22 @@ try {
|
||||
return -1;
|
||||
}
|
||||
Console.WriteLine("applying...");
|
||||
um.ApplyUpdatesAndExit();
|
||||
um.ApplyUpdatesAndRestart(new[] { "test", "args !!" });
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine("exception: " + ex.ToString());
|
||||
if (Debugger.IsAttached) throw;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Console.WriteLine("Invalid args: " + String.Join(", ", args));
|
||||
return -1;
|
||||
|
||||
void debugFile(string name, string message)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "..", name);
|
||||
File.AppendAllText(path, message + Environment.NewLine);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user