diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..a4ce18e9e1 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --manifest-path=crates/xtask/Cargo.toml --" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03083d66b6..571ea27011 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -134,6 +134,21 @@ The main test harness is described in the [testsuite documentation](tests/testsu - `cargo clippy --workspace --all-targets --no-deps -- -D warnings` — This makes sure that there are no clippy warnings. - `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --document-private-items --no-deps` — This verifies that there aren't any rustdoc warnings. - `cargo fmt --check` — Verifies that everything is formatted correctly. +- `cargo +stable semver-checks` — Verifies that no SemVer breaking changes have been made. You must install [`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks) first. + +To help simplify running all these commands, you can run the following cargo command: + +```sh +cargo xtask test-all +``` + +It is useful to run all tests before submitting a PR. While developing I recommend to run some subset of that command based on what you are working on. There are individual arguments for each one. For example: + +```sh +cargo xtask test-workspace clippy doc eslint fmt gui semver-checks +``` + +While developing, remove any of those arguments that are not relevant to what you are changing, or are really slow. ## Making a pull-request @@ -208,12 +223,9 @@ Instructions for mdBook maintainers to publish a new release: 1. Create a PR to update the version and update the CHANGELOG: 1. Update the version in `Cargo.toml` - 2. Run `cargo test` to verify that everything is passing, and to update `Cargo.lock`. - 3. Double-check for any SemVer breaking changes. - Try [`cargo-semver-checks`](https://crates.io/crates/cargo-semver-checks), though beware that the current version of mdBook isn't properly adhering to SemVer due to the lack of `#[non_exhaustive]` and other issues. See https://github.com/rust-lang/mdBook/issues/1835. - 4. Update `CHANGELOG.md` with any changes that users may be interested in. - 5. Update `continuous-integration.md` to update the version number for the installation instructions. - 6. Commit the changes, and open a PR. + 2. Run `cargo xtask test-all` to verify that everything is passing, and to update `Cargo.lock`. + 3. Update `CHANGELOG.md` with any changes that users may be interested in. + 4. Commit the changes, and open a PR. 2. After the PR has been merged, create a release in GitHub. This can either be done in the GitHub web UI, or on the command-line: ```bash MDBOOK_VERS="`cargo read-manifest | jq -r .version`" ; \ diff --git a/Cargo.lock b/Cargo.lock index e913aa3040..963a21ed61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2514,6 +2514,10 @@ dependencies = [ "markup5ever 0.11.0", ] +[[package]] +name = "xtask" +version = "0.0.0" + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/crates/xtask/Cargo.toml b/crates/xtask/Cargo.toml new file mode 100644 index 0000000000..fa10586d12 --- /dev/null +++ b/crates/xtask/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "xtask" +publish = false +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/crates/xtask/README.md b/crates/xtask/README.md new file mode 100644 index 0000000000..338bedaf22 --- /dev/null +++ b/crates/xtask/README.md @@ -0,0 +1,4 @@ +# xtask + +This is a CLI utility for running development commands for mdbook. +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for how to use this. diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs new file mode 100644 index 0000000000..6bd891cdb8 --- /dev/null +++ b/crates/xtask/src/main.rs @@ -0,0 +1,122 @@ +//! Helper for local development. + +use std::collections::BTreeMap; +use std::error::Error; +use std::process::Command; +use std::process::exit; + +type Result = std::result::Result>; + +fn main() -> Result<()> { + macro_rules! commands { + ($($name:literal => $func:ident),* $(,)?) => { + [$(($name, $func as fn() -> Result<()>)),*] + }; + } + + let cmds: BTreeMap<&'static str, fn() -> Result<()>> = commands! { + "test-all" => test_all, + "test-workspace" => test_workspace, + "clippy" => clippy, + "doc" => doc, + "fmt" => fmt, + "semver-checks" => semver_checks, + "eslint" => eslint, + "gui" => gui, + } + .into_iter() + .collect(); + let keys = cmds.keys().copied().collect::>().join(", "); + let mut args = std::env::args().skip(1).peekable(); + if args.peek().is_none() { + eprintln!("error: specify a command (valid options: {keys})"); + exit(1); + } + for arg in args { + if let Some(cmd_fn) = cmds.get(arg.as_str()) { + cmd_fn()?; + } else if matches!(arg.as_str(), "-h" | "--help") { + println!("valid options: {keys}"); + exit(0) + } else { + eprintln!("error: unknown command `{arg}` (valid options: {keys}"); + exit(1); + } + } + println!("all tests passed!"); + Ok(()) +} + +fn test_all() -> Result<()> { + test_workspace()?; + clippy()?; + doc()?; + fmt()?; + semver_checks()?; + eslint()?; + gui()?; + Ok(()) +} + +fn cargo(args: &str, cb: &dyn Fn(&mut Command)) -> Result<()> { + println!("Running `cargo {args}`"); + let mut cmd = Command::new("cargo"); + cmd.args(args.split_whitespace()); + cb(&mut cmd); + let status = cmd.status().expect("cargo should be installed"); + if !status.success() { + return Err("command `cargo {args}` failed".into()); + } + Ok(()) +} + +fn test_workspace() -> Result<()> { + cargo("test --workspace", &|_| {})?; + cargo("test --workspace --no-default-features", &|_| {})?; + Ok(()) +} + +fn clippy() -> Result<()> { + cargo( + "clippy --workspace --all-targets --no-deps -- -D warnings", + &|_| {}, + )?; + Ok(()) +} + +fn doc() -> Result<()> { + cargo( + "doc --workspace --document-private-items --no-deps", + &|cmd| { + cmd.env("RUSTDOCFLAGS", "-D warnings"); + }, + )?; + Ok(()) +} + +fn fmt() -> Result<()> { + cargo("fmt --check", &|_| {})?; + Ok(()) +} + +fn semver_checks() -> Result<()> { + cargo("+stable semver-checks --workspace", &|_| {})?; + Ok(()) +} + +fn gui() -> Result<()> { + cargo("test --test gui", &|_| {})?; + Ok(()) +} + +fn eslint() -> Result<()> { + println!("Running `npm run lint`"); + let status = Command::new("npm") + .args(["run", "lint"]) + .status() + .expect("npm should be installed"); + if !status.success() { + return Err("eslint failed".into()); + } + Ok(()) +}