diff --git a/Makefile.toml b/Makefile.toml index 27c4073..c274847 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -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] diff --git a/packager/mac/espanso.rb b/packager/mac/espanso.rb deleted file mode 100644 index 10f0e2b..0000000 --- a/packager/mac/espanso.rb +++ /dev/null @@ -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 diff --git a/packager/win/espanso.cmd b/packager/win/espanso.cmd deleted file mode 100644 index 83de66f..0000000 --- a/packager/win/espanso.cmd +++ /dev/null @@ -1 +0,0 @@ -@"%~dp0espansow.exe" %* \ No newline at end of file diff --git a/packager/win/icon.ico b/packager/win/icon.ico deleted file mode 100644 index 1d1c06c..0000000 Binary files a/packager/win/icon.ico and /dev/null differ diff --git a/packager/win/modpath.iss b/packager/win/modpath.iss deleted file mode 100644 index a48ae7b..0000000 --- a/packager/win/modpath.iss +++ /dev/null @@ -1,219 +0,0 @@ -// ---------------------------------------------------------------------------- -// -// Inno Setup Ver: 5.4.2 -// Script Version: 1.4.2 -// Author: Jared Breland -// 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; diff --git a/scripts/build_windows_installer.rs b/scripts/build_windows_installer.rs new file mode 100644 index 0000000..d033c96 --- /dev/null +++ b/scripts/build_windows_installer.rs @@ -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::() + .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"); + } +} diff --git a/scripts/resources/windows/espanso.cmd b/scripts/resources/windows/espanso.cmd new file mode 100644 index 0000000..8654a10 --- /dev/null +++ b/scripts/resources/windows/espanso.cmd @@ -0,0 +1 @@ +@"%~dp0espansod.exe" %* \ No newline at end of file diff --git a/scripts/resources/windows/icon.ico b/scripts/resources/windows/icon.ico new file mode 100644 index 0000000..4258066 Binary files /dev/null and b/scripts/resources/windows/icon.ico differ diff --git a/packager/win/setupscript.iss b/scripts/resources/windows/setupscript.iss similarity index 56% rename from packager/win/setupscript.iss rename to scripts/resources/windows/setupscript.iss index b177a9d..3b4bfaf 100644 --- a/packager/win/setupscript.iss +++ b/scripts/resources/windows/setupscript.iss @@ -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" \ No newline at end of file +Filename: "{cmd}"; Parameters: "/C ""taskkill /im espansod.exe /f /t" \ No newline at end of file