liveBook · Manning

Build script

As explained in Refactoring to Rust

A Cargo build script is a Rust program run before package compilation to perform build-time configuration or generate source and other artifacts.

A build script is a Rust program that Cargo compiles and runs before compiling a package. Cargo recognizes a file named `build.rs` at the crate root. Because it runs first, the script can read build-time settings and generate files that the package then compiles. section 4.3.1

Generate a greeting at build time

A simple `build.rs` reads `GREET_LANG` and writes a Rust function to `src/greet.rs`. The application uses `include!("greet.rs")` to compile that function and call it. Running with `GREET_LANG=el` generates a Greek greeting; omitting the variable makes this script panic. This is a teaching example, not a recommended internationalization strategy. section 4.3.1

Listing 4.3 Build script writing code with environment variables
use std::fs::File;
use std::io::Write;

fn main() {
  let language = std::env::var("GREET_LANG").unwrap();

  let greeting = match language.as_ref() {  #1
    "en" => "Hello!",
    "es" => "¡Hola!",
    "el" => "γεια σας",
    "de" => "Hallo!",
    x => panic!("Unsupported language code {}", x),
  };

  let rust_code = format!("fn greet() {{
    println!(\"{}\"); }}", greeting);  #2

  let mut file = File::create("src/greet.rs").unwrap();
  file.write_all(rust_code.as_bytes()).unwrap();
}

The script selects a greeting, formats it as Rust code, and writes the generated file. section 4.3.1

The same pattern supports C interoperability. A build script can run `bindgen` over a C header to generate Rust bindings before the Rust library compiles. Put `bindgen` under Cargo's `[build-dependencies]`: the script needs it, but the finished binary does not need it as a normal dependency. section 4.3 section 4.3.2

For the NGINX binding, the build script:

  • Reads `wrapper.h`, supplying paths to the NGINX headers needed for parsing. section 4.3.2
  • Restricts generated declarations to `ngx_`-prefixed items rather than keeping an unnecessarily large API surface. section 4.3.2
  • Writes `nginx.rs` under Cargo's `OUT_DIR`; the Rust source includes it with `include!(concat!(env!("OUT_DIR"), "/nginx.rs"))`. section 4.3.2
Listing 4.5 Build script creating NGINX bindings for Rust
fn main() {
  let nginx_dir = "nginx-1.19.3";

  let bindings = bindgen::builder()
    .header("wrapper.h")  #1
    .clang_args(vec![
      format!("-I{}/src/core", nginx_dir), #2
      format!("-I{}/src/event", nginx_dir),  #2
      format!("-I{}/src/event/modules", nginx_dir),  #2
      format!("-I{}/src/os/unix", nginx_dir),  #2
      format!("-I{}/objs", nginx_dir), #2
      format!("-I{}/src/http", nginx_dir),  #2
      format!("-I{}/src/http/v2", nginx_dir),  #2
      format!("-I{}/src/http/modules", nginx_dir),  #2
    ]) #2
    .generate() #2
    .unwrap(); #2

  bindings
    .write_to_file("nginx.rs")  #3
    .unwrap();
}

The initial NGINX script shows the `bindgen` call and header search paths; the later version filters declarations and writes to `OUT_DIR`. section 4.3.2

How Refactoring to Rust covers it

How deep it goes
Works through creating `build.rs`, reading an environment variable, writing source, and configuring `bindgen`; does not explain Cargo's rebuild rules.
Examples
Uses a `GREET_LANG` greeting generator and a NGINX header-to-Rust binding generator.
What it assumes
Introduces build scripts from a new Cargo crate, then uses C headers for the NGINX integration.
What you can do after
Build it from scratch with Cargo's `build.rs`, or apply it via `bindgen` to generate C bindings.
How it connects
Connects Cargo, environment variables, `include!`, `OUT_DIR`, `bindgen`, and C interoperability.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Refactoring to Rust. The section links point to the parts of the book it draws on.