feat(core): implement Windows installer packaging
This commit is contained in:
parent
fc70b4fc03
commit
f8b7300d31
|
@ -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]
|
||||
|
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
@"%~dp0espansow.exe" %*
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
|
@ -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;
|
151
scripts/build_windows_installer.rs
Normal file
151
scripts/build_windows_installer.rs
Normal 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");
|
||||
}
|
||||
}
|
1
scripts/resources/windows/espanso.cmd
Normal file
1
scripts/resources/windows/espanso.cmd
Normal file
|
@ -0,0 +1 @@
|
|||
@"%~dp0espansod.exe" %*
|
BIN
scripts/resources/windows/icon.ico
Normal file
BIN
scripts/resources/windows/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
|
@ -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"
|
Loading…
Reference in New Issue
Block a user