cargo/crates/build-rs/src/output.rs
2024-12-09 14:00:56 -06:00

438 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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 compilers [`-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 targets 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 Cargos 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 crates 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 youre 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));
}