diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 44fd6d5c..43f6d4a8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -36,8 +36,6 @@ jobs: run: sudo apt-get install -y --no-install-recommends musl-tools - name: Add musl target run: rustup target add x86_64-unknown-linux-musl - - name: Install wasm-opt - run: sudo apt-get install -y --no-install-recommends binaryen #run: cargo install --debug cargo-make - name: Build asset run: cargo xtask ci e2e --build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4705467..315968f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,14 +63,6 @@ jobs: if: matrix.os == 'ubuntu-latest' run: sudo apt-get install -y --no-install-recommends musl-tools - - name: Install wasm-opt (macos) - if: runner.os == 'macos' - run: brew install binaryen - - - name: Install wasm-opt (ubuntu) - if: matrix.os == 'ubuntu-latest' - run: sudo apt-get install -y --no-install-recommends binaryen - # Workaround for - name: Switch Xcode SDK if: runner.os == 'macos' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65d52849..3543a438 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,9 +43,6 @@ cargo xtask manpage You can see a list of all commands (with supported arguments) with `cargo xtask --help`. For convenience, `xtask` may be shortened to `x`: `cargo x build` etc. -To run `install` or `publish`, you'll need the package `binaryen` in the -version `wasm-opt --version` > 97, for it's command `wasm-opt`. - To run `test`, you will need the package `pkg-config` and a version of `openssl`. ## Running the end-to-end tests diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 00000000..d38e4741 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,144 @@ +# How to release a zellij version + +This document is primarily target at zellij maintainers in need to (prepare to) +release a new zellij version. + + +## Simulating a release + +This section explains how to do a "dry-run" of the release process. This is +useful to check if a release is successful beforehand, i.e. before publishing +it to the world. Because there is no "undo"-button for a real release as +described below, it is recommended to perform a simulated release first. + + +### Requirements + +You only need a publicly accessible Git repository to provide a cargo registry. + + +### High-level concept + +The setup explained below will host a third-party cargo registry software +([ktra](https://github.com/moriturus/ktra)) locally on your PC. In order for +`cargo` to pick this up and be able to work with it, we must perform a few +modifications to the zellij repository and other components. Once setup, we +release a zellij version to this private registry and install zellij from there +to make sure it works as expected. + + +### Step-by-step guide + +1. Create a cargo index repository + 1. Create a new repo on some git forge (GitHub/GitLab/...) + 1. Clone the repo **with HTTPS (not SSH)**, we'll refer to the `https://` + clone-url as `$INDEX_REPO` for the remainder of this text + 1. Add a file named `config.json` with the following content in the root: + ```json + {"dl":"http://localhost:8000/dl","api":"http://localhost:8000"} + ``` + 1. Generate an access token for full repo access, we'll refer to this as + `$TOKEN` for the remained of this text + 1. Create and push a commit with these changes. Provide the following HTTPS + credentials: + 1. Username: Your git-forge username + 1. Password: `$TOKEN` +1. Prepare the zellij repo + 1. `cd` into your local copy of the zellij repository + 1. Add a new cargo registry to `.cargo/config.toml` like this: + ```toml + + [registries] + ktra = { index = "https://$INDEX_REPO" } + ``` + 1. Modify **all** `Cargo.toml` in the zellij repo to retrieve the individual + zellij subcrates from the private registry: + 1. Find all dependencies that look like this: + ```toml + zellij-utils = { path = "../zellij-utils/", version = "XXX" } + ``` + 1. Change them to look like this + ```toml + zellij-utils = { path = "../zellij-utils/", version = "XXX", registry = "ktra" } + ``` + 1. This applies to all zellij subcrates, e.g. `zellij-client`, + `zellij-server`, ... You can ignore the plugins, because these aren't + released as sources. +1. Launch your private registry + 1. Create the file `~/.cargo/config.toml` with the following content: + ``` + [registries.ktra] + index = "https://$INDEX_REPO" + ``` + 1. Install `ktra`, the registry server: `cargo install ktra` + 1. In a separate shell/pane/whatever, navigate to some folder where you + want to store all data for the registry + 1. Create a config file for `ktra` named `ktra.toml` there with the + following content: + ```toml + [index_config] + remote_url = "https://$INDEX_REPO" + https_username = "your-git-username" + https_password = "$TOKEN" + branch = "main" # Or whatever branch name you used + ``` + 1. Launch ktra (with logging to see what happens): `RUST_LOG=debug ktra` + 1. Get a registry token for `ktra` (The details don't really matter, unless + you want to reuse this registry): + ```bash + curl -X POST -H 'Content-Type: application/json' -d '{"password":"PASSWORD"}' http://localhost:8000/ktra/api/v1/new_user/ALICE + ``` + 1. Login to the registry with the token you received as reply to the + previous command: + ```bash + cargo login --registry ktra "KTRA_TOKEN" + ``` +1. **Install safety measures to prevent accidentally performing a real release**: + 1. In your `zellij` repo, remove all configured remotes that allow you to + push/publish directly to the zellij main GitHub repo. Setup a fork of + the main zellij repo instead and configure a remote that allows you to + push/publish to that. Please, this is very important. + 1. Comment out the entire `[registry]` section in `~/.cargo/credentials` to + prevent accidentally pushing a new release to `crates.io`. +1. **Simulate a release** + 1. Go back to the zellij repo, type: + ```bash + cargo x publish --git-remote --cargo-registry ktra + ``` + 1. A prompt will open with the commit message for the release commit. Just + save and close your editor to continue + 1. If all goes well, the release will be done in a few minutes and all the + crates are published to the private `ktra` registry! +1. Testing the release binary + 1. Install zellij from the registry to some local directory like this: + ```bash + $ cargo install --registry ktra --root /tmp zellij + ``` + 1. Execute the binary to see if all went well: + ```bash + $ /tmp/bin/zellij + ``` +1. Cleaning up + 1. Uncomment the `[registry]` section in `~/.cargo/config.toml` + 1. Restore your original git remotes for the zellij repo + 1. Undo your last commit: + ```bash + $ git reset --hard HEAD~1 + ``` + 1. Undo your last commit in the remote zellij repo: + ```bash + $ git push --force + ``` + 1. Delete the release tag: + ```bash + $ git tag -d "vX.Y.Z" + ``` + 1. Delete the release tag in the remote zellij repo + ```bash + $ git push --force --delete "vX.Y.Z" + ``` + +You're done! :tada: + + +## Releasing a new version diff --git a/xtask/src/build.rs b/xtask/src/build.rs index 86ca7cd2..edc0c79e 100644 --- a/xtask/src/build.rs +++ b/xtask/src/build.rs @@ -3,7 +3,6 @@ //! Currently has the following functions: //! //! - [`build`]: Builds general cargo projects (i.e. zellij components) with `cargo build` -//! - [`wasm_opt_plugin`]: Calls `wasm-opt` on all plugins //! - [`manpage`]: Builds the manpage with `mandown` use crate::flags; use anyhow::Context; @@ -48,91 +47,10 @@ pub fn build(sh: &Shell, flags: flags::Build) -> anyhow::Result<()> { base_cmd = base_cmd.arg("--release"); } base_cmd.run().with_context(err_context)?; - - if subcrate.contains("plugins") { - let (_, plugin_name) = subcrate - .rsplit_once('/') - .context("Cannot determine plugin name from '{subcrate}'")?; - - if flags.release { - // Perform wasm-opt on plugin - wasm_opt_plugin(sh, plugin_name).with_context(err_context)?; - } - } } Ok(()) } -/// Call `wasm-opt` on all plugins. -/// -/// Plugins are discovered automatically by scanning the contents of `target/wasm32-wasi/release` -/// for filenames ending with `.wasm`. For this to work the plugins must be built beforehand. -// TODO: Should this panic if there is no plugin found? What should we do when only some plugins -// have been built before? -pub fn wasm_opt_plugin(sh: &Shell, plugin_name: &str) -> anyhow::Result<()> { - let err_context = || format!("failed to run 'wasm-opt' on plugin '{plugin_name}'"); - - let wasm_opt = wasm_opt(sh).with_context(err_context)?; - - let asset_dir = crate::project_root() - .join("zellij-utils") - .join("assets") - .join("plugins"); - sh.create_dir(&asset_dir).with_context(err_context)?; - let _pd = sh.push_dir(asset_dir); - - let plugin = PathBuf::from( - std::env::var_os("CARGO_TARGET_DIR") - .unwrap_or(crate::project_root().join("target").into_os_string()), - ) - .join("wasm32-wasi") - .join("release") - .join(plugin_name) - .with_extension("wasm"); - - if !plugin.is_file() { - return Err(anyhow::anyhow!("No plugin found at '{}'", plugin.display())) - .with_context(err_context); - } - let name = match plugin.file_name().with_context(err_context)?.to_str() { - Some(name) => name, - None => { - return Err(anyhow::anyhow!( - "couldn't read filename containing invalid unicode" - )) - .with_context(err_context) - }, - }; - - // This is a plugin we want to optimize - println!(); - let msg = format!(">> Optimizing plugin '{name}'"); - crate::status(&msg); - println!("{}", msg); - - let input = plugin.as_path(); - cmd!(sh, "{wasm_opt} -O {input} -o {name}") - .run() - .with_context(err_context)?; - - Ok(()) -} - -/// Get the path to a `wasm-opt` executable. -/// -/// If the executable isn't found, an error is returned instead. -// TODO: Offer the user to install latest wasm-opt on path? -fn wasm_opt(_sh: &Shell) -> anyhow::Result { - match which::which("wasm-opt") { - Ok(path) => Ok(path), - Err(e) => { - println!("!! 'wasm-opt' wasn't found but is needed for this build step."); - println!("!! Please install it from here: https://github.com/WebAssembly/binaryen"); - Err(e).context("couldn't find 'wasm-opt' executable") - }, - } -} - /// Build the manpage with `mandown`. // mkdir -p ${root_dir}/assets/man // mandown ${root_dir}/docs/MANPAGE.md 1 > ${root_dir}/assets/man/zellij.1 diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs index 2aed0013..4630a893 100644 --- a/xtask/src/flags.rs +++ b/xtask/src/flags.rs @@ -38,6 +38,10 @@ xflags::xflags! { cmd publish { /// Perform a dry-run (don't push/publish anything) optional --dry-run + /// Push commit to custom git remote + optional --git-remote remote: OsString + /// Publish crates to custom registry + optional --cargo-registry registry: OsString } /// Package zellij for distribution (result found in ./target/dist) @@ -151,6 +155,8 @@ pub struct Manpage; #[derive(Debug)] pub struct Publish { pub dry_run: bool, + pub git_remote: Option, + pub cargo_registry: Option, } #[derive(Debug)] @@ -175,7 +181,6 @@ pub struct Run { pub args: Vec, pub data_dir: Option, - pub singlepass: bool, } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c8171705..209caa5e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -5,36 +5,6 @@ //! xtask install` for installation of rust-analyzer server and client. //! //! This binary is integrated into the `cargo` command line by using an alias in `.cargo/config`. -// Current default "flow": -// - format-flow: `cargo fmt` -// - format-toml-conditioned-flow: ?? -// - build: `cargo build` -// - test: `cargo test` -// - clippy: `cargo clippy --all-targets --all-features -- --deny warnings $@` -// -// # Install flow: -// - build-plugins-release: `cargo build --release ...` -// - wasm-opt-plugins: `wasm-opt ...` -// - build-release: `cargo build --release` -// - install-mandown: `cargo install mandown` -// - manpage: | -// mkdir -p ${root_dir}/assets/man -// mandown ${root_dir}/docs/MANPAGE.md 1 > ${root_dir}/assets/man/zellij.1 -// - install: `cp target/release/zellij "$1"` -// -// # Release flow: -// - workspace: cargo make --profile development -- release -// -// # Publish flow: -// - update-default-config: -// - build-plugins-release: `cargo build --release ...` -// - wasm-opt-plugins: `wasm-opt ...` -// - release-commit: -// - commit-all: `git commit -aem "chore(release): v${CRATE_VERSION}"` -// - tag-release: `git tag --annotate --message "Version ${CRATE_VERSION}" -// "v${CRATE_VERSION}"` -// - `git push --atomic origin main "v${CRATE_VERSION}"` -// - publish-zellij: `cargo publish [tile, client, server, utils, tile-utils, zellij]` mod build; mod ci; diff --git a/xtask/src/pipelines.rs b/xtask/src/pipelines.rs index a615c097..ad21bffe 100644 --- a/xtask/src/pipelines.rs +++ b/xtask/src/pipelines.rs @@ -44,7 +44,6 @@ pub fn make(sh: &Shell, flags: flags::Make) -> anyhow::Result<()> { /// Runs the following steps in sequence: /// /// - [`build`](build::build) (release, plugins only) -/// - [`wasm_opt_plugins`](build::wasm_opt_plugins) /// - [`build`](build::build) (release, without plugins) /// - [`manpage`](build::manpage) /// - Copy the executable to [target file](flags::Install::destination) @@ -168,12 +167,30 @@ pub fn dist(sh: &Shell, _flags: flags::Dist) -> anyhow::Result<()> { pub fn publish(sh: &Shell, flags: flags::Publish) -> anyhow::Result<()> { let err_context = "failed to publish zellij"; - sh.change_dir(crate::project_root()); + // 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 @@ -265,22 +282,36 @@ pub fn publish(sh: &Shell, flags: flags::Publish) -> anyhow::Result<()> { if flags.dry_run { println!("Skipping push due to dry-run"); } else { - cmd!(sh, "git push --atomic origin main v{version}") + 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") { + if member.contains("plugin") || member.contains("xtask") { continue; } let _pd = sh.push_dir(project_dir.join(member)); loop { - if let Err(err) = cmd!(sh, "{cargo} publish {dry_run...}") - .run() - .context(err_context) + 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:"); @@ -325,6 +356,11 @@ pub fn publish(sh: &Shell, flags: flags::Publish) -> anyhow::Result<()> { } } } + + println!(); + println!(" +-----------------------------------------------+"); + println!(" | PRAISE THE DEVS, WE HAVE A NEW ZELLIJ RELEASE |"); + println!(" +-----------------------------------------------+"); Ok(()) };