diff --git a/Makefile.toml b/Makefile.toml index 11190d1..3df1d92 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -19,109 +19,24 @@ EXEC_PATH = "target/release/espanso" # This one was written in Rust instead of bash because it has to run on Windows as well [tasks.build-binary] script_runner = "@rust" -script = ''' -//! ```cargo -//! [dependencies] -//! envmnt = "*" -//! ``` - -use std::process::Command; - -#[derive(Debug, PartialEq)] -enum Profile { - Debug, - Release, -} - -fn main() { - let profile = if envmnt::get_or_panic("RELEASE") == "true" { - Profile::Release - } else { - Profile::Debug - }; - - println!("Using profile: {:?}", profile); - - let wayland = envmnt::get_or("NO_X11", "false") == "true"; - if wayland { - println!("Using Wayland feature"); - } - - let avoid_modulo = envmnt::get_or("NO_MODULO", "false") == "true"; - if avoid_modulo { - println!("Skipping modulo feature"); - } - - let mut args = Vec::new(); - args.push("build"); - - if profile == Profile::Release { - args.push("--release"); - } - - args.push("--manifest-path"); - args.push("espanso/Cargo.toml"); - - let mut features = Vec::new(); - if wayland { - features.push("wayland"); - } - if !avoid_modulo { - features.push("modulo"); - } - - let features_flag = features.join(" "); - - args.push("-p"); - args.push("espanso"); - args.push("--no-default-features"); - args.push("--features"); - args.push(&features_flag); - - println!("Calling with args: {:?}", args); - - let mut cmd = Command::new("cargo"); - cmd.args(&args); - - // Remove cargo/rust-specific env variables, as otherwise they mess up the - // nested cargo build call. - let all_vars = envmnt::vars(); - for (key, _) in all_vars { - if key.starts_with("CARGO") || key.starts_with("RUST") { - //println!("Removing {}", key); - cmd.env_remove(key); - } - } - -let mut handle = cmd.spawn().expect("cargo build failed"); - handle.wait(); -} -''' +script = { file = "scripts/build_binary.rs" } [tasks.run-binary] command = "${EXEC_PATH}" args = ["${@}"] dependencies = ["build-binary"] +# Windows + +[tasks.build-windows-portable] +script_runner = "@rust" +script = { file = "scripts/build_windows_portable.rs" } +dependencies = ["build-binary"] + +# macOS + [tasks.create-bundle] -script = ''' -TARGET_DIR=target/mac/Espanso.app - -rm -Rf $TARGET_DIR - -VERSION=$(cat espanso/Cargo.toml | grep version | head -1 | awk -F '"' '{ print $2 }') - -mkdir -p $TARGET_DIR/Contents -mkdir -p $TARGET_DIR/Contents/MacOS -mkdir -p $TARGET_DIR/Contents/Resources - -sed -e "s/VERSION/$VERSION/" espanso/src/res/macos/Info.plist > $TARGET_DIR/Contents/Info.plist - -/bin/echo "APPL????" > $TARGET_DIR/Contents/PkgInfo - -cp -f espanso/src/res/macos/icon.icns $TARGET_DIR/Contents/Resources/icon.icns -cp -f $EXEC_PATH $TARGET_DIR/Contents/MacOS/espanso -''' +script = { file = "scripts/create_bundle.sh" } dependencies=["build-binary"] [tasks.run-bundle] @@ -129,36 +44,10 @@ command="target/mac/Espanso.app/Contents/MacOS/espanso" args=["${@}"] dependencies=["create-bundle"] +# Linux + [tasks.create-app-image] -script = ''' -#!/usr/bin/env bash -set -e -TOOL_DIR=$(pwd)/target/linux/linuxdeploy -TARGET_DIR=$(pwd)/target/linux/AppImage -BUILD_DIR=$TARGET_DIR/build -OUTPUT_DIR=$TARGET_DIR/out -BASE_DIR=$(pwd) - -mkdir -p $TOOL_DIR - -if ls $TOOL_DIR/*.AppImage 1> /dev/null 2>&1; then - echo "Skipping download of linuxdeploy" -else - echo "Downloading linuxdeploy tool" - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -P "$TOOL_DIR" - chmod +x $TOOL_DIR/linuxdeploy*.AppImage -fi - -rm -Rf "$TARGET_DIR" -mkdir -p $OUTPUT_DIR -mkdir -p $BUILD_DIR - -echo Building AppImage into $OUTPUT_DIR -pushd $OUTPUT_DIR -$TOOL_DIR/linuxdeploy*.AppImage --appimage-extract-and-run -e "$BASE_DIR/$EXEC_PATH" -d "$BASE_DIR/espanso/src/res/linux/espanso.desktop" -i "$BASE_DIR/espanso/src/res/linux/icon.png" --appdir $BUILD_DIR --output appimage -chmod +x ./Espanso*.AppImage -popd -''' +script = { file = "scripts/create_app_image.sh" } dependencies=["build-binary"] [tasks.run-app-image] diff --git a/scripts/build_binary.rs b/scripts/build_binary.rs new file mode 100644 index 0000000..4ba62d4 --- /dev/null +++ b/scripts/build_binary.rs @@ -0,0 +1,76 @@ +//! ```cargo +//! [dependencies] +//! envmnt = "*" +//! ``` + +use std::process::Command; + +#[derive(Debug, PartialEq)] +enum Profile { + Debug, + Release, +} + +fn main() { + let profile = if envmnt::get_or_panic("RELEASE") == "true" { + Profile::Release + } else { + Profile::Debug + }; + + println!("Using profile: {:?}", profile); + + let wayland = envmnt::get_or("NO_X11", "false") == "true"; + if wayland { + println!("Using Wayland feature"); + } + + let avoid_modulo = envmnt::get_or("NO_MODULO", "false") == "true"; + if avoid_modulo { + println!("Skipping modulo feature"); + } + + let mut args = Vec::new(); + args.push("build"); + + if profile == Profile::Release { + args.push("--release"); + } + + args.push("--manifest-path"); + args.push("espanso/Cargo.toml"); + + let mut features = Vec::new(); + if wayland { + features.push("wayland"); + } + if !avoid_modulo { + features.push("modulo"); + } + + let features_flag = features.join(" "); + + args.push("-p"); + args.push("espanso"); + args.push("--no-default-features"); + args.push("--features"); + args.push(&features_flag); + + println!("Calling with args: {:?}", args); + + let mut cmd = Command::new("cargo"); + cmd.args(&args); + + // Remove cargo/rust-specific env variables, as otherwise they mess up the + // nested cargo build call. + let all_vars = envmnt::vars(); + for (key, _) in all_vars { + if key.starts_with("CARGO") || key.starts_with("RUST") { + //println!("Removing {}", key); + cmd.env_remove(key); + } + } + + let mut handle = cmd.spawn().expect("cargo build failed"); + handle.wait(); +} diff --git a/scripts/build_windows_portable.rs b/scripts/build_windows_portable.rs new file mode 100644 index 0000000..28ae17c --- /dev/null +++ b/scripts/build_windows_portable.rs @@ -0,0 +1,108 @@ +//! ```cargo +//! [dependencies] +//! cc = "1.0.66" +//! glob = "0.3.0" +//! envmnt = "*" +//! ``` + +use std::process::Command; +use std::path::PathBuf; + +const TARGET_DIR: &str = "target/windows/portable"; + +fn main() { + // Clean the target directory + 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"); + } + + // We first need to find all the DLLs to redistribute with the binary + // These are found inside the MSVC compiler directory, usually in a place like: + // C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\14.28.29910\x64\Microsoft.VC142.CRT + // and they include files like vcruntime140.dll and msvcp140.dll + + // First, we try to find the directory containing the various versions: + // C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\ + let tool = cc::windows_registry::find_tool("msvc", "msbuild") + .expect("unable to locate MSVC compiler, did you install Visual Studio?"); + let mut versions_dir = None; + let mut current_root = tool.path(); + while let Some(parent) = current_root.parent() { + let target = parent.join("VC").join("Redist").join("MSVC"); + if target.exists() { + versions_dir = Some(target); + break; + } + current_root = parent; + } + let versions_dir = versions_dir.expect( + r"unable to find path: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC", + ); + + // Then we try to find a suitable directory containing the required DLLs + // C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Redist\MSVC\14.28.29910\x64\Microsoft.VC142.CRT + let glob_pattern = format!( + r"{}\*\x64\Microsoft.*\vcruntime140_1.dll", + versions_dir.to_string_lossy().to_string() + ); + println!("glob_pattern: {}", glob_pattern); + let mut target_file = glob::glob(&glob_pattern) + .expect("failed to read glob pattern") + .next() + .expect("unable to find vcruntime140_1.dll file") + .expect("unable to extract path of vcruntime140_1.dll file"); + + // Copy the DLLs in the target directory + let parent_dir = target_file.parent().expect("unable to obtain directory containing DLLs"); + for entry in glob::glob(&format!(r"{}\*.dll", parent_dir.to_string_lossy().to_string())).expect("unable to glob over DLLs") { + let entry = entry.expect("unable to unwrap DLL entry"); + let filename = entry.file_name().expect("unable to obtain filename"); + std::fs::copy(&entry, target_dir.join(filename)); + } + + // Copy the executable + let exec_path = envmnt::get_or_panic("EXEC_PATH"); + let exec_file = PathBuf::from(&format!("{}.exe", exec_path)); + if !exec_file.is_file() { + panic!("expected espanso binary, found none in {:?}", exec_file); + } + std::fs::copy(exec_file, target_dir.join("espansod.exe")); + + // Create the CLI wrapper + std::fs::write(target_dir.join("espanso.cmd"), r#"@"%~dp0espansod.exe" %*"#).unwrap(); + + // Create the launcher + std::fs::write(target_dir.join("START_ESPANSO.bat"), r#"start espansod.exe launcher"#).unwrap(); + + // Create the necessary folders + std::fs::create_dir_all(target_dir.join(".espanso")).expect("unable to create data directory"); + std::fs::create_dir_all(target_dir.join(".espanso-runtime")).expect("unable to create runtime directory"); + + std::fs::write(target_dir.join("README.txt"), r##"Welcome to Espanso (Portable edition)! + +To start espanso, you can double click on "START_ESPANSO.bat" + +After the first run, you will see some files in the ".espanso" directory. +This is where your snippets and configurations should be defined. + +For more information, please visit the official documentation: +https://espanso.org/docs/ + +IMPORTANT: Don't delete any file or directory, otherwise espanso won't work. + + +FOR ADVANCED USERS: + +Espanso also offers a rich CLI interface. To start it from the terminal, cd into the +current directory and run "espanso start". You can also run "espanso --help" for more information. + +You might have noticed that the directory contains both an "espansod.exe" and an "espanso.cmd" file. +You should generally avoid running "espansod.exe" directly, and instead use the "espanso.cmd" +wrapper (which can simply be run as "espanso" in the terminal). This is needed to correctly manage +STD console handles on Windows. + "##).unwrap(); +} diff --git a/scripts/create_app_image.sh b/scripts/create_app_image.sh new file mode 100644 index 0000000..4779363 --- /dev/null +++ b/scripts/create_app_image.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e +TOOL_DIR=$(pwd)/target/linux/linuxdeploy +TARGET_DIR=$(pwd)/target/linux/AppImage +BUILD_DIR=$TARGET_DIR/build +OUTPUT_DIR=$TARGET_DIR/out +BASE_DIR=$(pwd) + +mkdir -p $TOOL_DIR + +if ls $TOOL_DIR/*.AppImage 1> /dev/null 2>&1; then + echo "Skipping download of linuxdeploy" +else + echo "Downloading linuxdeploy tool" + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -P "$TOOL_DIR" + chmod +x $TOOL_DIR/linuxdeploy*.AppImage +fi + +rm -Rf "$TARGET_DIR" +mkdir -p $OUTPUT_DIR +mkdir -p $BUILD_DIR + +echo Building AppImage into $OUTPUT_DIR +pushd $OUTPUT_DIR +$TOOL_DIR/linuxdeploy*.AppImage --appimage-extract-and-run -e "$BASE_DIR/$EXEC_PATH" -d "$BASE_DIR/espanso/src/res/linux/espanso.desktop" -i "$BASE_DIR/espanso/src/res/linux/icon.png" --appdir $BUILD_DIR --output appimage +chmod +x ./Espanso*.AppImage +popd \ No newline at end of file diff --git a/scripts/create_bundle.sh b/scripts/create_bundle.sh new file mode 100644 index 0000000..c81d02c --- /dev/null +++ b/scripts/create_bundle.sh @@ -0,0 +1,16 @@ +TARGET_DIR=target/mac/Espanso.app + +rm -Rf $TARGET_DIR + +VERSION=$(cat espanso/Cargo.toml | grep version | head -1 | awk -F '"' '{ print $2 }') + +mkdir -p $TARGET_DIR/Contents +mkdir -p $TARGET_DIR/Contents/MacOS +mkdir -p $TARGET_DIR/Contents/Resources + +sed -e "s/VERSION/$VERSION/" espanso/src/res/macos/Info.plist > $TARGET_DIR/Contents/Info.plist + +/bin/echo "APPL????" > $TARGET_DIR/Contents/PkgInfo + +cp -f espanso/src/res/macos/icon.icns $TARGET_DIR/Contents/Resources/icon.icns +cp -f $EXEC_PATH $TARGET_DIR/Contents/MacOS/espanso \ No newline at end of file