* xtask: Add arguments to `publish` that allow specifying a custom git remote to push to and a custom cargo registry to publish packages to. * xtask/publish: Don't release `xtask` subcrate because it's not meant to be released at all. * xtask/publish: Add status messages to publish so we see what crate is currently being published, too. * xtask/publish: Disable default features on `zellij` because otherwise it tries to pick up the debug builds of the plugins, which aren't part of released version of `zellij utils`. * xtask/publish: Fix handling of custom registry * docs: Add `RELEASE.md` which explains how to simulate a zellij release. * xtask: Apply rustfmt * xtask: Remove `wasm-opt` from build steps because recent versions cause havoc in the release process in GitHub pipelines and it's primary goal is to only reduce binary size. Current rust versions seem to produce very compact wasm binaries themselves, though. * .github: Don't install wasm-opt in workflows
378 lines
12 KiB
Rust
378 lines
12 KiB
Rust
//! Composite pipelines for the build system.
|
|
//!
|
|
//! Defines multiple "pipelines" that run specific individual steps in sequence.
|
|
use crate::flags;
|
|
use crate::{build, clippy, format, test};
|
|
use anyhow::Context;
|
|
use xshell::{cmd, Shell};
|
|
|
|
/// Perform a default build.
|
|
///
|
|
/// Runs the following steps in sequence:
|
|
///
|
|
/// - format
|
|
/// - build
|
|
/// - test
|
|
/// - clippy
|
|
pub fn make(sh: &Shell, flags: flags::Make) -> anyhow::Result<()> {
|
|
let err_context = || format!("failed to run pipeline 'make' with args {flags:?}");
|
|
|
|
if flags.clean {
|
|
crate::cargo()
|
|
.and_then(|cargo| cmd!(sh, "{cargo} clean").run().map_err(anyhow::Error::new))
|
|
.with_context(err_context)?;
|
|
}
|
|
|
|
format::format(sh, flags::Format { check: false })
|
|
.and_then(|_| {
|
|
build::build(
|
|
sh,
|
|
flags::Build {
|
|
release: flags.release,
|
|
no_plugins: false,
|
|
plugins_only: false,
|
|
},
|
|
)
|
|
})
|
|
.and_then(|_| test::test(sh, flags::Test { args: vec![] }))
|
|
.and_then(|_| clippy::clippy(sh, flags::Clippy {}))
|
|
.with_context(err_context)
|
|
}
|
|
|
|
/// Generate a runnable executable.
|
|
///
|
|
/// Runs the following steps in sequence:
|
|
///
|
|
/// - [`build`](build::build) (release, plugins only)
|
|
/// - [`build`](build::build) (release, without plugins)
|
|
/// - [`manpage`](build::manpage)
|
|
/// - Copy the executable to [target file](flags::Install::destination)
|
|
pub fn install(sh: &Shell, flags: flags::Install) -> anyhow::Result<()> {
|
|
let err_context = || format!("failed to run pipeline 'install' with args {flags:?}");
|
|
|
|
// Build and optimize plugins
|
|
build::build(
|
|
sh,
|
|
flags::Build {
|
|
release: true,
|
|
no_plugins: false,
|
|
plugins_only: true,
|
|
},
|
|
)
|
|
.and_then(|_| {
|
|
// Build the main executable
|
|
build::build(
|
|
sh,
|
|
flags::Build {
|
|
release: true,
|
|
no_plugins: true,
|
|
plugins_only: false,
|
|
},
|
|
)
|
|
})
|
|
.and_then(|_| {
|
|
// Generate man page
|
|
build::manpage(sh)
|
|
})
|
|
.with_context(err_context)?;
|
|
|
|
// Copy binary to destination
|
|
let destination = if flags.destination.is_absolute() {
|
|
flags.destination.clone()
|
|
} else {
|
|
std::env::current_dir()
|
|
.context("Can't determine current working directory")?
|
|
.join(&flags.destination)
|
|
};
|
|
sh.change_dir(crate::project_root());
|
|
sh.copy_file("target/release/zellij", &destination)
|
|
.with_context(err_context)
|
|
}
|
|
|
|
/// Run zellij debug build.
|
|
pub fn run(sh: &Shell, flags: flags::Run) -> anyhow::Result<()> {
|
|
let err_context = || format!("failed to run pipeline 'run' with args {flags:?}");
|
|
|
|
let singlepass = flags.singlepass.then_some(["--features", "singlepass"]);
|
|
|
|
if let Some(ref data_dir) = flags.data_dir {
|
|
let data_dir = sh.current_dir().join(data_dir);
|
|
|
|
crate::cargo()
|
|
.and_then(|cargo| {
|
|
cmd!(sh, "{cargo} run")
|
|
.args(["--package", "zellij"])
|
|
.arg("--no-default-features")
|
|
.args(["--features", "disable_automatic_asset_installation"])
|
|
.args(singlepass.iter().flatten())
|
|
.args(["--", "--data-dir", &format!("{}", data_dir.display())])
|
|
.args(&flags.args)
|
|
.run()
|
|
.map_err(anyhow::Error::new)
|
|
})
|
|
.with_context(err_context)
|
|
} else {
|
|
build::build(
|
|
sh,
|
|
flags::Build {
|
|
release: false,
|
|
no_plugins: false,
|
|
plugins_only: true,
|
|
},
|
|
)
|
|
.and_then(|_| crate::cargo())
|
|
.and_then(|cargo| {
|
|
cmd!(sh, "{cargo} run")
|
|
.args(singlepass.iter().flatten())
|
|
.args(["--"])
|
|
.args(&flags.args)
|
|
.run()
|
|
.map_err(anyhow::Error::new)
|
|
})
|
|
.with_context(err_context)
|
|
}
|
|
}
|
|
|
|
/// Bundle all distributable content to `target/dist`.
|
|
///
|
|
/// This includes the optimized zellij executable from the [`install`] pipeline, the man page, the
|
|
/// `.desktop` file and the application logo.
|
|
pub fn dist(sh: &Shell, _flags: flags::Dist) -> anyhow::Result<()> {
|
|
let err_context = || "failed to run pipeline 'dist'";
|
|
|
|
sh.change_dir(crate::project_root());
|
|
if sh.path_exists("target/dist") {
|
|
sh.remove_path("target/dist").with_context(err_context)?;
|
|
}
|
|
sh.create_dir("target/dist")
|
|
.map_err(anyhow::Error::new)
|
|
.and_then(|_| {
|
|
install(
|
|
sh,
|
|
flags::Install {
|
|
destination: crate::project_root().join("./target/dist/zellij"),
|
|
},
|
|
)
|
|
})
|
|
.with_context(err_context)?;
|
|
|
|
sh.create_dir("target/dist/man")
|
|
.and_then(|_| sh.copy_file("assets/man/zellij.1", "target/dist/man/zellij.1"))
|
|
.and_then(|_| sh.copy_file("assets/zellij.desktop", "target/dist/zellij.desktop"))
|
|
.and_then(|_| sh.copy_file("assets/logo.png", "target/dist/logo.png"))
|
|
.with_context(err_context)
|
|
}
|
|
|
|
/// Make a zellij release and publish all crates.
|
|
pub fn publish(sh: &Shell, flags: flags::Publish) -> anyhow::Result<()> {
|
|
let err_context = "failed to publish zellij";
|
|
|
|
// Process flags
|
|
let dry_run = if flags.dry_run {
|
|
Some("--dry-run")
|
|
} else {
|
|
None
|
|
};
|
|
let remote = flags.git_remote.unwrap_or("origin".into());
|
|
let registry = if let Some(registry) = flags.cargo_registry {
|
|
Some(format!(
|
|
"--registry={}",
|
|
registry
|
|
.into_string()
|
|
.map_err(|registry| anyhow::Error::msg(format!(
|
|
"failed to convert '{:?}' to valid registry name",
|
|
registry
|
|
)))
|
|
.context(err_context)?
|
|
))
|
|
} else {
|
|
None
|
|
};
|
|
let registry = registry.as_ref();
|
|
|
|
sh.change_dir(crate::project_root());
|
|
let cargo = crate::cargo().context(err_context)?;
|
|
let project_dir = crate::project_root();
|
|
let manifest = sh
|
|
.read_file(project_dir.join("Cargo.toml"))
|
|
.context(err_context)?
|
|
.parse::<toml::Value>()
|
|
.context(err_context)?;
|
|
// Version of the core crate
|
|
let version = manifest
|
|
.get("package")
|
|
.and_then(|package| package["version"].as_str())
|
|
.context(err_context)?;
|
|
|
|
let mut skip_build = false;
|
|
if cmd!(sh, "git tag -l")
|
|
.read()
|
|
.context(err_context)?
|
|
.contains(version)
|
|
{
|
|
println!();
|
|
println!("Git tag 'v{version}' is already present.");
|
|
println!("If this is a mistake, delete it with: git tag -d 'v{version}'");
|
|
println!("Skip build phase and continue to publish? [y/n]");
|
|
|
|
let stdin = std::io::stdin();
|
|
loop {
|
|
let mut buffer = String::new();
|
|
stdin.read_line(&mut buffer).context(err_context)?;
|
|
match buffer.trim_end() {
|
|
"y" | "Y" => {
|
|
skip_build = true;
|
|
break;
|
|
},
|
|
"n" | "N" => {
|
|
skip_build = false;
|
|
break;
|
|
},
|
|
_ => {
|
|
println!(" --> Unknown input '{buffer}', ignoring...");
|
|
println!();
|
|
println!("Skip build phase and continue to publish? [y/n]");
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
if !skip_build {
|
|
// Clean project
|
|
cmd!(sh, "{cargo} clean").run().context(err_context)?;
|
|
|
|
// Build plugins
|
|
build::build(
|
|
sh,
|
|
flags::Build {
|
|
release: true,
|
|
no_plugins: false,
|
|
plugins_only: true,
|
|
},
|
|
)
|
|
.context(err_context)?;
|
|
|
|
// Update default config
|
|
sh.copy_file(
|
|
project_dir
|
|
.join("zellij-utils")
|
|
.join("assets")
|
|
.join("config")
|
|
.join("default.kdl"),
|
|
project_dir.join("example").join("default.kdl"),
|
|
)
|
|
.context(err_context)?;
|
|
|
|
// Commit changes
|
|
cmd!(sh, "git commit -aem")
|
|
.arg(format!("chore(release): v{}", version))
|
|
.run()
|
|
.context(err_context)?;
|
|
|
|
// Tag release
|
|
cmd!(sh, "git tag --annotate --message")
|
|
.arg(format!("Version {}", version))
|
|
.arg(format!("v{}", version))
|
|
.run()
|
|
.context(err_context)?;
|
|
}
|
|
|
|
let closure = || -> anyhow::Result<()> {
|
|
// Push commit and tag
|
|
if flags.dry_run {
|
|
println!("Skipping push due to dry-run");
|
|
} else {
|
|
cmd!(sh, "git push --atomic {remote} main v{version}")
|
|
.run()
|
|
.context(err_context)?;
|
|
}
|
|
|
|
// Publish all the crates
|
|
for member in crate::WORKSPACE_MEMBERS.iter() {
|
|
if member.contains("plugin") || member.contains("xtask") {
|
|
continue;
|
|
}
|
|
|
|
let _pd = sh.push_dir(project_dir.join(member));
|
|
loop {
|
|
let msg = format!(">> Publishing '{member}'");
|
|
crate::status(&msg);
|
|
println!("{}", msg);
|
|
|
|
let more_args = match *member {
|
|
// This is needed for zellij to pick up the plugins from the assets included in
|
|
// the released zellij-utils binary
|
|
"." => Some("--no-default-features"),
|
|
_ => None,
|
|
};
|
|
|
|
if let Err(err) = cmd!(
|
|
sh,
|
|
"{cargo} publish {registry...} {more_args...} {dry_run...}"
|
|
)
|
|
.run()
|
|
.context(err_context)
|
|
{
|
|
println!();
|
|
println!("Publishing crate '{member}' failed with error:");
|
|
println!("{:?}", err);
|
|
println!();
|
|
println!("Retry? [y/n]");
|
|
|
|
let stdin = std::io::stdin();
|
|
let mut buffer = String::new();
|
|
let retry: bool;
|
|
|
|
loop {
|
|
stdin.read_line(&mut buffer).context(err_context)?;
|
|
|
|
match buffer.trim_end() {
|
|
"y" | "Y" => {
|
|
retry = true;
|
|
break;
|
|
},
|
|
"n" | "N" => {
|
|
retry = false;
|
|
break;
|
|
},
|
|
_ => {
|
|
println!(" --> Unknown input '{buffer}', ignoring...");
|
|
println!();
|
|
println!("Retry? [y/n]");
|
|
},
|
|
}
|
|
}
|
|
|
|
if retry {
|
|
continue;
|
|
} else {
|
|
println!("Aborting publish for crate '{member}'");
|
|
return Err::<(), _>(err);
|
|
}
|
|
} else {
|
|
println!("Waiting for crates.io to catch up...");
|
|
std::thread::sleep(std::time::Duration::from_secs(15));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
println!();
|
|
println!(" +-----------------------------------------------+");
|
|
println!(" | PRAISE THE DEVS, WE HAVE A NEW ZELLIJ RELEASE |");
|
|
println!(" +-----------------------------------------------+");
|
|
Ok(())
|
|
};
|
|
|
|
// We run this in a closure so that a failure in any of the commands doesn't abort the whole
|
|
// program. When dry-running we need to undo the release commit first!
|
|
let result = closure();
|
|
|
|
if flags.dry_run {
|
|
cmd!(sh, "git reset --hard HEAD~1")
|
|
.run()
|
|
.context(err_context)?;
|
|
}
|
|
|
|
result
|
|
}
|