A few words from Agical

15 second Netlify deploy of a Rust mdBook

I have been enjoying learning some Rust and some Macroquad game development via Olle Wreede’s tutorial (Swedish only so far, sorry!). When I sent some pull requests towards the tutorial I lacked a deployed version attached to the PR. I decided to add that, and my goto for such things is Netlify. However, the tutorial is authored with mdBook, and there doesn’t seem to be a straightforward way to get Netlify to build this.

I figured out a bit of a roundabout way, but before I describe the solution, let’s look at what the problem is about.

  1. I want to solve it with Netlify’s build system, without involving a second CI/CD for it.

  2. Netlify has rustup and cargo pre-installed, but the support seems to be designed to build Rust apps and/or libraries. At least to get caching of the dependencies.

  3. The book in question depends not only on mdbook, but also on mdbook-quiz, mdbook-catppuccin, and mdbook-admonish.

  4. Doing a straight cargo install mdbook mdbook-quiz mdbook-catppuccin takes an awful lot of time. Like 5 minutes. I don’t have that kind of patience waiting for feedback.

I did find a closed issue, with a solution that people seemed happy with, on Netlify about support for mdBook. It was about downloading an mdbook binary. I imagine it is quicker than cargo install, but I would still have to install the plugins and it seemed a bit messy to download individual binaries for each.

My solution was to “trick” Netlify to cache the dependencies by creating a dummy Rust app in the repository, and add the mdBook build tools to build-dependencies of that dummy app. Then to run mdbook build with the directory of these binaries on the PATH.

Here follows the relevant files to make this work. Please note that the repository has the actual book project in a subdirectory, macroquad-introduction-book.

Netlify needs a Rust toolchain file, macroquad-introduction-book/rust-toolchain.toml:

[toolchain]
channel = "stable"

To get the caching of the dependencies I need a cargo configuration in macroquad-introduction-book/Cargo.toml:

# Dummy project to cache book build dependencies with Netlify

[package]
name = "macroquad-introduktion"
version = "0.1.0"
edition = "2021"

[build-dependencies]
mdbook = "*"
mdbook-quiz = "*"
mdbook-catppuccin = "*"
mdbook-admonish = "*"

The cargo command needs either a \[\[bin\]\] configuration (or so it tells me, and I am too noobish to understand how it works) or a main.rs file. I chose the latter, macroquad-introduction-book/src/main.rs:

// Dummy project to cache book build dependencies with Netlify
fn main() {
   println!("Hello, world!");
}

We also need to configure Netlify. I like to do it via netlify.toml, rather than clicking around in the web UI:

[build]
  base = "macroquad-introduction-book"
  publish = "book"
  command = "cargo build --release && export
PATH=\"$PATH:$(pwd)/target/release/build\" && mdbook build"

[context.master]
  command = "cargo build --release && export
PATH=\"$PATH:$(pwd)/target/release/build\" && mdbook build"

[context.branch-deploy]
  command = "cargo build --release && export
PATH=\"$PATH:$(pwd)/target/release/build\" && mdbook build"

In this configuration, the important part is the command. We use cargo build to build the dummy app and force the caching of the mdbook tools. Then we place the build directory on the path before we run mdbook build. This way the mdbook command is found and the plugins will be found as well.

And there it is. With the caching, subsequent builds of the book goes from 5+ minutes to 15 seconds. 15 seconds is an OK wait time for the result of a pull request to build, I think.