Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
xtask = "run --manifest-path=crates/xtask/Cargo.toml --"
24 changes: 18 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`" ; \
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions crates/xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions crates/xtask/README.md
Original file line number Diff line number Diff line change
@@ -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.
122 changes: 122 additions & 0 deletions crates/xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<T> = std::result::Result<T, Box<dyn Error>>;

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::<Vec<_>>().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(())
}
Loading