feat(core): implement Windows installer packaging

This commit is contained in:
Federico Terzi 2021-07-17 10:24:25 +02:00
parent fc70b4fc03
commit f8b7300d31
9 changed files with 184 additions and 263 deletions

View File

@ -7,7 +7,7 @@ RELEASE = false
NO_X11 = false
NO_MODULO = false
EXEC_PATH = "target/debug/espanso"
# TODO: flag to enable/disable modulo support
BUILD_ARCH = "x86_64" # TODO: do something with this
[env.release]
DEBUG = false
@ -38,6 +38,14 @@ script_runner = "@rust"
script = { file = "scripts/build_windows_portable.rs" }
dependencies = ["build-windows-resources"]
[tasks.build-windows-installer]
script_runner = "@rust"
script = { file = "scripts/build_windows_installer.rs" }
dependencies = ["build-windows-resources"]
[tasks.build-windows-all]
dependencies = ["build-windows-portable", "build-windows-installer"]
# macOS
[tasks.create-bundle]

View File

@ -1,21 +0,0 @@
# Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!
class Espanso < Formula
desc "{{{app_desc}}}"
homepage "{{{app_url}}}"
url "https://github.com/federico-terzi/espanso/releases/download/v{{{app_version}}}/espanso-mac.tar.gz"
sha256 "{{{release_hash}}}"
version "{{{app_version}}}"
resource "modulo" do
url "https://github.com/federico-terzi/modulo/releases/download/v{{{modulo_version}}}/modulo-mac"
sha256 "{{{modulo_sha}}}"
end
def install
bin.install "espanso"
resource("modulo").stage { bin.install "modulo-mac" => "modulo" }
end
end

View File

@ -1 +0,0 @@
@"%~dp0espansow.exe" %*

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,219 +0,0 @@
// ----------------------------------------------------------------------------
//
// Inno Setup Ver: 5.4.2
// Script Version: 1.4.2
// Author: Jared Breland <jbreland@legroom.net>
// Homepage: http://www.legroom.net/software
// License: GNU Lesser General Public License (LGPL), version 3
// http://www.gnu.org/licenses/lgpl.html
//
// Script Function:
// Allow modification of environmental path directly from Inno Setup installers
//
// Instructions:
// Copy modpath.iss to the same directory as your setup script
//
// Add this statement to your [Setup] section
// ChangesEnvironment=true
//
// Add this statement to your [Tasks] section
// You can change the Description or Flags
// You can change the Name, but it must match the ModPathName setting below
// Name: modifypath; Description: &Add application directory to your environmental path; Flags: unchecked
//
// Add the following to the end of your [Code] section
// ModPathName defines the name of the task defined above
// ModPathType defines whether the 'user' or 'system' path will be modified;
// this will default to user if anything other than system is set
// setArrayLength must specify the total number of dirs to be added
// Result[0] contains first directory, Result[1] contains second, etc.
// const
// ModPathName = 'modifypath';
// ModPathType = 'user';
//
// function ModPathDir(): TArrayOfString;
// begin
// setArrayLength(Result, 1);
// Result[0] := ExpandConstant('{app}');
// end;
// #include "modpath.iss"
// ----------------------------------------------------------------------------
procedure ModPath();
var
oldpath: String;
newpath: String;
updatepath: Boolean;
pathArr: TArrayOfString;
aExecFile: String;
aExecArr: TArrayOfString;
i, d: Integer;
pathdir: TArrayOfString;
regroot: Integer;
regpath: String;
begin
// Get constants from main script and adjust behavior accordingly
// ModPathType MUST be 'system' or 'user'; force 'user' if invalid
if ModPathType = 'system' then begin
regroot := HKEY_LOCAL_MACHINE;
regpath := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
end else begin
regroot := HKEY_CURRENT_USER;
regpath := 'Environment';
end;
// Get array of new directories and act on each individually
pathdir := ModPathDir();
for d := 0 to GetArrayLength(pathdir)-1 do begin
updatepath := true;
// Modify WinNT path
if UsingWinNT() = true then begin
// Get current path, split into an array
RegQueryStringValue(regroot, regpath, 'Path', oldpath);
oldpath := oldpath + ';';
i := 0;
while (Pos(';', oldpath) > 0) do begin
SetArrayLength(pathArr, i+1);
pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1);
oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath));
i := i + 1;
// Check if current directory matches app dir
if pathdir[d] = pathArr[i-1] then begin
// if uninstalling, remove dir from path
if IsUninstaller() = true then begin
continue;
// if installing, flag that dir already exists in path
end else begin
updatepath := false;
end;
end;
// Add current directory to new path
if i = 1 then begin
newpath := pathArr[i-1];
end else begin
newpath := newpath + ';' + pathArr[i-1];
end;
end;
// Append app dir to path if not already included
if (IsUninstaller() = false) AND (updatepath = true) then
newpath := newpath + ';' + pathdir[d];
// Write new path
RegWriteStringValue(regroot, regpath, 'Path', newpath);
// Modify Win9x path
end else begin
// Convert to shortened dirname
pathdir[d] := GetShortName(pathdir[d]);
// If autoexec.bat exists, check if app dir already exists in path
aExecFile := 'C:\AUTOEXEC.BAT';
if FileExists(aExecFile) then begin
LoadStringsFromFile(aExecFile, aExecArr);
for i := 0 to GetArrayLength(aExecArr)-1 do begin
if IsUninstaller() = false then begin
// If app dir already exists while installing, skip add
if (Pos(pathdir[d], aExecArr[i]) > 0) then
updatepath := false;
break;
end else begin
// If app dir exists and = what we originally set, then delete at uninstall
if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then
aExecArr[i] := '';
end;
end;
end;
// If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path
if (IsUninstaller() = false) AND (updatepath = true) then begin
SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True);
// If uninstalling, write the full autoexec out
end else begin
SaveStringsToFile(aExecFile, aExecArr, False);
end;
end;
end;
end;
// Split a string into an array using passed delimeter
procedure MPExplode(var Dest: TArrayOfString; Text: String; Separator: String);
var
i: Integer;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
if Pos(Separator,Text) > 0 then begin
Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1);
Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text));
i := i + 1;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
taskname: String;
begin
taskname := ModPathName;
if CurStep = ssPostInstall then
if WizardIsTaskSelected(taskname) then
ModPath();
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
aSelectedTasks: TArrayOfString;
i: Integer;
taskname: String;
regpath: String;
regstring: String;
appid: String;
begin
// only run during actual uninstall
if CurUninstallStep = usUninstall then begin
// get list of selected tasks saved in registry at install time
appid := '{#emit SetupSetting("AppId")}';
if appid = '' then appid := '{#emit SetupSetting("AppName")}';
regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1');
RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring);
if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring);
// check each task; if matches modpath taskname, trigger patch removal
if regstring <> '' then begin
taskname := ModPathName;
MPExplode(aSelectedTasks, regstring, ',');
if GetArrayLength(aSelectedTasks) > 0 then begin
for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin
if comparetext(aSelectedTasks[i], taskname) = 0 then
ModPath();
end;
end;
end;
end;
end;
function NeedRestart(): Boolean;
var
taskname: String;
begin
taskname := ModPathName;
if IsTaskSelected(taskname) and not UsingWinNT() then begin
Result := True;
end else begin
Result := False;
end;
end;

View File

@ -0,0 +1,151 @@
//! ```cargo
//! [dependencies]
//! glob = "0.3.0"
//! envmnt = "*"
//! fs_extra = "1.2.0"
//! toml = "0.5.8"
//! dunce = "1.0.2"
//! ```
use std::path::PathBuf;
use std::process::Command;
use toml::Value;
const INSTALLER_NAME: &str = "Espanso-Win-Installer";
const TARGET_DIR: &str = "target/windows/installer";
const RESOURCE_DIR: &str = "target/windows/resources";
fn main() {
// Clean the target directory
let _ = std::fs::remove_dir_all(TARGET_DIR);
// Create the target directory
std::fs::create_dir_all(TARGET_DIR).expect("unable to create target directory");
let target_dir = PathBuf::from(TARGET_DIR);
if !target_dir.is_dir() {
panic!("expected target directory, found none");
}
let resources_dir = PathBuf::from(RESOURCE_DIR);
if !resources_dir.is_dir() {
panic!("expected resources dir, found none");
}
// Check InnoSetup
Command::new("iscc").output().expect("Could not find Inno Setup compiler. Please install it from here: http://www.jrsoftware.org/isdl.php");
// Read the InnoSetup template
let makefile_path = envmnt::get_or_panic("CARGO_MAKE_MAKEFILE_PATH");
let makefile_path = PathBuf::from(makefile_path);
let project_path = makefile_path
.parent()
.expect("unable to inferproject directory");
let script_resources_path = project_path
.join("scripts")
.join("resources")
.join("windows");
let template_path = script_resources_path.join("setupscript.iss");
if !template_path.is_file() {
panic!("InnoSetup template not found");
}
let template = std::fs::read_to_string(template_path).expect("unable to read InnoSetup template");
let espanso_toml_path = project_path.join("espanso").join("Cargo.toml");
if !espanso_toml_path.is_file() {
panic!("could not find espanso Cargo.toml file");
}
let espanso_toml_str =
std::fs::read_to_string(espanso_toml_path).expect("unable to read Cargo.toml file");
let espanso_toml = espanso_toml_str
.parse::<Value>()
.expect("unable to parse Cargo.toml");
// Populating template variables
let mut iss_setup = template;
iss_setup = iss_setup.replace(
"{{{app_version}}}",
espanso_toml["package"].as_table().unwrap()["version"]
.as_str()
.unwrap(),
);
iss_setup = iss_setup.replace(
"{{{app_url}}}",
espanso_toml["package"].as_table().unwrap()["homepage"]
.as_str()
.unwrap(),
);
iss_setup = iss_setup.replace(
"{{{app_license}}}",
&dunce::canonicalize(project_path.join("LICENSE"))
.unwrap()
.to_string_lossy()
.to_string(),
);
iss_setup = iss_setup.replace(
"{{{app_icon}}}",
&dunce::canonicalize(script_resources_path.join("icon.ico"))
.unwrap()
.to_string_lossy()
.to_string(),
);
iss_setup = iss_setup.replace(
"{{{cli_helper}}}",
&dunce::canonicalize(script_resources_path.join("espanso.cmd"))
.unwrap()
.to_string_lossy()
.to_string(),
);
iss_setup = iss_setup.replace(
"{{{output_dir}}}",
&dunce::canonicalize(TARGET_DIR)
.unwrap()
.to_string_lossy()
.to_string(),
);
iss_setup = iss_setup.replace(
"{{{output_name}}}",
&format!("{}-{}", INSTALLER_NAME, envmnt::get_or_panic("BUILD_ARCH")),
);
iss_setup = iss_setup.replace(
"{{{executable_path}}}",
&dunce::canonicalize(&format!("{}.exe", envmnt::get_or_panic("EXEC_PATH")))
.unwrap()
.to_string_lossy()
.to_string(),
);
// Generate file includes
let mut include_paths = Vec::new();
for entry in glob::glob(&format!(
r"{}\*.dll",
resources_dir.to_string_lossy().to_string()
))
.expect("unable to glob over DLLs")
{
let entry = entry.expect("unable to unwrap DLL entry");
include_paths.push(format!(
"Source: \"{}\"; DestDir: \"{{app}}\"; Flags: ignoreversion",
dunce::canonicalize(&entry)
.unwrap()
.to_string_lossy()
.to_string()
));
}
iss_setup = iss_setup.replace("{{{dll_include}}}", &include_paths.join("\r\n"));
let iss_setup_path = target_dir.join("setupscript.iss");
std::fs::write(&iss_setup_path, &iss_setup).expect("unable to write InnoSetup setup script");
// Compile the installer
let status = Command::new("iscc")
.arg(&iss_setup_path.to_string_lossy().to_string())
.status()
.expect("unable to invoke InnoSetup compilation");
if !status.success() {
panic!("InnoSetup compilation process returned non-zero exit code");
}
}

View File

@ -0,0 +1 @@
@"%~dp0espansod.exe" %*

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -1,11 +1,11 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "{{{app_name}}}"
#define MyAppName "Espanso"
#define MyAppVersion "{{{app_version}}}"
#define MyAppPublisher "{{{app_publisher}}}"
#define MyAppPublisher "Federico Terzi"
#define MyAppURL "{{{app_url}}}"
#define MyAppExeName "espanso.exe"
#define MyAppExeName "espansod.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
@ -35,31 +35,33 @@ ChangesEnvironment=yes
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "{{{executable_path}}}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{{{executable_path}}}"; DestDir: "{app}"; DestName: "espansod.exe"; Flags: ignoreversion
Source: "{{{app_icon}}}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{{{cli_helper}}}"; DestDir: "{app}"; Flags: ignoreversion
{{{dll_include}}}
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Parameters: "start"; IconFilename: "{app}\icon.ico"
Name: "{userstartup}\espanso"; Filename: "{app}\espanso.exe"; Parameters: "start"; Tasks:StartMenuEntry;
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Parameters: "launcher"; IconFilename: "{app}\icon.ico";
;Name: "{userstartup}\espanso"; Filename: "{app}\espansod.exe"; Parameters: "launcher"; Tasks:StartMenuEntry;
[Tasks]
Name: modifypath; Description: Add espanso to PATH ( recommended );
Name: "StartMenuEntry" ; Description: "Start espanso at Windows startup" ;
;[Tasks]
;Name: modifypath; Description: Add espanso to PATH ( recommended );
;Name: "StartMenuEntry" ; Description: "Start espanso at Windows startup" ;
[Code]
const
ModPathName = 'modifypath';
ModPathType = 'user';
function ModPathDir(): TArrayOfString;
begin
setArrayLength(Result, 1)
Result[0] := ExpandConstant('{app}');
end;
#include "modpath.iss"
; [Code]
; const
; ModPathName = 'modifypath';
; ModPathType = 'user';
; function ModPathDir(): TArrayOfString;
; begin
; setArrayLength(Result, 1)
; Result[0] := ExpandConstant('{app}');
; end;
; #include "modpath.iss"
[Run]
Filename: "{app}\{#MyAppExeName}"; Parameters: "start"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
Filename: "{app}\{#MyAppExeName}"; Parameters: "launcher"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[UninstallRun]
Filename: "{cmd}"; Parameters: "/C ""taskkill /im espanso.exe /f /t"
Filename: "{cmd}"; Parameters: "/C ""taskkill /im espansod.exe /f /t"