| //! Outputs from the build script to the build system. |
| //! |
| //! This crate assumes that stdout is at a new line whenever an output directive |
| //! is called. Printing to stdout without a terminating newline (i.e. not using |
| //! [`println!`]) may lead to surprising behavior. |
| //! |
| //! Reference: <https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script> |
| |
| use std::ffi::OsStr; |
| use std::path::Path; |
| use std::{fmt::Display, fmt::Write as _}; |
| |
| use crate::ident::{is_ascii_ident, is_ident}; |
| |
| fn emit(directive: &str, value: impl Display) { |
| println!("cargo::{}={}", directive, value); |
| } |
| |
| /// The `rerun-if-changed` instruction tells Cargo to re-run the build script if the |
| /// file at the given path has changed. |
| /// |
| /// Currently, Cargo only uses the filesystem |
| /// last-modified “mtime” timestamp to determine if the file has changed. It |
| /// compares against an internal cached timestamp of when the build script last ran. |
| /// |
| /// If the path points to a directory, it will scan the entire directory for any |
| /// modifications. |
| /// |
| /// If the build script inherently does not need to re-run under any circumstance, |
| /// then calling `rerun_if_changed("build.rs")` is a simple way to prevent it from |
| /// being re-run (otherwise, the default if no `rerun-if` instructions are emitted |
| /// is to scan the entire package directory for changes). Cargo automatically |
| /// handles whether or not the script itself needs to be recompiled, and of course |
| /// the script will be re-run after it has been recompiled. Otherwise, specifying |
| /// `build.rs` is redundant and unnecessary. |
| #[track_caller] |
| pub fn rerun_if_changed(path: impl AsRef<Path>) { |
| let Some(path) = path.as_ref().to_str() else { |
| panic!("cannot emit rerun-if-changed: path is not UTF-8"); |
| }; |
| if path.contains('\n') { |
| panic!("cannot emit rerun-if-changed: path contains newline"); |
| } |
| emit("rerun-if-changed", path); |
| } |
| |
| /// The `rerun-if-env-changed` instruction tells Cargo to re-run the build script |
| /// if the value of an environment variable of the given name has changed. |
| /// |
| /// Note that the environment variables here are intended for global environment |
| /// variables like `CC` and such, it is not possible to use this for environment |
| /// variables like `TARGET` that [Cargo sets for build scripts][build-env]. The |
| /// environment variables in use are those received by cargo invocations, not |
| /// those received by the executable of the build script. |
| /// |
| /// [build-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts |
| #[track_caller] |
| pub fn rerun_if_env_changed(key: impl AsRef<OsStr>) { |
| let Some(key) = key.as_ref().to_str() else { |
| panic!("cannot emit rerun-if-env-changed: key is not UTF-8"); |
| }; |
| if key.contains('\n') { |
| panic!("cannot emit rerun-if-env-changed: key contains newline"); |
| } |
| emit("rerun-if-env-changed", key); |
| } |
| |
| /// The `rustc-link-arg` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// supported targets (benchmarks, binaries, cdylib crates, examples, and tests). |
| /// |
| /// Its usage is highly platform specific. It is useful to set the shared library |
| /// version or linker script. |
| /// |
| /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg |
| #[track_caller] |
| pub fn rustc_link_arg(flag: &str) { |
| if flag.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-arg: invalid flag {flag:?}"); |
| } |
| emit("rustc-link-arg", flag); |
| } |
| |
| /// The `rustc-link-arg-bin` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// the binary target with name `BIN`. Its usage is highly platform specific. |
| /// |
| /// It |
| /// is useful to set a linker script or other linker options. |
| /// |
| /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg |
| #[track_caller] |
| pub fn rustc_link_arg_bin(bin: &str, flag: &str) { |
| if !is_ident(bin) { |
| panic!("cannot emit rustc-link-arg-bin: invalid bin name {bin:?}"); |
| } |
| if flag.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-arg-bin: invalid flag {flag:?}"); |
| } |
| emit("rustc-link-arg-bin", format_args!("{}={}", bin, flag)); |
| } |
| |
| /// The `rustc-link-arg-bins` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// the binary target. |
| /// |
| /// Its usage is highly platform specific. It is useful to set |
| /// a linker script or other linker options. |
| /// |
| /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg |
| #[track_caller] |
| pub fn rustc_link_arg_bins(flag: &str) { |
| if flag.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-arg-bins: invalid flag {flag:?}"); |
| } |
| emit("rustc-link-arg-bins", flag); |
| } |
| |
| /// The `rustc-link-arg-tests` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// a tests target. |
| #[track_caller] |
| pub fn rustc_link_arg_tests(flag: &str) { |
| if flag.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-arg-tests: invalid flag {flag:?}"); |
| } |
| emit("rustc-link-arg-tests", flag); |
| } |
| |
| /// The `rustc-link-arg-examples` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// an examples target. |
| #[track_caller] |
| pub fn rustc_link_arg_examples(flag: &str) { |
| if flag.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-arg-examples: invalid flag {flag:?}"); |
| } |
| emit("rustc-link-arg-examples", flag); |
| } |
| |
| /// The `rustc-link-arg-benches` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// a benchmark target. |
| #[track_caller] |
| pub fn rustc_link_arg_benches(flag: &str) { |
| if flag.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-arg-benches: invalid flag {flag:?}"); |
| } |
| emit("rustc-link-arg-benches", flag); |
| } |
| |
| /// The `rustc-link-lib` instruction tells Cargo to link the given library using |
| /// the compiler’s [`-l` flag][-l]. |
| /// |
| /// This is typically used to link a native library |
| /// using [FFI]. |
| /// |
| /// The `LIB` string is passed directly to rustc, so it supports any syntax that |
| /// `-l` does. Currently the full supported syntax for `LIB` is |
| /// `[KIND[:MODIFIERS]=]NAME[:RENAME]`. |
| /// |
| /// The `-l` flag is only passed to the library target of the package, unless there |
| /// is no library target, in which case it is passed to all targets. This is done |
| /// because all other targets have an implicit dependency on the library target, |
| /// and the given library to link should only be included once. This means that |
| /// if a package has both a library and a binary target, the library has access |
| /// to the symbols from the given lib, and the binary should access them through |
| /// the library target’s public API. |
| /// |
| /// The optional `KIND` may be one of `dylib`, `static`, or `framework`. See the |
| /// [rustc book][-l] for more detail. |
| /// |
| /// [-l]: https://doc.rust-lang.org/stable/rustc/command-line-arguments.html#option-l-link-lib |
| /// [FFI]: https://doc.rust-lang.org/stable/nomicon/ffi.html |
| #[track_caller] |
| pub fn rustc_link_lib(lib: &str) { |
| if lib.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-lib: invalid lib {lib:?}"); |
| } |
| emit("rustc-link-lib", lib); |
| } |
| |
| /// Like [`rustc_link_lib`], but with `KIND[:MODIFIERS]` specified separately. |
| #[track_caller] |
| pub fn rustc_link_lib_kind(kind: &str, lib: &str) { |
| if kind.contains(['=', ' ', '\n']) { |
| panic!("cannot emit rustc-link-lib: invalid kind {kind:?}"); |
| } |
| if lib.contains([' ', '\n']) { |
| panic!("cannot emit rustc-link-lib: invalid lib {lib:?}"); |
| } |
| emit("rustc-link-lib", format_args!("{kind}={lib}")); |
| } |
| |
| /// The `rustc-link-search` instruction tells Cargo to pass the [`-L` flag] to the |
| /// compiler to add a directory to the library search path. |
| /// |
| /// The optional `KIND` may be one of `dependency`, `crate`, `native`, `framework`, |
| /// or `all`. See the [rustc book][-L] for more detail. |
| /// |
| /// These paths are also added to the |
| /// [dynamic library search path environment variable][search-path] if they are |
| /// within the `OUT_DIR`. Depending on this behavior is discouraged since this |
| /// makes it difficult to use the resulting binary. In general, it is best to |
| /// avoid creating dynamic libraries in a build script (using existing system |
| /// libraries is fine). |
| /// |
| /// [-L]: https://doc.rust-lang.org/stable/rustc/command-line-arguments.html#option-l-search-path |
| /// [search-path]: https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#dynamic-library-paths |
| #[track_caller] |
| pub fn rustc_link_search(path: impl AsRef<Path>) { |
| let Some(path) = path.as_ref().to_str() else { |
| panic!("cannot emit rustc-link-search: path is not UTF-8"); |
| }; |
| if path.contains('\n') { |
| panic!("cannot emit rustc-link-search: path contains newline"); |
| } |
| emit("rustc-link-search", path); |
| } |
| |
| /// Like [`rustc_link_search`], but with KIND specified separately. |
| #[track_caller] |
| pub fn rustc_link_search_kind(kind: &str, path: impl AsRef<Path>) { |
| if kind.contains(['=', '\n']) { |
| panic!("cannot emit rustc-link-search: invalid kind {kind:?}"); |
| } |
| let Some(path) = path.as_ref().to_str() else { |
| panic!("cannot emit rustc-link-search: path is not UTF-8"); |
| }; |
| if path.contains('\n') { |
| panic!("cannot emit rustc-link-search: path contains newline"); |
| } |
| emit("rustc-link-search", format_args!("{kind}={path}")); |
| } |
| |
| /// The `rustc-flags` instruction tells Cargo to pass the given space-separated |
| /// flags to the compiler. |
| /// |
| /// This only allows the `-l` and `-L` flags, and is |
| /// equivalent to using [`rustc_link_lib`] and [`rustc_link_search`]. |
| #[track_caller] |
| pub fn rustc_flags(flags: &str) { |
| if flags.contains('\n') { |
| panic!("cannot emit rustc-flags: invalid flags"); |
| } |
| emit("rustc-flags", flags); |
| } |
| |
| /// The `rustc-cfg` instruction tells Cargo to pass the given value to the |
| /// [`--cfg` flag][cfg] to the compiler. |
| /// |
| /// This may be used for compile-time |
| /// detection of features to enable conditional compilation. |
| /// |
| /// Note that this does not affect Cargo’s dependency resolution. This cannot |
| /// be used to enable an optional dependency, or enable other Cargo features. |
| /// |
| /// Be aware that [Cargo features] use the form `feature="foo"`. `cfg` values |
| /// passed with this flag are not restricted to that form, and may provide just |
| /// a single identifier, or any arbitrary key/value pair. For example, emitting |
| /// `rustc_cfg("abc")` will then allow code to use `#[cfg(abc)]` (note the lack |
| /// of `feature=`). Or an arbitrary key/value pair may be used with an `=` symbol |
| /// like `rustc_cfg(r#"my_component="foo""#)`. The key should be a Rust identifier, |
| /// the value should be a string. |
| /// |
| /// [cfg]: https://doc.rust-lang.org/rustc/command-line-arguments.html#option-cfg |
| /// [Cargo features]: https://doc.rust-lang.org/cargo/reference/features.html |
| #[track_caller] |
| pub fn rustc_cfg(key: &str) { |
| if !is_ident(key) { |
| panic!("cannot emit rustc-cfg: invalid key {key:?}"); |
| } |
| emit("rustc-cfg", key); |
| } |
| |
| /// Like [`rustc_cfg`], but with the value specified separately. |
| /// |
| /// To replace the |
| /// less convenient `rustc_cfg(r#"my_component="foo""#)`, you can instead use |
| /// `rustc_cfg_value("my_component", "foo")`. |
| #[track_caller] |
| pub fn rustc_cfg_value(key: &str, value: &str) { |
| if !is_ident(key) { |
| panic!("cannot emit rustc-cfg-value: invalid key"); |
| } |
| let value = value.escape_default(); |
| emit("rustc-cfg", format_args!("{key}=\"{value}\"")); |
| } |
| |
| /// Add to the list of expected config names that is used when checking the |
| /// *reachable* cfg expressions with the [`unexpected_cfgs`] lint. |
| /// |
| /// This form is for keys without an expected value, such as `cfg(name)`. |
| /// |
| /// It is recommended to group the `rustc_check_cfg` and `rustc_cfg` calls as |
| /// closely as possible in order to avoid typos, missing check_cfg, stale cfgs, |
| /// and other mistakes. |
| /// |
| /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs |
| #[doc = respected_msrv!("1.80")] |
| #[track_caller] |
| pub fn rustc_check_cfgs(keys: &[&str]) { |
| if keys.is_empty() { |
| return; |
| } |
| for key in keys { |
| if !is_ident(key) { |
| panic!("cannot emit rustc-check-cfg: invalid key {key:?}"); |
| } |
| } |
| |
| let mut directive = keys[0].to_string(); |
| for key in &keys[1..] { |
| write!(directive, ", {key}").expect("writing to string should be infallible"); |
| } |
| emit("rustc-check-cfg", format_args!("cfg({directive})")); |
| } |
| |
| /// Add to the list of expected config names that is used when checking the |
| /// *reachable* cfg expressions with the [`unexpected_cfgs`] lint. |
| /// |
| /// This form is for keys with expected values, such as `cfg(name = "value")`. |
| /// |
| /// It is recommended to group the `rustc_check_cfg` and `rustc_cfg` calls as |
| /// closely as possible in order to avoid typos, missing check_cfg, stale cfgs, |
| /// and other mistakes. |
| /// |
| /// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs |
| #[doc = respected_msrv!("1.80")] |
| #[track_caller] |
| pub fn rustc_check_cfg_values(key: &str, values: &[&str]) { |
| if !is_ident(key) { |
| panic!("cannot emit rustc-check-cfg: invalid key {key:?}"); |
| } |
| if values.is_empty() { |
| rustc_check_cfgs(&[key]); |
| return; |
| } |
| |
| let mut directive = format!("\"{}\"", values[0].escape_default()); |
| for value in &values[1..] { |
| write!(directive, ", \"{}\"", value.escape_default()) |
| .expect("writing to string should be infallible"); |
| } |
| emit( |
| "rustc-check-cfg", |
| format_args!("cfg({key}, values({directive}))"), |
| ); |
| } |
| |
| /// The `rustc-env` instruction tells Cargo to set the given environment variable |
| /// when compiling the package. |
| /// |
| /// The value can be then retrieved by the |
| /// [`env!` macro][env!] in the compiled crate. This is useful for embedding |
| /// additional metadata in crate’s code, such as the hash of git HEAD or the |
| /// unique identifier of a continuous integration server. |
| /// |
| /// See also the [environment variables automatically included by Cargo][cargo-env]. |
| /// |
| /// [cargo-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates |
| #[track_caller] |
| pub fn rustc_env(key: &str, value: &str) { |
| if key.contains(['=', '\n']) { |
| panic!("cannot emit rustc-env: invalid key {key:?}"); |
| } |
| if value.contains('\n') { |
| panic!("cannot emit rustc-env: invalid value {value:?}"); |
| } |
| emit("rustc-env", format_args!("{key}={value}")); |
| } |
| |
| /// The `rustc-cdylib-link-arg` instruction tells Cargo to pass the |
| /// [`-C link-arg=FLAG` option][link-arg] to the compiler, but only when building |
| /// a `cdylib` library target. |
| /// |
| /// Its usage is highly platform specific. It is useful |
| /// to set the shared library version or the runtime-path. |
| /// |
| /// [link-arg]: https://doc.rust-lang.org/rustc/codegen-options/index.html#link-arg |
| #[track_caller] |
| pub fn rustc_cdylib_link_arg(flag: &str) { |
| if flag.contains('\n') { |
| panic!("cannot emit rustc-cdylib-link-arg: invalid flag {flag:?}"); |
| } |
| emit("rustc-cdylib-link-arg", flag); |
| } |
| |
| /// The `warning` instruction tells Cargo to display a warning after the build |
| /// script has finished running. |
| /// |
| /// Warnings are only shown for path dependencies |
| /// (that is, those you’re working on locally), so for example warnings printed |
| /// out in [crates.io] crates are not emitted by default. The `-vv` “very verbose” |
| /// flag may be used to have Cargo display warnings for all crates. |
| /// |
| /// [crates.io]: https://crates.io/ |
| #[track_caller] |
| pub fn warning(message: &str) { |
| if message.contains('\n') { |
| panic!("cannot emit warning: message contains newline"); |
| } |
| emit("warning", message); |
| } |
| |
| /// The `error` instruction tells Cargo to display an error after the build script has finished |
| /// running, and then fail the build. |
| /// |
| /// <div class="warning"> |
| /// |
| /// Build script libraries should carefully consider if they want to use [`error`] versus |
| /// returning a `Result`. It may be better to return a `Result`, and allow the caller to decide if the |
| /// error is fatal or not. The caller can then decide whether or not to display the `Err` variant |
| /// using [`error`]. |
| /// |
| /// </div> |
| #[doc = respected_msrv!("1.84")] |
| #[track_caller] |
| pub fn error(message: &str) { |
| if message.contains('\n') { |
| panic!("cannot emit error: message contains newline"); |
| } |
| emit("error", message); |
| } |
| |
| /// Metadata, used by `links` scripts. |
| #[track_caller] |
| pub fn metadata(key: &str, val: &str) { |
| if !is_ascii_ident(key) { |
| panic!("cannot emit metadata: invalid key {key:?}"); |
| } |
| if val.contains('\n') { |
| panic!("cannot emit metadata: invalid value {val:?}"); |
| } |
| |
| emit("metadata", format_args!("{}={}", key, val)); |
| } |