* xtask/pipeline: Fix publish task which was previously stuck in an infinite loop after successfully publishing a crate. The error originated in the code only checking for error conditions but not breaking out of the inner infinite loop in case of success. * xtask: Improve publish failure UX by offering the user more actions to choose from when an error occured. * utils/assets: Add generated prost files to assets to make sure they're available at build time and are picked up by all components. It seems we hit some strange bug with the build script where, when running `cargo publish --dry-run` the build script **is not** run before regularly compiling zellij-utils. This shouldn't happen according to the docs, but I cannot explain what's causing it. So we're using this as a workaround for now to make a smooth release. * xtask: Prevent accidental git commit deletion when dry-running a publish. * utils: Add comments to protobuf-related code to explain why these changes were performed. The comments all include a link to an issue comment explaining the situation in greater detail. * xtask: Build protobuf definitions when building any part of the project, similar to how we build the plugins when required. This should ensure that all crates built through `cargo xtask` (which is the officially supported build method) will receive up-to-date protobuf definitions.
405 lines
13 KiB
Rust
405 lines
13 KiB
Rust
//! Composite pipelines for the build system.
|
|
//!
|
|
//! Defines multiple "pipelines" that run specific individual steps in sequence.
|
|
use crate::{build, clippy, format, test};
|
|
use crate::{flags, WorkspaceMember};
|
|
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, mut flags: flags::Run) -> anyhow::Result<()> {
|
|
let err_context =
|
|
|flags: &flags::Run| format!("failed to run pipeline 'run' with args {:?}", flags);
|
|
|
|
let singlepass = flags.singlepass.then_some(["--features", "singlepass"]);
|
|
if flags.quick_run {
|
|
if flags.data_dir.is_some() {
|
|
eprintln!("cannot use '--data-dir' and '--quick-run' at the same time!");
|
|
std::process::exit(1);
|
|
}
|
|
flags.data_dir.replace(crate::asset_dir());
|
|
}
|
|
|
|
let profile = if flags.disable_deps_optimize {
|
|
"dev"
|
|
} else {
|
|
"dev-opt"
|
|
};
|
|
|
|
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(["--profile", profile])
|
|
.args(["--", "--data-dir", &format!("{}", data_dir.display())])
|
|
.args(&flags.args)
|
|
.run()
|
|
.map_err(anyhow::Error::new)
|
|
})
|
|
.with_context(|| err_context(&flags))
|
|
} 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(["--profile", profile])
|
|
.args(["--"])
|
|
.args(&flags.args)
|
|
.run()
|
|
.map_err(anyhow::Error::new)
|
|
})
|
|
.with_context(|| err_context(&flags))
|
|
}
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/// Actions for the user to choose from to resolve publishing errors/conflicts.
|
|
enum UserAction {
|
|
Retry,
|
|
Abort,
|
|
Ignore,
|
|
}
|
|
|
|
/// 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 WorkspaceMember { crate_name, .. } in crate::WORKSPACE_MEMBERS.iter() {
|
|
if crate_name.contains("plugin") || crate_name.contains("xtask") {
|
|
continue;
|
|
}
|
|
|
|
let _pd = sh.push_dir(project_dir.join(crate_name));
|
|
loop {
|
|
let msg = format!(">> Publishing '{crate_name}'");
|
|
crate::status(&msg);
|
|
println!("{}", msg);
|
|
|
|
let more_args = match *crate_name {
|
|
// 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 '{crate_name}' failed with error:");
|
|
println!("{:?}", err);
|
|
println!();
|
|
println!("Please choose what to do: [r]etry/[a]bort/[i]gnore");
|
|
|
|
let stdin = std::io::stdin();
|
|
let action;
|
|
|
|
loop {
|
|
let mut buffer = String::new();
|
|
stdin.read_line(&mut buffer).context(err_context)?;
|
|
match buffer.trim_end() {
|
|
"r" | "R" => {
|
|
action = UserAction::Retry;
|
|
break;
|
|
},
|
|
"a" | "A" => {
|
|
action = UserAction::Abort;
|
|
break;
|
|
},
|
|
"i" | "I" => {
|
|
action = UserAction::Ignore;
|
|
break;
|
|
},
|
|
_ => {
|
|
println!(" --> Unknown input '{buffer}', ignoring...");
|
|
println!();
|
|
println!("Please choose what to do: [r]etry/[a]bort/[i]gnore");
|
|
},
|
|
}
|
|
}
|
|
|
|
match action {
|
|
UserAction::Retry => continue,
|
|
UserAction::Ignore => break,
|
|
UserAction::Abort => {
|
|
eprintln!("Aborting publish for crate '{crate_name}'");
|
|
return Err::<(), _>(err);
|
|
},
|
|
}
|
|
} else {
|
|
// publish successful, continue to next crate
|
|
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 && !skip_build {
|
|
cmd!(sh, "git reset --hard HEAD~1")
|
|
.run()
|
|
.context(err_context)?;
|
|
}
|
|
|
|
result
|
|
}
|