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