feat(core): create packager for Windows portable mode and move scripts to separate files

This commit is contained in:
Federico Terzi 2021-07-16 22:14:05 +02:00
parent fd2f385858
commit a4b9b30cdf
5 changed files with 241 additions and 125 deletions

View File

@ -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 # This one was written in Rust instead of bash because it has to run on Windows as well
[tasks.build-binary] [tasks.build-binary]
script_runner = "@rust" script_runner = "@rust"
script = ''' script = { file = "scripts/build_binary.rs" }
//! ```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();
}
'''
[tasks.run-binary] [tasks.run-binary]
command = "${EXEC_PATH}" command = "${EXEC_PATH}"
args = ["${@}"] args = ["${@}"]
dependencies = ["build-binary"] 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] [tasks.create-bundle]
script = ''' script = { file = "scripts/create_bundle.sh" }
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
'''
dependencies=["build-binary"] dependencies=["build-binary"]
[tasks.run-bundle] [tasks.run-bundle]
@ -129,36 +44,10 @@ command="target/mac/Espanso.app/Contents/MacOS/espanso"
args=["${@}"] args=["${@}"]
dependencies=["create-bundle"] dependencies=["create-bundle"]
# Linux
[tasks.create-app-image] [tasks.create-app-image]
script = ''' script = { file = "scripts/create_app_image.sh" }
#!/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
'''
dependencies=["build-binary"] dependencies=["build-binary"]
[tasks.run-app-image] [tasks.run-app-image]

76
scripts/build_binary.rs Normal file
View File

@ -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();
}

View File

@ -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();
}

View File

@ -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

16
scripts/create_bundle.sh Normal file
View File

@ -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