1## Build Script Examples 2 3The following sections illustrate some examples of writing build scripts. 4 5Some common build script functionality can be found via crates on [crates.io]. 6Check out the [`build-dependencies` 7keyword](https://crates.io/keywords/build-dependencies) to see what is 8available. The following is a sample of some popular crates[^†]: 9 10* [`bindgen`](https://crates.io/crates/bindgen) — Automatically generate Rust 11 FFI bindings to C libraries. 12* [`cc`](https://crates.io/crates/cc) — Compiles C/C++/assembly. 13* [`pkg-config`](https://crates.io/crates/pkg-config) — Detect system 14 libraries using the `pkg-config` utility. 15* [`cmake`](https://crates.io/crates/cmake) — Runs the `cmake` build tool to build a native library. 16* [`autocfg`](https://crates.io/crates/autocfg), 17 [`rustc_version`](https://crates.io/crates/rustc_version), 18 [`version_check`](https://crates.io/crates/version_check) — These crates 19 provide ways to implement conditional compilation based on the current 20 `rustc` such as the version of the compiler. 21 22[^†]: This list is not an endorsement. Evaluate your dependencies to see which 23is right for your project. 24 25### Code generation 26 27Some Cargo packages need to have code generated just before they are compiled 28for various reasons. Here we’ll walk through a simple example which generates a 29library call as part of the build script. 30 31First, let’s take a look at the directory structure of this package: 32 33```text 34. 35├── Cargo.toml 36├── build.rs 37└── src 38 └── main.rs 39 401 directory, 3 files 41``` 42 43Here we can see that we have a `build.rs` build script and our binary in 44`main.rs`. This package has a basic manifest: 45 46```toml 47# Cargo.toml 48 49[package] 50name = "hello-from-generated-code" 51version = "0.1.0" 52``` 53 54Let’s see what’s inside the build script: 55 56```rust,no_run 57// build.rs 58 59use std::env; 60use std::fs; 61use std::path::Path; 62 63fn main() { 64 let out_dir = env::var_os("OUT_DIR").unwrap(); 65 let dest_path = Path::new(&out_dir).join("hello.rs"); 66 fs::write( 67 &dest_path, 68 "pub fn message() -> &'static str { 69 \"Hello, World!\" 70 } 71 " 72 ).unwrap(); 73 println!("cargo:rerun-if-changed=build.rs"); 74} 75``` 76 77There’s a couple of points of note here: 78 79* The script uses the `OUT_DIR` environment variable to discover where the 80 output files should be located. It can use the process’ current working 81 directory to find where the input files should be located, but in this case we 82 don’t have any input files. 83* In general, build scripts should not modify any files outside of `OUT_DIR`. 84 It may seem fine on the first blush, but it does cause problems when you use 85 such crate as a dependency, because there's an *implicit* invariant that 86 sources in `.cargo/registry` should be immutable. `cargo` won't allow such 87 scripts when packaging. 88* This script is relatively simple as it just writes out a small generated file. 89 One could imagine that other more fanciful operations could take place such as 90 generating a Rust module from a C header file or another language definition, 91 for example. 92* The [`rerun-if-changed` instruction](build-scripts.md#rerun-if-changed) 93 tells Cargo that the build script only needs to re-run if the build script 94 itself changes. Without this line, Cargo will automatically run the build 95 script if any file in the package changes. If your code generation uses some 96 input files, this is where you would print a list of each of those files. 97 98Next, let’s peek at the library itself: 99 100```rust,ignore 101// src/main.rs 102 103include!(concat!(env!("OUT_DIR"), "/hello.rs")); 104 105fn main() { 106 println!("{}", message()); 107} 108``` 109 110This is where the real magic happens. The library is using the rustc-defined 111[`include!` macro][include-macro] in combination with the 112[`concat!`][concat-macro] and [`env!`][env-macro] macros to include the 113generated file (`hello.rs`) into the crate’s compilation. 114 115Using the structure shown here, crates can include any number of generated files 116from the build script itself. 117 118[include-macro]: ../../std/macro.include.html 119[concat-macro]: ../../std/macro.concat.html 120[env-macro]: ../../std/macro.env.html 121 122### Building a native library 123 124Sometimes it’s necessary to build some native C or C++ code as part of a 125package. This is another excellent use case of leveraging the build script to 126build a native library before the Rust crate itself. As an example, we’ll create 127a Rust library which calls into C to print “Hello, World!”. 128 129Like above, let’s first take a look at the package layout: 130 131```text 132. 133├── Cargo.toml 134├── build.rs 135└── src 136 ├── hello.c 137 └── main.rs 138 1391 directory, 4 files 140``` 141 142Pretty similar to before! Next, the manifest: 143 144```toml 145# Cargo.toml 146 147[package] 148name = "hello-world-from-c" 149version = "0.1.0" 150edition = "2018" 151``` 152 153For now we’re not going to use any build dependencies, so let’s take a look at 154the build script now: 155 156```rust,no_run 157// build.rs 158 159use std::process::Command; 160use std::env; 161use std::path::Path; 162 163fn main() { 164 let out_dir = env::var("OUT_DIR").unwrap(); 165 166 // Note that there are a number of downsides to this approach, the comments 167 // below detail how to improve the portability of these commands. 168 Command::new("gcc").args(&["src/hello.c", "-c", "-fPIC", "-o"]) 169 .arg(&format!("{}/hello.o", out_dir)) 170 .status().unwrap(); 171 Command::new("ar").args(&["crus", "libhello.a", "hello.o"]) 172 .current_dir(&Path::new(&out_dir)) 173 .status().unwrap(); 174 175 println!("cargo:rustc-link-search=native={}", out_dir); 176 println!("cargo:rustc-link-lib=static=hello"); 177 println!("cargo:rerun-if-changed=src/hello.c"); 178} 179``` 180 181This build script starts out by compiling our C file into an object file (by 182invoking `gcc`) and then converting this object file into a static library (by 183invoking `ar`). The final step is feedback to Cargo itself to say that our 184output was in `out_dir` and the compiler should link the crate to `libhello.a` 185statically via the `-l static=hello` flag. 186 187Note that there are a number of drawbacks to this hard-coded approach: 188 189* The `gcc` command itself is not portable across platforms. For example it’s 190 unlikely that Windows platforms have `gcc`, and not even all Unix platforms 191 may have `gcc`. The `ar` command is also in a similar situation. 192* These commands do not take cross-compilation into account. If we’re cross 193 compiling for a platform such as Android it’s unlikely that `gcc` will produce 194 an ARM executable. 195 196Not to fear, though, this is where a `build-dependencies` entry would help! 197The Cargo ecosystem has a number of packages to make this sort of task much 198easier, portable, and standardized. Let's try the [`cc` 199crate](https://crates.io/crates/cc) from [crates.io]. First, add it to the 200`build-dependencies` in `Cargo.toml`: 201 202```toml 203[build-dependencies] 204cc = "1.0" 205``` 206 207And rewrite the build script to use this crate: 208 209```rust,ignore 210// build.rs 211 212fn main() { 213 cc::Build::new() 214 .file("src/hello.c") 215 .compile("hello"); 216 println!("cargo:rerun-if-changed=src/hello.c"); 217} 218``` 219 220The [`cc` crate] abstracts a range of build script requirements for C code: 221 222* It invokes the appropriate compiler (MSVC for windows, `gcc` for MinGW, `cc` 223 for Unix platforms, etc.). 224* It takes the `TARGET` variable into account by passing appropriate flags to 225 the compiler being used. 226* Other environment variables, such as `OPT_LEVEL`, `DEBUG`, etc., are all 227 handled automatically. 228* The stdout output and `OUT_DIR` locations are also handled by the `cc` 229 library. 230 231Here we can start to see some of the major benefits of farming as much 232functionality as possible out to common build dependencies rather than 233duplicating logic across all build scripts! 234 235Back to the case study though, let’s take a quick look at the contents of the 236`src` directory: 237 238```c 239// src/hello.c 240 241#include <stdio.h> 242 243void hello() { 244 printf("Hello, World!\n"); 245} 246``` 247 248```rust,ignore 249// src/main.rs 250 251// Note the lack of the `#[link]` attribute. We’re delegating the responsibility 252// of selecting what to link over to the build script rather than hard-coding 253// it in the source file. 254extern { fn hello(); } 255 256fn main() { 257 unsafe { hello(); } 258} 259``` 260 261And there we go! This should complete our example of building some C code from a 262Cargo package using the build script itself. This also shows why using a build 263dependency can be crucial in many situations and even much more concise! 264 265We’ve also seen a brief example of how a build script can use a crate as a 266dependency purely for the build process and not for the crate itself at runtime. 267 268[`cc` crate]: https://crates.io/crates/cc 269 270### Linking to system libraries 271 272This example demonstrates how to link a system library and how the build 273script is used to support this use case. 274 275Quite frequently a Rust crate wants to link to a native library provided on 276the system to bind its functionality or just use it as part of an 277implementation detail. This is quite a nuanced problem when it comes to 278performing this in a platform-agnostic fashion. It is best, if possible, to 279farm out as much of this as possible to make this as easy as possible for 280consumers. 281 282For this example, we will be creating a binding to the system's zlib library. 283This is a library that is commonly found on most Unix-like systems that 284provides data compression. This is already wrapped up in the [`libz-sys` 285crate], but for this example, we'll do an extremely simplified version. Check 286out [the source code][libz-source] for the full example. 287 288To make it easy to find the location of the library, we will use the 289[`pkg-config` crate]. This crate uses the system's `pkg-config` utility to 290discover information about a library. It will automatically tell Cargo what is 291needed to link the library. This will likely only work on Unix-like systems 292with `pkg-config` installed. Let's start by setting up the manifest: 293 294```toml 295# Cargo.toml 296 297[package] 298name = "libz-sys" 299version = "0.1.0" 300edition = "2018" 301links = "z" 302 303[build-dependencies] 304pkg-config = "0.3.16" 305``` 306 307Take note that we included the `links` key in the `package` table. This tells 308Cargo that we are linking to the `libz` library. See ["Using another sys 309crate"](#using-another-sys-crate) for an example that will leverage this. 310 311The build script is fairly simple: 312 313```rust,ignore 314// build.rs 315 316fn main() { 317 pkg_config::Config::new().probe("zlib").unwrap(); 318 println!("cargo:rerun-if-changed=build.rs"); 319} 320``` 321 322Let's round out the example with a basic FFI binding: 323 324```rust,ignore 325// src/lib.rs 326 327use std::os::raw::{c_uint, c_ulong}; 328 329extern "C" { 330 pub fn crc32(crc: c_ulong, buf: *const u8, len: c_uint) -> c_ulong; 331} 332 333#[test] 334fn test_crc32() { 335 let s = "hello"; 336 unsafe { 337 assert_eq!(crc32(0, s.as_ptr(), s.len() as c_uint), 0x3610a686); 338 } 339} 340``` 341 342Run `cargo build -vv` to see the output from the build script. On a system 343with `libz` already installed, it may look something like this: 344 345```text 346[libz-sys 0.1.0] cargo:rustc-link-search=native=/usr/lib 347[libz-sys 0.1.0] cargo:rustc-link-lib=z 348[libz-sys 0.1.0] cargo:rerun-if-changed=build.rs 349``` 350 351Nice! `pkg-config` did all the work of finding the library and telling Cargo 352where it is. 353 354It is not unusual for packages to include the source for the library, and 355build it statically if it is not found on the system, or if a feature or 356environment variable is set. For example, the real [`libz-sys` crate] checks the 357environment variable `LIBZ_SYS_STATIC` or the `static` feature to build it 358from source instead of using the system library. Check out [the 359source][libz-source] for a more complete example. 360 361[`libz-sys` crate]: https://crates.io/crates/libz-sys 362[`pkg-config` crate]: https://crates.io/crates/pkg-config 363[libz-source]: https://github.com/rust-lang/libz-sys 364 365### Using another `sys` crate 366 367When using the `links` key, crates may set metadata that can be read by other 368crates that depend on it. This provides a mechanism to communicate information 369between crates. In this example, we'll be creating a C library that makes use 370of zlib from the real [`libz-sys` crate]. 371 372If you have a C library that depends on zlib, you can leverage the [`libz-sys` 373crate] to automatically find it or build it. This is great for cross-platform 374support, such as Windows where zlib is not usually installed. `libz-sys` [sets 375the `include` 376metadata](https://github.com/rust-lang/libz-sys/blob/3c594e677c79584500da673f918c4d2101ac97a1/build.rs#L156) 377to tell other packages where to find the header files for zlib. Our build 378script can read that metadata with the `DEP_Z_INCLUDE` environment variable. 379Here's an example: 380 381```toml 382# Cargo.toml 383 384[package] 385name = "zuser" 386version = "0.1.0" 387edition = "2018" 388 389[dependencies] 390libz-sys = "1.0.25" 391 392[build-dependencies] 393cc = "1.0.46" 394``` 395 396Here we have included `libz-sys` which will ensure that there is only one 397`libz` used in the final library, and give us access to it from our build 398script: 399 400```rust,ignore 401// build.rs 402 403fn main() { 404 let mut cfg = cc::Build::new(); 405 cfg.file("src/zuser.c"); 406 if let Some(include) = std::env::var_os("DEP_Z_INCLUDE") { 407 cfg.include(include); 408 } 409 cfg.compile("zuser"); 410 println!("cargo:rerun-if-changed=src/zuser.c"); 411} 412``` 413 414With `libz-sys` doing all the heavy lifting, the C source code may now include 415the zlib header, and it should find the header, even on systems where it isn't 416already installed. 417 418```c 419// src/zuser.c 420 421#include "zlib.h" 422 423// … rest of code that makes use of zlib. 424``` 425 426### Conditional compilation 427 428A build script may emit [`rustc-cfg` instructions] which can enable conditions 429that can be checked at compile time. In this example, we'll take a look at how 430the [`openssl` crate] uses this to support multiple versions of the OpenSSL 431library. 432 433The [`openssl-sys` crate] implements building and linking the OpenSSL library. 434It supports multiple different implementations (like LibreSSL) and multiple 435versions. It makes use of the `links` key so that it may pass information to 436other build scripts. One of the things it passes is the `version_number` key, 437which is the version of OpenSSL that was detected. The code in the build 438script looks something [like 439this](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl-sys/build/main.rs#L216): 440 441```rust,ignore 442println!("cargo:version_number={:x}", openssl_version); 443``` 444 445This instruction causes the `DEP_OPENSSL_VERSION_NUMBER` environment variable 446to be set in any crates that directly depend on `openssl-sys`. 447 448The `openssl` crate, which provides the higher-level interface, specifies 449`openssl-sys` as a dependency. The `openssl` build script can read the 450version information generated by the `openssl-sys` build script with the 451`DEP_OPENSSL_VERSION_NUMBER` environment variable. It uses this to generate 452some [`cfg` 453values](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl/build.rs#L18-L36): 454 455```rust,ignore 456// (portion of build.rs) 457 458if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { 459 let version = u64::from_str_radix(&version, 16).unwrap(); 460 461 if version >= 0x1_00_01_00_0 { 462 println!("cargo:rustc-cfg=ossl101"); 463 } 464 if version >= 0x1_00_02_00_0 { 465 println!("cargo:rustc-cfg=ossl102"); 466 } 467 if version >= 0x1_01_00_00_0 { 468 println!("cargo:rustc-cfg=ossl110"); 469 } 470 if version >= 0x1_01_00_07_0 { 471 println!("cargo:rustc-cfg=ossl110g"); 472 } 473 if version >= 0x1_01_01_00_0 { 474 println!("cargo:rustc-cfg=ossl111"); 475 } 476} 477``` 478 479These `cfg` values can then be used with the [`cfg` attribute] or the [`cfg` 480macro] to conditionally include code. For example, SHA3 support was added in 481OpenSSL 1.1.1, so it is [conditionally 482excluded](https://github.com/sfackler/rust-openssl/blob/dc72a8e2c429e46c275e528b61a733a66e7877fc/openssl/src/hash.rs#L67-L85) 483for older versions: 484 485```rust,ignore 486// (portion of openssl crate) 487 488#[cfg(ossl111)] 489pub fn sha3_224() -> MessageDigest { 490 unsafe { MessageDigest(ffi::EVP_sha3_224()) } 491} 492``` 493 494Of course, one should be careful when using this, since it makes the resulting 495binary even more dependent on the build environment. In this example, if the 496binary is distributed to another system, it may not have the exact same shared 497libraries, which could cause problems. 498 499[`cfg` attribute]: ../../reference/conditional-compilation.md#the-cfg-attribute 500[`cfg` macro]: ../../std/macro.cfg.html 501[`rustc-cfg` instructions]: build-scripts.md#rustc-cfg 502[`openssl` crate]: https://crates.io/crates/openssl 503[`openssl-sys` crate]: https://crates.io/crates/openssl-sys 504 505[crates.io]: https://crates.io/ 506