mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
fix: allow building without bundled plugin assets (#5433)
* fix: allow building without bundled plugin assets * add pr
This commit is contained in:
@@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
* chore: remove outdated manpage and some build fixes (https://github.com/zellij-org/zellij/pull/5426)
|
||||
* feat: implement support for the kitty graphics protocol to display images in the terminal (https://github.com/zellij-org/zellij/pull/5428)
|
||||
* fix: only advertise Sixel support if the attached terminal also supports it (https://github.com/zellij-org/zellij/pull/5432)
|
||||
* fix: properly unbundle plugin wasm executables when stripping them (to assist distro packagers) (https://github.com/zellij-org/zellij/pull/5433)
|
||||
|
||||
## [0.44.3] - 2026-05-13
|
||||
* fix(windows): bump windows-sys to 0.59 to align manifest with code, fixing source builds via `cargo install`/`cargo binstall` (https://github.com/zellij-org/zellij/pull/5139)
|
||||
|
||||
@@ -47,6 +47,57 @@ You can see a list of all commands (with supported arguments) with `cargo xtask
|
||||
|
||||
To run `test`, you will need the package `pkg-config` and a version of `openssl`.
|
||||
|
||||
### The cargo target directory
|
||||
|
||||
Debug builds embed the plugins from `<repository>/target/wasm32-wasip1/debug`, which is
|
||||
resolved at compile time. `.cargo/config.toml` therefore pins `build.target-dir` to
|
||||
`target`, so that a `target-dir` configured globally in `~/.cargo/config.toml` does not
|
||||
move the plugins out of reach. Removing that pin breaks debug builds for everyone who
|
||||
configures a shared target directory.
|
||||
|
||||
If you want to share compilation artifacts between projects anyway, symlink the folder
|
||||
instead of pointing cargo elsewhere:
|
||||
|
||||
```sh
|
||||
ln -s /path/to/shared/target /path/to/zellij/target
|
||||
```
|
||||
|
||||
Setting the `CARGO_TARGET_DIR` environment variable takes precedence over
|
||||
`.cargo/config.toml` and will break debug builds for the same reason. Release builds are
|
||||
unaffected, since they embed the plugins from `zellij-utils/assets/plugins`.
|
||||
|
||||
## Packaging Zellij for a distribution
|
||||
|
||||
Builtin plugins (`status-bar`, `tab-bar`, ...) are `.wasm` files that are embedded into
|
||||
the Zellij binary at compile time. Pre-built copies live in `zellij-utils/assets/plugins`
|
||||
so that `cargo install zellij` works without further tooling. Distributions that forbid
|
||||
pre-built binaries in source packages can strip that folder and build the plugins from
|
||||
source instead:
|
||||
|
||||
```sh
|
||||
# 1. build the plugins from source (repeat for every plugin, or use `cargo x build --release`)
|
||||
cargo build --release --target wasm32-wasip1 \
|
||||
-p status-bar -p tab-bar -p compact-bar -p strider -p session-manager \
|
||||
-p configuration -p plugin-manager -p about -p share -p multiple-select \
|
||||
-p layout-manager -p link -p mobile
|
||||
|
||||
# 2. install the resulting artifacts from <target-dir>/wasm32-wasip1/release/*.wasm
|
||||
# into $PREFIX/share/zellij/plugins/
|
||||
|
||||
# 3. build zellij without bundled plugins
|
||||
PREFIX=/usr cargo build --release --bin zellij \
|
||||
--no-default-features --features disable_automatic_asset_installation
|
||||
```
|
||||
|
||||
With the `disable_automatic_asset_installation` feature no plugin is embedded into the
|
||||
binary and `zellij setup --dump-plugins` is unavailable. At runtime builtin plugins are
|
||||
looked up in the configured plugin directory and then in `$PREFIX/share/zellij/plugins`.
|
||||
`zellij setup --check` prints both locations.
|
||||
|
||||
`protoc` is required to regenerate the protobuf definitions in `zellij-utils/assets/prost*`
|
||||
via `cargo x proto`. The `web_server_capability` and `vendored_curl` features are optional
|
||||
and may be dropped with `--no-default-features`.
|
||||
|
||||
## Running the end-to-end tests
|
||||
Zellij includes some end-to-end tests which test the whole application as a black-box from the outside.
|
||||
These tests work by running a docker container which contains the Zellij binary, connecting to it via ssh, sending some commands and comparing the output received against predefined snapshots.
|
||||
|
||||
@@ -126,6 +126,8 @@ pub fn run(sh: &Shell, mut flags: flags::Run) -> anyhow::Result<()> {
|
||||
"disable_automatic_asset_installation web_server_capability"
|
||||
};
|
||||
|
||||
build::ensure_plugin_assets(sh).with_context(|| err_context(&flags))?;
|
||||
|
||||
crate::cargo()
|
||||
.and_then(|cargo| {
|
||||
cmd!(sh, "{cargo} run")
|
||||
|
||||
+65
-14
@@ -116,6 +116,22 @@ pub const FEATURES: &[&str] = &[
|
||||
"disable_automatic_asset_installation",
|
||||
];
|
||||
|
||||
pub const BUILTIN_PLUGIN_NAMES: &[&str] = &[
|
||||
"compact-bar",
|
||||
"status-bar",
|
||||
"tab-bar",
|
||||
"strider",
|
||||
"session-manager",
|
||||
"configuration",
|
||||
"plugin-manager",
|
||||
"about",
|
||||
"share",
|
||||
"multiple-select",
|
||||
"layout-manager",
|
||||
"link",
|
||||
"mobile",
|
||||
];
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use not_wasm::*;
|
||||
|
||||
@@ -133,6 +149,10 @@ mod not_wasm {
|
||||
// `plugins_from_target` feature IS NOT set
|
||||
// - `zellij-utils/../target/wasm32-wasip1/debug`: When building in debug mode AND the
|
||||
// `plugins_from_target` feature IS set
|
||||
//
|
||||
// When the `disable_automatic_asset_installation` feature is set, no plugins are embedded at
|
||||
// all and `ASSET_MAP` is empty. Builtin plugins must then be provided in the plugin directory.
|
||||
#[cfg(not(feature = "disable_automatic_asset_installation"))]
|
||||
macro_rules! add_plugin {
|
||||
($assets:expr, $plugin:literal) => {
|
||||
$assets.insert(
|
||||
@@ -158,23 +178,54 @@ mod not_wasm {
|
||||
lazy_static! {
|
||||
// Zellij asset map
|
||||
pub static ref ASSET_MAP: HashMap<PathBuf, Vec<u8>> = {
|
||||
let mut assets = std::collections::HashMap::new();
|
||||
add_plugin!(assets, "compact-bar.wasm");
|
||||
add_plugin!(assets, "status-bar.wasm");
|
||||
add_plugin!(assets, "tab-bar.wasm");
|
||||
add_plugin!(assets, "strider.wasm");
|
||||
add_plugin!(assets, "session-manager.wasm");
|
||||
add_plugin!(assets, "configuration.wasm");
|
||||
add_plugin!(assets, "plugin-manager.wasm");
|
||||
add_plugin!(assets, "about.wasm");
|
||||
add_plugin!(assets, "share.wasm");
|
||||
add_plugin!(assets, "multiple-select.wasm");
|
||||
add_plugin!(assets, "layout-manager.wasm");
|
||||
add_plugin!(assets, "link.wasm");
|
||||
add_plugin!(assets, "mobile.wasm");
|
||||
#[allow(unused_mut)]
|
||||
let mut assets: HashMap<PathBuf, Vec<u8>> = std::collections::HashMap::new();
|
||||
#[cfg(not(feature = "disable_automatic_asset_installation"))]
|
||||
{
|
||||
add_plugin!(assets, "compact-bar.wasm");
|
||||
add_plugin!(assets, "status-bar.wasm");
|
||||
add_plugin!(assets, "tab-bar.wasm");
|
||||
add_plugin!(assets, "strider.wasm");
|
||||
add_plugin!(assets, "session-manager.wasm");
|
||||
add_plugin!(assets, "configuration.wasm");
|
||||
add_plugin!(assets, "plugin-manager.wasm");
|
||||
add_plugin!(assets, "about.wasm");
|
||||
add_plugin!(assets, "share.wasm");
|
||||
add_plugin!(assets, "multiple-select.wasm");
|
||||
add_plugin!(assets, "layout-manager.wasm");
|
||||
add_plugin!(assets, "link.wasm");
|
||||
add_plugin!(assets, "mobile.wasm");
|
||||
}
|
||||
assets
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(feature = "disable_automatic_asset_installation")))]
|
||||
mod asset_map_test {
|
||||
use super::ASSET_MAP;
|
||||
use crate::consts::BUILTIN_PLUGIN_NAMES;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn asset_map_matches_builtin_plugin_names() {
|
||||
let mut embedded: Vec<String> = ASSET_MAP
|
||||
.keys()
|
||||
.filter_map(|path| {
|
||||
path.file_stem()
|
||||
.map(|stem| stem.to_string_lossy().to_string())
|
||||
})
|
||||
.collect();
|
||||
let mut expected: Vec<String> =
|
||||
BUILTIN_PLUGIN_NAMES.iter().map(|s| s.to_string()).collect();
|
||||
embedded.sort();
|
||||
expected.sort();
|
||||
assert_eq!(embedded, expected);
|
||||
for name in BUILTIN_PLUGIN_NAMES {
|
||||
assert!(ASSET_MAP
|
||||
.contains_key(&PathBuf::from("plugins").join(format!("{}.wasm", name))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -703,9 +703,10 @@ If you're a developer:
|
||||
plugin directory.
|
||||
|
||||
Possible fix for your problem:
|
||||
Run `zellij setup --dump-plugins`, and optionally point it to your
|
||||
'DATA DIR', visible in e.g. the output of `zellij setup --check`. Without
|
||||
further arguments, it will use the default 'DATA DIR'.
|
||||
Place the builtin plugin '.wasm' files in the plugin directory shown above,
|
||||
or in the 'plugins' folder of the system data directory. Both are visible in
|
||||
the output of `zellij setup --check`. This build carries no bundled plugins,
|
||||
so `zellij setup --dump-plugins` cannot provide them.
|
||||
"
|
||||
)]
|
||||
BuiltinPluginMissing {
|
||||
|
||||
@@ -10,6 +10,7 @@ use url::Url;
|
||||
use super::layout::{PluginUserConfiguration, RunPlugin, RunPluginLocation};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::consts::ASSET_MAP;
|
||||
use crate::consts::BUILTIN_PLUGIN_NAMES;
|
||||
pub use crate::data::PluginTag;
|
||||
use crate::errors::prelude::*;
|
||||
|
||||
@@ -57,20 +58,7 @@ impl PluginConfig {
|
||||
}),
|
||||
RunPluginLocation::Zellij(tag) => {
|
||||
let tag = tag.to_string();
|
||||
if tag == "status-bar"
|
||||
|| tag == "tab-bar"
|
||||
|| tag == "compact-bar"
|
||||
|| tag == "strider"
|
||||
|| tag == "session-manager"
|
||||
|| tag == "configuration"
|
||||
|| tag == "plugin-manager"
|
||||
|| tag == "about"
|
||||
|| tag == "share"
|
||||
|| tag == "multiple-select"
|
||||
|| tag == "layout-manager"
|
||||
|| tag == "link"
|
||||
|| tag == "mobile"
|
||||
{
|
||||
if BUILTIN_PLUGIN_NAMES.contains(&tag.as_str()) {
|
||||
Some(PluginConfig {
|
||||
path: PathBuf::from(&tag),
|
||||
_allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
|
||||
@@ -99,13 +87,16 @@ impl PluginConfig {
|
||||
/// binary-internal asset map. Otherwise:
|
||||
///
|
||||
/// Attempts to first resolve the plugin path as an absolute path, then adds a ".wasm"
|
||||
/// extension to the path and resolves that, finally we use the plugin directory joined with
|
||||
/// the path with an appended ".wasm" extension. So if our path is "tab-bar" and the given
|
||||
/// plugin dir is "/home/bob/.zellij/plugins" the lookup chain will be this:
|
||||
/// extension to the path and resolves that, then the plugin directory joined with the path
|
||||
/// with an appended ".wasm" extension, and finally the system data directory joined with
|
||||
/// "plugins" and the same file name. So if our path is "tab-bar" and the given plugin dir is
|
||||
/// "/home/bob/.local/share/zellij/plugins" the lookup chain will be this:
|
||||
///
|
||||
/// ```bash
|
||||
/// /tab-bar
|
||||
/// /tab-bar.wasm
|
||||
/// tab-bar
|
||||
/// tab-bar.wasm
|
||||
/// /home/bob/.local/share/zellij/plugins/tab-bar.wasm
|
||||
/// /usr/share/zellij/plugins/tab-bar.wasm
|
||||
/// ```
|
||||
///
|
||||
pub fn resolve_wasm_bytes(&self, plugin_dir: &Path) -> Result<Vec<u8>> {
|
||||
@@ -113,14 +104,21 @@ impl PluginConfig {
|
||||
|err: std::io::Error, path: &PathBuf| format!("{}: '{}'", err, path.display());
|
||||
|
||||
// Locations we check for valid plugins
|
||||
let paths_arr = [
|
||||
&self.path,
|
||||
&self.path.with_extension("wasm"),
|
||||
&plugin_dir.join(&self.path).with_extension("wasm"),
|
||||
#[allow(unused_mut)]
|
||||
let mut paths: Vec<PathBuf> = vec![
|
||||
self.path.clone(),
|
||||
self.path.with_extension("wasm"),
|
||||
plugin_dir.join(&self.path).with_extension("wasm"),
|
||||
];
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
paths.push(
|
||||
crate::home::system_data_dir()
|
||||
.join("plugins")
|
||||
.join(&self.path)
|
||||
.with_extension("wasm"),
|
||||
);
|
||||
// Throw out dupes, because it's confusing to read that zellij checked the same plugin
|
||||
// location multiple times. Do NOT sort the vector here, because it will break the lookup!
|
||||
let mut paths = paths_arr.to_vec();
|
||||
paths.dedup();
|
||||
|
||||
// This looks weird and usually we would handle errors like this differently, but in this
|
||||
@@ -134,11 +132,11 @@ impl PluginConfig {
|
||||
// from memory, don't bother with the disk.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() {
|
||||
let asset_path = PathBuf::from("plugins").join(path);
|
||||
let asset_path = PathBuf::from("plugins").join(&path);
|
||||
if let Some(bytes) = ASSET_MAP.get(&asset_path) {
|
||||
log::debug!("Loaded plugin '{}' from internal assets", path.display());
|
||||
|
||||
if plugin_dir.join(path).with_extension("wasm").exists() {
|
||||
if plugin_dir.join(&path).with_extension("wasm").exists() {
|
||||
log::info!(
|
||||
"Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored",
|
||||
path.display(),
|
||||
@@ -168,9 +166,7 @@ impl PluginConfig {
|
||||
// Layout requested a builtin plugin that wasn't found
|
||||
let plugin_path = self.path.with_extension("wasm");
|
||||
|
||||
if cfg!(feature = "disable_automatic_asset_installation")
|
||||
&& ASSET_MAP.contains_key(&PathBuf::from("plugins").join(&plugin_path))
|
||||
{
|
||||
if cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin_name() {
|
||||
return Err(ZellijError::BuiltinPluginMissing {
|
||||
plugin_path,
|
||||
plugin_dir: plugin_dir.to_owned(),
|
||||
@@ -192,6 +188,14 @@ impl PluginConfig {
|
||||
pub fn is_builtin(&self) -> bool {
|
||||
matches!(self.location, RunPluginLocation::Zellij(_))
|
||||
}
|
||||
|
||||
pub fn is_builtin_name(&self) -> bool {
|
||||
self.path
|
||||
.file_stem()
|
||||
.and_then(|stem| stem.to_str())
|
||||
.map(|name| BUILTIN_PLUGIN_NAMES.contains(&name))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
|
||||
@@ -402,6 +402,15 @@ impl Setup {
|
||||
}
|
||||
|
||||
if let Some(maybe_path) = &self.dump_plugins {
|
||||
if cfg!(feature = "disable_automatic_asset_installation") {
|
||||
return Err(anyhow!(
|
||||
"This zellij was built without bundled plugins (feature \
|
||||
'disable_automatic_asset_installation'). Builtin plugins are provided by the \
|
||||
distributor of this build and must be placed in the plugin directory, \
|
||||
visible in the output of `zellij setup --check`."
|
||||
))
|
||||
.context("failed to dump plugins");
|
||||
}
|
||||
let data_dir = &opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
|
||||
let dir = match maybe_path {
|
||||
Some(path) => path,
|
||||
@@ -494,6 +503,18 @@ impl Setup {
|
||||
" Create a custom layout if you require this behavior."
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
&mut message,
|
||||
" This zellij was built without bundled plugins."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
&mut message,
|
||||
" Builtin plugins are loaded from the 'PLUGIN DIR' above, or from '{}'.",
|
||||
system_data_dir.join("plugins").display()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
if let Some(layout_dir) = layout_dir {
|
||||
writeln!(&mut message, "[LAYOUT DIR]: \"{}\"", layout_dir.display()).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user