Migrate build-rs to the Cargo repo (#14786)

### What does this PR try to resolve?

Fixes #12432

### How should we test and review this PR?

This pulls in https://github.com/cad97/build-rs at
eb389d1104dae2dc2eb4eafa9d6e821a7dcb45a7 with the following changes:
- `Cargo.toml` metadata
- Removal of `.github`, `.gitignore`, `Cargo.lock`

We'll need to integrate `test-lib` into our processes but that seemed
more invasive, so I wanted to leave that for a future PR.

### Additional information

Infra changes are being coordinated in
https://rust-lang.zulipchat.com/#narrow/channel/242791-t-infra/topic/Transfering.20.60build-rs.60.20crate.20to.20rust-lang/near/480779960

Context: per [Cargo's
charter](https://doc.crates.io/contrib/team.html#decision-process), we
approved this transfer in an
[FCP](https://github.com/rust-lang/cargo/issues/12432#issuecomment-2389528102).
This commit is contained in:
Weihang Lo 2024-11-13 05:13:01 +00:00 committed by GitHub
commit e5ce5e5588
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1242 additions and 2 deletions

18
Cargo.lock generated
View File

@ -252,6 +252,20 @@ dependencies = [
"serde",
]
[[package]]
name = "build-rs"
version = "0.2.0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "build-rs-test-lib"
version = "0.0.0"
dependencies = [
"build-rs",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -3760,9 +3774,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"

View File

@ -24,6 +24,7 @@ anstyle = "1.0.8"
anyhow = "1.0.86"
base64 = "0.22.1"
blake3 = "1.5.2"
build-rs = { version = "0.2.0", path = "crates/build-rs" }
bytesize = "1.3"
cargo = { path = "" }
cargo-credential = { version = "0.4.2", path = "credential/cargo-credential" }
@ -107,6 +108,7 @@ tracing = { version = "0.1.40", default-features = false, features = ["std"] } #
tracing-chrome = "0.7.2"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
unicase = "2.7.0"
unicode-ident = "1.0.13"
unicode-width = "0.2.0"
unicode-xid = "0.2.4"
url = "2.5.2"

View File

@ -0,0 +1,11 @@
[package]
name = "build-rs-test-lib"
version = "0.0.0"
edition.workspace = true
publish = false
[features]
unstable = ["build-rs/unstable"]
[build-dependencies]
build-rs.workspace = true

View File

@ -0,0 +1,78 @@
fn main() {
smoke_test_inputs();
build_rs::output::rerun_if_changed("build.rs");
build_rs::output::rustc_check_cfgs(&["did_run_build_script"]);
build_rs::output::rustc_cfg("did_run_build_script");
}
fn smoke_test_inputs() {
use build_rs::input::*;
dbg!(cargo());
dbg!(cargo_cfg("careful"));
dbg!(cargo_cfg_debug_assertions());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_fmt_debug());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_overflow_checks());
dbg!(cargo_cfg_panic());
dbg!(cargo_cfg_proc_macro());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_relocation_model());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_sanitize());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_sanitizer_cfi_generalize_pointers());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_sanitizer_cfi_normalize_integers());
dbg!(cargo_cfg_target_abi());
dbg!(cargo_cfg_target_arch());
dbg!(cargo_cfg_target_endian());
dbg!(cargo_cfg_target_env());
dbg!(cargo_cfg_target_feature());
dbg!(cargo_cfg_target_has_atomic());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_target_has_atomic_equal_alignment());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_target_has_atomic_load_store());
dbg!(cargo_cfg_target_os());
dbg!(cargo_cfg_target_pointer_width());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_target_thread_local());
dbg!(cargo_cfg_target_vendor());
#[cfg(feature = "unstable")]
dbg!(cargo_cfg_ub_checks());
dbg!(cargo_cfg_unix());
dbg!(cargo_cfg_windows());
dbg!(cargo_encoded_rustflags());
dbg!(cargo_feature("unstable"));
dbg!(cargo_manifest_dir());
dbg!(cargo_manifest_links());
dbg!(cargo_pkg_authors());
dbg!(cargo_pkg_description());
dbg!(cargo_pkg_homepage());
dbg!(cargo_pkg_license());
dbg!(cargo_pkg_license_file());
dbg!(cargo_pkg_name());
dbg!(cargo_pkg_readme());
dbg!(cargo_pkg_repository());
dbg!(cargo_pkg_rust_version());
dbg!(cargo_pkg_version());
dbg!(cargo_pkg_version_major());
dbg!(cargo_pkg_version_minor());
dbg!(cargo_pkg_version_patch());
dbg!(cargo_pkg_version_pre());
dbg!(debug());
dbg!(dep_metadata("z", "include"));
dbg!(host());
dbg!(num_jobs());
dbg!(opt_level());
dbg!(out_dir());
dbg!(profile());
dbg!(rustc());
dbg!(rustc_linker());
dbg!(rustc_workspace_wrapper());
dbg!(rustc_wrapper());
dbg!(rustdoc());
dbg!(target());
}

View File

@ -0,0 +1,4 @@
#[test]
fn test() {
assert!(cfg!(did_run_build_script));
}

View File

@ -0,0 +1,16 @@
[package]
name = "build-rs"
version = "0.2.0"
rust-version.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "API for writing Cargo `build.rs` files"
[features]
## Experimental API. This feature flag is **NOT** semver stable.
unstable = []
[dependencies]
unicode-ident.workspace = true

View File

@ -0,0 +1,2 @@
> This crate is maintained by the Cargo team for use by the wider
> ecosystem. This crate follows semver compatibility for its APIs.

View File

@ -0,0 +1,46 @@
use std::{process::Command, sync::OnceLock};
fn rust_version_minor() -> u32 {
static VERSION_MINOR: OnceLock<u32> = OnceLock::new();
*VERSION_MINOR.get_or_init(|| {
crate::input::cargo_pkg_rust_version()
.split('.')
.nth(1)
// assume build-rs's MSRV if none specified for the current package
.unwrap_or(env!("CARGO_PKG_RUST_VERSION").split('.').nth(1).unwrap())
.parse()
.unwrap()
})
}
fn cargo_version_minor() -> u32 {
static VERSION_MINOR: OnceLock<u32> = OnceLock::new();
*VERSION_MINOR.get_or_init(|| {
let out = Command::new(crate::input::cargo())
.arg("-V")
.output()
.expect("running `cargo -V` should succeed");
assert!(out.status.success(), "running `cargo -V` should succeed");
// > cargo -V # example output
// cargo 1.82.0 (8f40fc59f 2024-08-21)
String::from_utf8(out.stdout).expect("`cargo -V` should output valid UTF-8")
["cargo 1.".len()..]
.split('.')
.next()
.expect("`cargo -V` format should be stable")
.parse()
.unwrap()
})
}
pub(crate) fn double_colon_directives() -> bool {
// cargo errors on `cargo::` directives with insufficient package.rust-version
rust_version_minor() >= 77
}
pub(crate) fn check_cfg() -> bool {
// emit check-cfg if the toolchain being used supports it
cargo_version_minor() >= 80
}

View File

@ -0,0 +1,27 @@
use unicode_ident::{is_xid_continue, is_xid_start};
pub(crate) fn is_feature_name(s: &str) -> bool {
s.chars()
.all(|ch| is_xid_continue(ch) || matches!(ch, '-' | '+' | '.'))
}
pub(crate) fn is_ident(s: &str) -> bool {
let mut cs = s.chars();
cs.next()
.is_some_and(|ch| is_xid_start(ch) || matches!(ch, '_'))
&& cs.all(is_xid_continue)
}
pub(crate) fn is_ascii_ident(s: &str) -> bool {
let mut cs = s.chars();
cs.next()
.is_some_and(|ch| ch.is_ascii_alphabetic() || matches!(ch, '_'))
&& cs.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_'))
}
pub(crate) fn is_crate_name(s: &str) -> bool {
let mut cs = s.chars();
cs.next()
.is_some_and(|ch| is_xid_start(ch) || matches!(ch, '-' | '_'))
&& cs.all(|ch| is_xid_continue(ch) || matches!(ch, '-'))
}

View File

@ -0,0 +1,591 @@
//! Inputs from the build system to the build script.
//!
//! This crate does not do any caching or interpreting of the values provided by
//! Cargo beyond the communication protocol itself. It is up to the build script
//! to interpret the string values and decide what to do with them.
//!
//! Reference: <https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>
use crate::ident::{is_ascii_ident, is_crate_name, is_feature_name};
use std::{
env,
fmt::Display,
path::PathBuf,
str::{self, FromStr},
};
macro_rules! missing {
($key:expr) => {
panic!("cargo environment variable `{}` is missing", $key)
};
}
macro_rules! invalid {
($key:expr, $err:expr) => {
panic!("cargo environment variable `{}` is invalid: {}", $key, $err)
};
}
#[track_caller]
fn get_bool(key: &str) -> bool {
env::var_os(key).is_some()
}
#[track_caller]
fn get_opt_path(key: &str) -> Option<PathBuf> {
let var = env::var_os(key)?;
Some(PathBuf::from(var))
}
#[track_caller]
fn get_path(key: &str) -> PathBuf {
get_opt_path(key).unwrap_or_else(|| missing!(key))
}
#[track_caller]
fn get_opt_str(key: &str) -> Option<String> {
let var = env::var_os(key)?;
match str::from_utf8(var.as_encoded_bytes()) {
Ok(s) => Some(s.to_owned()),
Err(err) => invalid!(key, err),
}
}
#[track_caller]
fn get_str(key: &str) -> String {
get_opt_str(key).unwrap_or_else(|| missing!(key))
}
#[track_caller]
fn get_num<T: FromStr>(key: &str) -> T
where
T::Err: Display,
{
let val = get_str(key);
match val.parse() {
Ok(num) => num,
Err(err) => invalid!(key, err),
}
}
#[track_caller]
fn get_opt_cfg(cfg: &str) -> (String, Option<Vec<String>>) {
if !is_ascii_ident(cfg) {
panic!("invalid configuration option {cfg:?}")
}
let cfg = cfg.to_uppercase().replace('-', "_");
let key = format!("CARGO_CFG_{cfg}");
let Some(var) = env::var_os(&key) else {
return (key, None);
};
let val = str::from_utf8(var.as_encoded_bytes()).unwrap_or_else(|err| invalid!(key, err));
(key, Some(val.split(',').map(str::to_owned).collect()))
}
#[track_caller]
fn get_cfg(cfg: &str) -> Vec<String> {
let (key, val) = get_opt_cfg(cfg);
val.unwrap_or_else(|| missing!(key))
}
// docs last updated to match release 1.82.0 reference
/// Path to the `cargo` binary performing the build.
#[track_caller]
pub fn cargo() -> PathBuf {
get_path("CARGO")
}
/// The directory containing the manifest for the package being built (the package
/// containing the build script). Also note that this is the value of the current
/// working directory of the build script when it starts.
#[track_caller]
pub fn cargo_manifest_dir() -> PathBuf {
get_path("CARGO_MANIFEST_DIR")
}
/// Contains parameters needed for Cargos [jobserver] implementation to parallelize
/// subprocesses. Rustc or cargo invocations from build.rs can already read
/// `CARGO_MAKEFLAGS`, but GNU Make requires the flags to be specified either
/// directly as arguments, or through the `MAKEFLAGS` environment variable.
/// Currently Cargo doesnt set the `MAKEFLAGS` variable, but its free for build
/// scripts invoking GNU Make to set it to the contents of `CARGO_MAKEFLAGS`.
///
/// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
#[track_caller]
pub fn cargo_manifest_links() -> Option<String> {
get_opt_str("CARGO_MANIFEST_LINKS")
}
/// For each activated feature of the package being built, this will be `true`.
#[track_caller]
pub fn cargo_feature(name: &str) -> bool {
if !is_feature_name(name) {
panic!("invalid feature name {name:?}")
}
let name = name.to_uppercase().replace('-', "_");
let key = format!("CARGO_FEATURE_{name}");
get_bool(&key)
}
/// For each [configuration option] of the package being built, this will contain
/// the value of the configuration. This includes values built-in to the compiler
/// (which can be seen with `rustc --print=cfg`) and values set by build scripts
/// and extra flags passed to rustc (such as those defined in `RUSTFLAGS`).
///
/// [configuration option]: https://doc.rust-lang.org/stable/reference/conditional-compilation.html
#[track_caller]
pub fn cargo_cfg(cfg: &str) -> Option<Vec<String>> {
let (_, val) = get_opt_cfg(cfg);
val
}
pub use self::cfg::*;
mod cfg {
use super::*;
// those disabled with #[cfg(any())] don't seem meaningfully useful
// but we list all cfg that are default known to check-cfg
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_clippy() -> bool {
get_bool("CARGO_CFG_CLIPPY")
}
/// If we are compiling with debug assertions enabled.
#[track_caller]
pub fn cargo_cfg_debug_assertions() -> bool {
get_bool("CARGO_CFG_DEBUG_ASSERTIONS")
}
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_doc() -> bool {
get_bool("CARGO_CFG_DOC")
}
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_docsrs() -> bool {
get_bool("CARGO_CFG_DOCSRS")
}
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_doctest() -> bool {
get_bool("CARGO_CFG_DOCTEST")
}
/// The level of detail provided by derived [`Debug`] implementations.
#[doc = unstable!(fmt_dbg, 129709)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_fmt_debug() -> String {
get_str("CARGO_CFG_FMT_DEBUG")
}
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_miri() -> bool {
get_bool("CARGO_CFG_MIRI")
}
/// If we are compiling with overflow checks enabled.
#[doc = unstable!(cfg_overflow_checks, 111466)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_overflow_checks() -> bool {
get_bool("CARGO_CFG_OVERFLOW_CHECKS")
}
/// The [panic strategy](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#panic).
#[track_caller]
pub fn cargo_cfg_panic() -> String {
get_str("CARGO_CFG_PANIC")
}
/// If the crate is being compiled as a procedural macro.
#[track_caller]
pub fn cargo_cfg_proc_macro() -> bool {
get_bool("CARGO_CFG_PROC_MACRO")
}
/// The target relocation model.
#[doc = unstable!(cfg_relocation_model, 114929)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_relocation_model() -> String {
get_str("CARGO_CFG_RELOCATION_MODEL")
}
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_rustfmt() -> bool {
get_bool("CARGO_CFG_RUSTFMT")
}
/// Sanitizers enabled for the crate being compiled.
#[doc = unstable!(cfg_sanitize, 39699)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_sanitize() -> Option<Vec<String>> {
let (_, val) = get_opt_cfg("CARGO_CFG_SANITIZE");
val
}
/// If CFI sanitization is generalizing pointers.
#[doc = unstable!(cfg_sanitizer_cfi, 89653)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_sanitizer_cfi_generalize_pointers() -> bool {
get_bool("CARGO_CFG_SANITIZER_CFI_GENERALIZE_POINTERS")
}
/// If CFI sanitization is normalizing integers.
#[doc = unstable!(cfg_sanitizer_cfi, 89653)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_sanitizer_cfi_normalize_integers() -> bool {
get_bool("CARGO_CFG_SANITIZER_CFI_NORMALIZE_INTEGERS")
}
/// Disambiguation of the [target ABI](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_abi)
/// when the [target env](cargo_cfg_target_env) isn't sufficient.
///
/// For historical reasons, this value is only defined as not the empty-string when
/// actually needed for disambiguation. Thus, for example, on many GNU platforms,
/// this value will be empty.
#[track_caller]
pub fn cargo_cfg_target_abi() -> String {
get_str("CARGO_CFG_TARGET_ABI")
}
/// The CPU [target architecture](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_arch).
/// This is similar to the first element of the platform's target triple, but not identical.
#[track_caller]
pub fn cargo_cfg_target_arch() -> String {
get_str("CARGO_CFG_TARGET_ARCH")
}
/// The CPU [target endianness](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_endian).
#[track_caller]
pub fn cargo_cfg_target_endian() -> String {
get_str("CARGO_CFG_TARGET_ENDIAN")
}
/// The [target environment](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_env) ABI.
/// This value is similar to the fourth element of the platform's target triple.
///
/// For historical reasons, this value is only defined as not the empty-string when
/// actually needed for disambiguation. Thus, for example, on many GNU platforms,
/// this value will be empty.
#[track_caller]
pub fn cargo_cfg_target_env() -> String {
get_str("CARGO_CFG_TARGET_ENV")
}
/// The [target family](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_family).
#[track_caller]
pub fn cargo_target_family() -> Vec<String> {
get_cfg("target_family")
}
/// List of CPU [target features](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_feature) enabled.
#[track_caller]
pub fn cargo_cfg_target_feature() -> Vec<String> {
get_cfg("target_feature")
}
/// List of CPU [supported atomic widths](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_has_atomic).
#[track_caller]
pub fn cargo_cfg_target_has_atomic() -> Vec<String> {
get_cfg("target_has_atomic")
}
/// List of atomic widths that have equal alignment requirements.
#[doc = unstable!(cfg_target_has_atomic_equal_alignment, 93822)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_target_has_atomic_equal_alignment() -> Vec<String> {
get_cfg("target_has_atomic_equal_alignment")
}
/// List of atomic widths that have atomic load and store operations.
#[doc = unstable!(cfg_target_has_atomic_load_store, 94039)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_target_has_atomic_load_store() -> Vec<String> {
get_cfg("target_has_atomic_load_store")
}
/// The [target operating system](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_os).
/// This value is similar to the second and third element of the platform's target triple.
#[track_caller]
pub fn cargo_cfg_target_os() -> String {
get_str("CARGO_CFG_TARGET_OS")
}
/// The CPU [pointer width](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_pointer_width).
#[track_caller]
pub fn cargo_cfg_target_pointer_width() -> u32 {
get_num("CARGO_CFG_TARGET_POINTER_WIDTH")
}
/// If the target supports thread-local storage.
#[doc = unstable!(cfg_target_thread_local, 29594)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_target_thread_local() -> bool {
get_bool("CARGO_CFG_TARGET_THREAD_LOCAL")
}
/// The [target vendor](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_vendor).
#[track_caller]
pub fn cargo_cfg_target_vendor() -> String {
get_str("CARGO_CFG_TARGET_VENDOR")
}
#[cfg(any())]
#[track_caller]
pub fn cargo_cfg_test() -> bool {
get_bool("CARGO_CFG_TEST")
}
/// If we are compiling with UB checks enabled.
#[doc = unstable!(cfg_ub_checks, 123499)]
#[cfg(feature = "unstable")]
#[track_caller]
pub fn cargo_cfg_ub_checks() -> bool {
get_bool("CARGO_CFG_UB_CHECKS")
}
/// Set on [unix-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows).
#[track_caller]
pub fn cargo_cfg_unix() -> bool {
get_bool("CARGO_CFG_UNIX")
}
/// Set on [windows-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows).
#[track_caller]
pub fn cargo_cfg_windows() -> bool {
get_bool("CARGO_CFG_WINDOWS")
}
}
/// The folder in which all output and intermediate artifacts should be placed.
/// This folder is inside the build directory for the package being built, and
/// it is unique for the package in question.
#[track_caller]
pub fn out_dir() -> PathBuf {
get_path("OUT_DIR")
}
/// The [target triple] that is being compiled for. Native code should be compiled
/// for this triple.
///
/// [target triple]: https://doc.rust-lang.org/stable/cargo/appendix/glossary.html#target
#[track_caller]
pub fn target() -> String {
get_str("TARGET")
}
/// The host triple of the Rust compiler.
#[track_caller]
pub fn host() -> String {
get_str("HOST")
}
/// The parallelism specified as the top-level parallelism. This can be useful to
/// pass a `-j` parameter to a system like `make`. Note that care should be taken
/// when interpreting this value. For historical purposes this is still provided
/// but Cargo, for example, does not need to run `make -j`, and instead can set the
/// `MAKEFLAGS` env var to the content of `CARGO_MAKEFLAGS` to activate the use of
/// Cargos GNU Make compatible [jobserver] for sub-make invocations.
///
/// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
#[track_caller]
pub fn num_jobs() -> u32 {
get_num("NUM_JOBS")
}
/// The [level of optimization](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#opt-level).
#[track_caller]
pub fn opt_level() -> String {
get_str("OPT_LEVEL")
}
/// The amount of [debug information](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#debug) included.
#[track_caller]
pub fn debug() -> String {
get_str("DEBUG")
}
/// `release` for release builds, `debug` for other builds. This is determined based
/// on if the [profile] inherits from the [`dev`] or [`release`] profile. Using this
/// function is not recommended. Using other functions like [`opt_level`] provides
/// a more correct view of the actual settings being used.
///
/// [profile]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html
/// [`dev`]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#dev
/// [`release`]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#release
#[track_caller]
pub fn profile() -> String {
get_str("PROFILE")
}
/// [Metadata] set by dependencies. For more information, see build script
/// documentation about [the `links` manifest key][links].
///
/// [metadata]: crate::output::metadata
/// [links]: https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#the-links-manifest-key
#[track_caller]
pub fn dep_metadata(name: &str, key: &str) -> Option<String> {
if !is_crate_name(name) {
panic!("invalid dependency name {name:?}")
}
if !is_ascii_ident(key) {
panic!("invalid metadata key {key:?}")
}
let name = name.to_uppercase().replace('-', "_");
let key = key.to_uppercase().replace('-', "_");
let key = format!("DEP_{name}_{key}");
get_opt_str(&key)
}
/// The compiler that Cargo has resolved to use.
#[track_caller]
pub fn rustc() -> PathBuf {
get_path("RUSTC")
}
/// The documentation generator that Cargo has resolved to use.
#[track_caller]
pub fn rustdoc() -> PathBuf {
get_path("RUSTDOC")
}
/// The rustc wrapper, if any, that Cargo is using. See [`build.rustc-wrapper`].
///
/// [`build.rustc-wrapper`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustc-wrapper
#[track_caller]
pub fn rustc_wrapper() -> Option<PathBuf> {
get_opt_path("RUSTC_WRAPPER")
}
/// The rustc wrapper, if any, that Cargo is using for workspace members. See
/// [`build.rustc-workspace-wrapper`].
///
/// [`build.rustc-workspace-wrapper`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustc-workspace-wrapper
#[track_caller]
pub fn rustc_workspace_wrapper() -> Option<PathBuf> {
get_opt_path("RUSTC_WORKSPACE_WRAPPER")
}
/// The linker that Cargo has resolved to use for the current target, if specified.
///
/// [`target.*.linker`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#targettriplelinker
#[track_caller]
pub fn rustc_linker() -> Option<PathBuf> {
get_opt_path("RUSTC_LINKER")
}
/// Extra flags that Cargo invokes rustc with. See [`build.rustflags`].
///
/// [`build.rustflags`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustflags
#[track_caller]
pub fn cargo_encoded_rustflags() -> Vec<String> {
get_str("CARGO_ENCODED_RUSTFLAGS")
.split('\x1f')
.map(str::to_owned)
.collect()
}
/// The full version of your package.
#[track_caller]
pub fn cargo_pkg_version() -> String {
get_str("CARGO_PKG_VERSION")
}
/// The major version of your package.
#[track_caller]
pub fn cargo_pkg_version_major() -> u64 {
get_num("CARGO_PKG_VERSION_MAJOR")
}
/// The minor version of your package.
#[track_caller]
pub fn cargo_pkg_version_minor() -> u64 {
get_num("CARGO_PKG_VERSION_MINOR")
}
/// The patch version of your package.
#[track_caller]
pub fn cargo_pkg_version_patch() -> u64 {
get_num("CARGO_PKG_VERSION_PATCH")
}
/// The pre-release version of your package.
#[track_caller]
pub fn cargo_pkg_version_pre() -> String {
get_str("CARGO_PKG_VERSION_PRE")
}
/// Colon separated list of authors from the manifest of your package.
#[track_caller]
pub fn cargo_pkg_authors() -> Vec<String> {
get_str("CARGO_PKG_AUTHORS")
.split(':')
.map(str::to_owned)
.collect()
}
/// The name of your package.
#[track_caller]
pub fn cargo_pkg_name() -> String {
get_str("CARGO_PKG_NAME")
}
/// The description from the manifest of your package.
#[track_caller]
pub fn cargo_pkg_description() -> String {
get_str("CARGO_PKG_DESCRIPTION")
}
/// The home page from the manifest of your package.
#[track_caller]
pub fn cargo_pkg_homepage() -> String {
get_str("CARGO_PKG_HOMEPAGE")
}
/// The repository from the manifest of your package.
#[track_caller]
pub fn cargo_pkg_repository() -> String {
get_str("CARGO_PKG_REPOSITORY")
}
/// The license from the manifest of your package.
#[track_caller]
pub fn cargo_pkg_license() -> String {
get_str("CARGO_PKG_LICENSE")
}
/// The license file from the manifest of your package.
#[track_caller]
pub fn cargo_pkg_license_file() -> PathBuf {
get_path("CARGO_PKG_LICENSE_FILE")
}
/// The Rust version from the manifest of your package. Note that this is the
/// minimum Rust version supported by the package, not the current Rust version.
#[track_caller]
pub fn cargo_pkg_rust_version() -> String {
get_str("CARGO_PKG_RUST_VERSION")
}
/// Path to the README file of your package.
#[track_caller]
pub fn cargo_pkg_readme() -> PathBuf {
get_path("CARGO_PKG_README")
}

View File

@ -0,0 +1,39 @@
//! build-rs provides a strongly typed interface around the Cargo build script
//! protocol. Cargo provides inputs to the build script by environment variable
//! and accepts commands by printing to stdout.
//!
//! > This crate is maintained by the Cargo team for use by the wider
//! > ecosystem. This crate follows semver compatibility for its APIs.
#![cfg_attr(all(doc, feature = "unstable"), feature(doc_auto_cfg, doc_cfg))]
#![allow(clippy::disallowed_methods)] // HACK: deferred resoling this
#![allow(clippy::print_stdout)] // HACK: deferred resoling this
#[cfg(feature = "unstable")]
macro_rules! unstable {
($feature:ident, $issue:literal) => {
concat!(
r#"<div class="stab unstable">"#,
r#"<span class="emoji">🔬</span>"#,
r#"<span>This is a nightly-only experimental API. (<code>"#,
stringify!($feature),
r#"</code>&nbsp;<a href="https://github.com/rust-lang/rust/issues/"#,
$issue,
r#"">#"#,
$issue,
r#"</a>)</span>"#,
r#"</div>"#
)
};
}
macro_rules! msrv {
($ver:literal) => {
concat!("> MSRV: Respected as of ", $ver, ".")
};
}
mod allow_use;
mod ident;
pub mod input;
pub mod output;

View File

@ -0,0 +1,408 @@
//! 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 crate::{
allow_use,
ident::{is_ascii_ident, is_ident},
};
use std::{ffi::OsStr, fmt::Display, fmt::Write, path::Path, str};
fn emit(directive: &str, value: impl Display) {
if allow_use::double_colon_directives() {
println!("cargo::{}={}", directive, value);
} else {
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 = 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:?}");
}
}
if allow_use::check_cfg() {
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 = 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;
}
if allow_use::check_cfg() {
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);
}
/// 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:?}");
}
if allow_use::double_colon_directives() {
emit("metadata", format_args!("{}={}", key, val));
} else {
emit(key, val);
}
}

View File

@ -184,6 +184,7 @@ fn bump_check(args: &clap::ArgMatches, gctx: &cargo::util::GlobalContext) -> Car
let mut cmd = ProcessBuilder::new("cargo");
cmd.arg("semver-checks")
.arg("--workspace")
.args(&["--exclude", "build-rs"]) // FIXME: Remove once 1.84 is stable.
.arg("--baseline-rev")
.arg(referenced_commit.id().to_string());
for krate in crates_not_check_against_channels {

View File

@ -170,6 +170,7 @@ The degree of process is correlated with the degree of change being proposed:
Per the [Rust crate ownership policy](https://forge.rust-lang.org/policies/crate-ownership.html), the Cargo team's "Intentional Artifacts" include:
- [build-rs](https://crates.io/crates/build-rs)
- [cargo-credential](https://crates.io/crates/cargo-credential)
- [cargo-platform](https://crates.io/crates/cargo-platform)
- [cargo-util-schemas](https://crates.io/crates/cargo-util-schemas)