build_rs/
output.rs

1//! Outputs from the build script to the build system.
2//!
3//! This crate assumes that stdout is at a new line whenever an output directive
4//! is called. Printing to stdout without a terminating newline (i.e. not using
5//! [`println!`]) may lead to surprising behavior.
6//!
7//! Reference: <https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script>
8
9use std::ffi::OsStr;
10use std::path::Path;
11use std::{fmt::Display, fmt::Write as _};
12
13use crate::ident::{is_ascii_ident, is_ident};
14
15fn emit(directive: &str, value: impl Display) {
16    println!("cargo::{directive}={value}");
17}
18
19/// The `rerun-if-changed` instruction tells Cargo to re-run the build script if the
20/// file at the given path has changed.
21///
22/// Currently, Cargo only uses the filesystem
23/// last-modified “mtime” timestamp to determine if the file has changed. It
24/// compares against an internal cached timestamp of when the build script last ran.
25///
26/// If the path points to a directory, it will scan the entire directory for any
27/// modifications.
28///
29/// If the build script inherently does not need to re-run under any circumstance,
30/// then calling `rerun_if_changed("build.rs")` is a simple way to prevent it from
31/// being re-run (otherwise, the default if no `rerun-if` instructions are emitted
32/// is to scan the entire package directory for changes). Cargo automatically
33/// handles whether or not the script itself needs to be recompiled, and of course
34/// the script will be re-run after it has been recompiled. Otherwise, specifying
35/// `build.rs` is redundant and unnecessary.
36#[track_caller]
37pub fn rerun_if_changed(path: impl AsRef<Path>) {
38    let Some(path) = path.as_ref().to_str() else {
39        panic!("cannot emit rerun-if-changed: path is not UTF-8");
40    };
41    if path.contains('\n') {
42        panic!("cannot emit rerun-if-changed: path contains newline");
43    }
44    emit("rerun-if-changed", path);
45}
46
47/// The `rerun-if-env-changed` instruction tells Cargo to re-run the build script
48/// if the value of an environment variable of the given name has changed.
49///
50/// Note that the environment variables here are intended for global environment
51/// variables like `CC` and such, it is not possible to use this for environment
52/// variables like `TARGET` that [Cargo sets for build scripts][build-env]. The
53/// environment variables in use are those received by cargo invocations, not
54/// those received by the executable of the build script.
55///
56/// [build-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
57#[track_caller]
58pub fn rerun_if_env_changed(key: impl AsRef<OsStr>) {
59    let Some(key) = key.as_ref().to_str() else {
60        panic!("cannot emit rerun-if-env-changed: key is not UTF-8");
61    };
62    if key.contains('\n') {
63        panic!("cannot emit rerun-if-env-changed: key contains newline");
64    }
65    emit("rerun-if-env-changed", key);
66}
67
68/// The `rustc-link-arg` instruction tells Cargo to pass the
69/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
70/// supported targets (benchmarks, binaries, cdylib crates, examples, and tests).
71///
72/// Its usage is highly platform specific. It is useful to set the shared library
73/// version or linker script.
74///
75/// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg
76#[track_caller]
77pub fn rustc_link_arg(flag: &str) {
78    if flag.contains([' ', '\n']) {
79        panic!("cannot emit rustc-link-arg: invalid flag {flag:?}");
80    }
81    emit("rustc-link-arg", flag);
82}
83
84/// The `rustc-link-arg-bin` instruction tells Cargo to pass the
85/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
86/// the binary target with name `BIN`. Its usage is highly platform specific.
87///
88/// It
89/// is useful to set a linker script or other linker options.
90///
91/// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg
92#[track_caller]
93pub fn rustc_link_arg_bin(bin: &str, flag: &str) {
94    if !is_ident(bin) {
95        panic!("cannot emit rustc-link-arg-bin: invalid bin name {bin:?}");
96    }
97    if flag.contains([' ', '\n']) {
98        panic!("cannot emit rustc-link-arg-bin: invalid flag {flag:?}");
99    }
100    emit("rustc-link-arg-bin", format_args!("{bin}={flag}"));
101}
102
103/// The `rustc-link-arg-bins` instruction tells Cargo to pass the
104/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
105/// the binary target.
106///
107/// Its usage is highly platform specific. It is useful to set
108/// a linker script or other linker options.
109///
110/// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg
111#[track_caller]
112pub fn rustc_link_arg_bins(flag: &str) {
113    if flag.contains([' ', '\n']) {
114        panic!("cannot emit rustc-link-arg-bins: invalid flag {flag:?}");
115    }
116    emit("rustc-link-arg-bins", flag);
117}
118
119/// The `rustc-link-arg-tests` instruction tells Cargo to pass the
120/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
121/// a tests target.
122#[track_caller]
123pub fn rustc_link_arg_tests(flag: &str) {
124    if flag.contains([' ', '\n']) {
125        panic!("cannot emit rustc-link-arg-tests: invalid flag {flag:?}");
126    }
127    emit("rustc-link-arg-tests", flag);
128}
129
130/// The `rustc-link-arg-examples` instruction tells Cargo to pass the
131/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
132/// an examples target.
133#[track_caller]
134pub fn rustc_link_arg_examples(flag: &str) {
135    if flag.contains([' ', '\n']) {
136        panic!("cannot emit rustc-link-arg-examples: invalid flag {flag:?}");
137    }
138    emit("rustc-link-arg-examples", flag);
139}
140
141/// The `rustc-link-arg-benches` instruction tells Cargo to pass the
142/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
143/// a benchmark target.
144#[track_caller]
145pub fn rustc_link_arg_benches(flag: &str) {
146    if flag.contains([' ', '\n']) {
147        panic!("cannot emit rustc-link-arg-benches: invalid flag {flag:?}");
148    }
149    emit("rustc-link-arg-benches", flag);
150}
151
152/// The `rustc-link-lib` instruction tells Cargo to link the given library using
153/// the compiler’s [`-l` flag][-l].
154///
155/// This is typically used to link a native library
156/// using [FFI].
157///
158/// The `LIB` string is passed directly to rustc, so it supports any syntax that
159/// `-l` does. Currently the full supported syntax for `LIB` is
160/// `[KIND[:MODIFIERS]=]NAME[:RENAME]`.
161///
162/// The `-l` flag is only passed to the library target of the package, unless there
163/// is no library target, in which case it is passed to all targets. This is done
164/// because all other targets have an implicit dependency on the library target,
165/// and the given library to link should only be included once. This means that
166/// if a package has both a library and a binary target, the library has access
167/// to the symbols from the given lib, and the binary should access them through
168/// the library target’s public API.
169///
170/// The optional `KIND` may be one of `dylib`, `static`, or `framework`. See the
171/// [rustc book][-l] for more detail.
172///
173/// [-l]: https://doc.rust-lang.org/stable/rustc/command-line-arguments.html#option-l-link-lib
174/// [FFI]: https://doc.rust-lang.org/stable/nomicon/ffi.html
175#[track_caller]
176pub fn rustc_link_lib(lib: &str) {
177    if lib.contains([' ', '\n']) {
178        panic!("cannot emit rustc-link-lib: invalid lib {lib:?}");
179    }
180    emit("rustc-link-lib", lib);
181}
182
183/// Like [`rustc_link_lib`], but with `KIND[:MODIFIERS]` specified separately.
184#[track_caller]
185pub fn rustc_link_lib_kind(kind: &str, lib: &str) {
186    if kind.contains(['=', ' ', '\n']) {
187        panic!("cannot emit rustc-link-lib: invalid kind {kind:?}");
188    }
189    if lib.contains([' ', '\n']) {
190        panic!("cannot emit rustc-link-lib: invalid lib {lib:?}");
191    }
192    emit("rustc-link-lib", format_args!("{kind}={lib}"));
193}
194
195/// The `rustc-link-search` instruction tells Cargo to pass the [`-L` flag] to the
196/// compiler to add a directory to the library search path.
197///
198/// The optional `KIND` may be one of `dependency`, `crate`, `native`, `framework`,
199/// or `all`. See the [rustc book][-L] for more detail.
200///
201/// These paths are also added to the
202/// [dynamic library search path environment variable][search-path] if they are
203/// within the `OUT_DIR`. Depending on this behavior is discouraged since this
204/// makes it difficult to use the resulting binary. In general, it is best to
205/// avoid creating dynamic libraries in a build script (using existing system
206/// libraries is fine).
207///
208/// [-L]: https://doc.rust-lang.org/stable/rustc/command-line-arguments.html#option-l-search-path
209/// [search-path]: https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#dynamic-library-paths
210#[track_caller]
211pub fn rustc_link_search(path: impl AsRef<Path>) {
212    let Some(path) = path.as_ref().to_str() else {
213        panic!("cannot emit rustc-link-search: path is not UTF-8");
214    };
215    if path.contains('\n') {
216        panic!("cannot emit rustc-link-search: path contains newline");
217    }
218    emit("rustc-link-search", path);
219}
220
221/// Like [`rustc_link_search`], but with KIND specified separately.
222#[track_caller]
223pub fn rustc_link_search_kind(kind: &str, path: impl AsRef<Path>) {
224    if kind.contains(['=', '\n']) {
225        panic!("cannot emit rustc-link-search: invalid kind {kind:?}");
226    }
227    let Some(path) = path.as_ref().to_str() else {
228        panic!("cannot emit rustc-link-search: path is not UTF-8");
229    };
230    if path.contains('\n') {
231        panic!("cannot emit rustc-link-search: path contains newline");
232    }
233    emit("rustc-link-search", format_args!("{kind}={path}"));
234}
235
236/// The `rustc-flags` instruction tells Cargo to pass the given space-separated
237/// flags to the compiler.
238///
239/// This only allows the `-l` and `-L` flags, and is
240/// equivalent to using [`rustc_link_lib`] and [`rustc_link_search`].
241#[track_caller]
242pub fn rustc_flags(flags: &str) {
243    if flags.contains('\n') {
244        panic!("cannot emit rustc-flags: invalid flags");
245    }
246    emit("rustc-flags", flags);
247}
248
249/// The `rustc-cfg` instruction tells Cargo to pass the given value to the
250/// [`--cfg` flag][cfg] to the compiler.
251///
252/// This may be used for compile-time
253/// detection of features to enable conditional compilation.
254///
255/// Note that this does not affect Cargo’s dependency resolution. This cannot
256/// be used to enable an optional dependency, or enable other Cargo features.
257///
258/// Be aware that [Cargo features] use the form `feature="foo"`. `cfg` values
259/// passed with this flag are not restricted to that form, and may provide just
260/// a single identifier, or any arbitrary key/value pair. For example, emitting
261/// `rustc_cfg("abc")` will then allow code to use `#[cfg(abc)]` (note the lack
262/// of `feature=`). Or an arbitrary key/value pair may be used with an `=` symbol
263/// like `rustc_cfg(r#"my_component="foo""#)`. The key should be a Rust identifier,
264/// the value should be a string.
265///
266/// [cfg]: https://doc.rust-lang.org/rustc/command-line-arguments.html#option-cfg
267/// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html
268#[track_caller]
269pub fn rustc_cfg(key: &str) {
270    if !is_ident(key) {
271        panic!("cannot emit rustc-cfg: invalid key {key:?}");
272    }
273    emit("rustc-cfg", key);
274}
275
276/// Like [`rustc_cfg`], but with the value specified separately.
277///
278/// To replace the
279/// less convenient `rustc_cfg(r#"my_component="foo""#)`, you can instead use
280/// `rustc_cfg_value("my_component", "foo")`.
281#[track_caller]
282pub fn rustc_cfg_value(key: &str, value: &str) {
283    if !is_ident(key) {
284        panic!("cannot emit rustc-cfg-value: invalid key");
285    }
286    let value = value.escape_default();
287    emit("rustc-cfg", format_args!("{key}=\"{value}\""));
288}
289
290/// Add to the list of expected config names that is used when checking the
291/// *reachable* cfg expressions with the [`unexpected_cfgs`] lint.
292///
293/// This form is for keys without an expected value, such as `cfg(name)`.
294///
295/// It is recommended to group the `rustc_check_cfg` and `rustc_cfg` calls as
296/// closely as possible in order to avoid typos, missing check_cfg, stale cfgs,
297/// and other mistakes.
298///
299/// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs
300#[doc = respected_msrv!("1.80")]
301#[track_caller]
302pub fn rustc_check_cfgs(keys: &[&str]) {
303    if keys.is_empty() {
304        return;
305    }
306    for key in keys {
307        if !is_ident(key) {
308            panic!("cannot emit rustc-check-cfg: invalid key {key:?}");
309        }
310    }
311
312    let mut directive = keys[0].to_string();
313    for key in &keys[1..] {
314        write!(directive, ", {key}").expect("writing to string should be infallible");
315    }
316    emit("rustc-check-cfg", format_args!("cfg({directive})"));
317}
318
319/// Add to the list of expected config names that is used when checking the
320/// *reachable* cfg expressions with the [`unexpected_cfgs`] lint.
321///
322/// This form is for keys with expected values, such as `cfg(name = "value")`.
323///
324/// It is recommended to group the `rustc_check_cfg` and `rustc_cfg` calls as
325/// closely as possible in order to avoid typos, missing check_cfg, stale cfgs,
326/// and other mistakes.
327///
328/// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs
329#[doc = respected_msrv!("1.80")]
330#[track_caller]
331pub fn rustc_check_cfg_values(key: &str, values: &[&str]) {
332    if !is_ident(key) {
333        panic!("cannot emit rustc-check-cfg: invalid key {key:?}");
334    }
335    if values.is_empty() {
336        rustc_check_cfgs(&[key]);
337        return;
338    }
339
340    let mut directive = format!("\"{}\"", values[0].escape_default());
341    for value in &values[1..] {
342        write!(directive, ", \"{}\"", value.escape_default())
343            .expect("writing to string should be infallible");
344    }
345    emit(
346        "rustc-check-cfg",
347        format_args!("cfg({key}, values({directive}))"),
348    );
349}
350
351/// The `rustc-env` instruction tells Cargo to set the given environment variable
352/// when compiling the package.
353///
354/// The value can be then retrieved by the
355/// [`env!` macro][env!] in the compiled crate. This is useful for embedding
356/// additional metadata in crate’s code, such as the hash of git HEAD or the
357/// unique identifier of a continuous integration server.
358///
359/// See also the [environment variables automatically included by Cargo][cargo-env].
360///
361/// [cargo-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
362#[track_caller]
363pub fn rustc_env(key: &str, value: &str) {
364    if key.contains(['=', '\n']) {
365        panic!("cannot emit rustc-env: invalid key {key:?}");
366    }
367    if value.contains('\n') {
368        panic!("cannot emit rustc-env: invalid value {value:?}");
369    }
370    emit("rustc-env", format_args!("{key}={value}"));
371}
372
373/// The `rustc-cdylib-link-arg` instruction tells Cargo to pass the
374/// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building
375/// a `cdylib` library target.
376///
377/// Its usage is highly platform specific. It is useful
378/// to set the shared library version or the runtime-path.
379///
380/// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg
381#[track_caller]
382pub fn rustc_cdylib_link_arg(flag: &str) {
383    if flag.contains('\n') {
384        panic!("cannot emit rustc-cdylib-link-arg: invalid flag {flag:?}");
385    }
386    emit("rustc-cdylib-link-arg", flag);
387}
388
389/// The `warning` instruction tells Cargo to display a warning after the build
390/// script has finished running.
391///
392/// Warnings are only shown for path dependencies
393/// (that is, those you’re working on locally), so for example warnings printed
394/// out in [crates.io] crates are not emitted by default. The `-vv` “very verbose”
395/// flag may be used to have Cargo display warnings for all crates.
396///
397/// [crates.io]: https://crates.io/
398#[track_caller]
399pub fn warning(message: &str) {
400    if message.contains('\n') {
401        panic!("cannot emit warning: message contains newline");
402    }
403    emit("warning", message);
404}
405
406/// The `error` instruction tells Cargo to display an error after the build script has finished
407/// running, and then fail the build.
408///
409/// <div class="warning">
410///
411/// Build script libraries should carefully consider if they want to use [`error`] versus
412/// returning a `Result`. It may be better to return a `Result`, and allow the caller to decide if the
413/// error is fatal or not. The caller can then decide whether or not to display the `Err` variant
414/// using [`error`].
415///
416/// </div>
417#[doc = respected_msrv!("1.84")]
418#[track_caller]
419pub fn error(message: &str) {
420    if message.contains('\n') {
421        panic!("cannot emit error: message contains newline");
422    }
423    emit("error", message);
424}
425
426/// Metadata, used by `links` scripts.
427#[track_caller]
428pub fn metadata(key: &str, val: &str) {
429    if !is_ascii_ident(key) {
430        panic!("cannot emit metadata: invalid key {key:?}");
431    }
432    if val.contains('\n') {
433        panic!("cannot emit metadata: invalid value {val:?}");
434    }
435
436    emit("metadata", format_args!("{key}={val}"));
437}