From e4912cee0bfedd88d8a3b3bc2b28a6189809bcc5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 5 Nov 2024 13:21:18 -0600 Subject: [PATCH] Migrate build-rs to the Cargo repo 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 seemeed more invasive, so I wanted to leave that for a future PR. 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). Fixes #12432 --- Cargo.lock | 4 + crates/build-rs/Cargo.toml | 15 + crates/build-rs/src/allow_use.rs | 17 ++ crates/build-rs/src/input.rs | 406 ++++++++++++++++++++++++++++ crates/build-rs/src/lib.rs | 26 ++ crates/build-rs/src/output.rs | 315 +++++++++++++++++++++ crates/build-rs/test-lib/Cargo.toml | 9 + crates/build-rs/test-lib/build.rs | 4 + crates/build-rs/test-lib/src/lib.rs | 4 + publish.py | 1 + 10 files changed, 801 insertions(+) create mode 100644 crates/build-rs/Cargo.toml create mode 100644 crates/build-rs/src/allow_use.rs create mode 100644 crates/build-rs/src/input.rs create mode 100644 crates/build-rs/src/lib.rs create mode 100644 crates/build-rs/src/output.rs create mode 100644 crates/build-rs/test-lib/Cargo.toml create mode 100644 crates/build-rs/test-lib/build.rs create mode 100644 crates/build-rs/test-lib/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1d3d0db6c69c..64e1c3550d6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,6 +252,10 @@ dependencies = [ "serde", ] +[[package]] +name = "build-rs" +version = "0.2.0" + [[package]] name = "bumpalo" version = "3.16.0" diff --git a/crates/build-rs/Cargo.toml b/crates/build-rs/Cargo.toml new file mode 100644 index 000000000000..4cf8ab87ea26 --- /dev/null +++ b/crates/build-rs/Cargo.toml @@ -0,0 +1,15 @@ +[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] +unstable = [] + +[lints] +workspace = true diff --git a/crates/build-rs/src/allow_use.rs b/crates/build-rs/src/allow_use.rs new file mode 100644 index 000000000000..fb39e9cfd298 --- /dev/null +++ b/crates/build-rs/src/allow_use.rs @@ -0,0 +1,17 @@ +use std::sync::OnceLock; + +fn rust_version_minor() -> u32 { + static VERSION_MINOR: OnceLock = OnceLock::new(); + *VERSION_MINOR.get_or_init(|| { + crate::input::cargo_pkg_rust_version() + .split('.') + .nth(1) + .unwrap_or("70") + .parse() + .unwrap() + }) +} + +pub(crate) fn double_colon_directives() -> bool { + rust_version_minor() >= 77 +} diff --git a/crates/build-rs/src/input.rs b/crates/build-rs/src/input.rs new file mode 100644 index 000000000000..10a41965ba2f --- /dev/null +++ b/crates/build-rs/src/input.rs @@ -0,0 +1,406 @@ +//! 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: + +use std::{ + env, + fmt::Display, + path::PathBuf, + str::{self, FromStr}, +}; + +macro_rules! missing { + ($key:ident) => { + panic!("cargo environment variable `{}` is missing", $key) + }; +} + +macro_rules! invalid { + ($key:ident, $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 { + 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 { + 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(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>) { + 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 { + let (key, val) = get_opt_cfg(cfg); + val.unwrap_or_else(|| missing!(key)) +} + +// docs last updated to match release 1.77.2 reference + +/// Path to the `cargo` binary performing the build. +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. +pub fn cargo_manifest_dir() -> PathBuf { + get_path("CARGO_MANIFEST_DIR") +} + +/// Contains parameters needed for Cargo’s [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 doesn’t set the `MAKEFLAGS` variable, but it’s 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 +pub fn cargo_manifest_links() -> Option { + get_opt_str("CARGO_MANIFEST_LINKS") +} + +/// For each activated feature of the package being built, this will be `true`. +pub fn cargo_feature(name: &str) -> bool { + 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 +pub fn cargo_cfg(cfg: &str) -> Option> { + let (_, val) = get_opt_cfg(cfg); + val +} + +/// Set on [unix-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows). +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). +pub fn cargo_cfg_windows() -> bool { + get_bool("CARGO_CFG_WINDOWS") +} + +/// The [target family](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_family). +pub fn cargo_target_family() -> Vec { + get_cfg("target_family") +} + +/// 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. +pub fn cargo_cfg_target_os() -> String { + get_str("CARGO_CFG_TARGET_OS") +} + +/// 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. +pub fn cargo_cfg_target_arch() -> String { + get_str("CARGO_CFG_TARGET_ARCH") +} + +/// The [target vendor](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_vendor). +pub fn cargo_cfg_target_vendor() -> String { + get_str("CARGO_CFG_TARGET_VENDOR") +} + +/// 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. +pub fn cargo_cfg_target_env() -> String { + get_str("CARGO_CFG_TARGET_ENV") +} + +/// The CPU [pointer width](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_pointer_width). +pub fn cargo_cfg_target_pointer_width() -> u32 { + get_num("CARGO_CFG_TARGET_POINTER_WIDTH") +} + +/// The CPU [target endianness](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_endian). +pub fn cargo_cfg_target_endian() -> String { + get_str("CARGO_CFG_TARGET_ENDIAN") +} + +/// List of CPU [target features](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_feature) enabled. +pub fn cargo_cfg_target_feature() -> Vec { + get_cfg("target_feature") +} + +/// List of CPU [supported atomic widths](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_has_atomic). +pub fn cargo_cfg_target_has_atomic() -> Vec { + 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")] +pub fn cargo_cfg_target_has_atomic_equal_alignment() -> Vec { + 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")] +pub fn cargo_cfg_target_has_atomic_load_store() -> Vec { + get_cfg("target_has_atomic_load_store") +} + +/// If the target supports thread-local storage. +/// +#[doc = unstable!(cfg_target_thread_local, 29594)] +#[cfg(feature = "unstable")] +pub fn cargo_cfg_target_thread_local() -> bool { + get_bool("CARGO_CFG_TARGET_THREAD_LOCAL") +} + +/// The [panic strategy](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#panic). +pub fn cargo_cfg_panic() -> String { + get_str("CARGO_CFG_PANIC") +} + +/// If we are compiling with debug assertions enabled. +pub fn cargo_cfg_debug_assertions() -> bool { + get_bool("CARGO_CFG_DEBUG_ASSERTIONS") +} + +/// If we are compiling with overflow checks enabled. +/// +#[doc = unstable!(cfg_overflow_checks, 111466)] +#[cfg(feature = "unstable")] +pub fn cargo_cfg_overflow_checks() -> bool { + get_bool("CARGO_CFG_OVERFLOW_CHECKS") +} + +/// If we are compiling with UB checks enabled. +/// +#[doc = unstable!(cfg_ub_checks, 123499)] +#[cfg(feature = "unstable")] +pub fn cargo_cfg_ub_checks() -> bool { + get_bool("CARGO_CFG_UB_CHECKS") +} + +/// The target relocation model. +/// +#[doc = unstable!(cfg_relocation_model, 114929)] +#[cfg(feature = "unstable")] +pub fn cargo_cfg_relocation_model() -> String { + get_str("CARGO_CFG_RELOCATION_MODEL") +} + +/// 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. +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 +pub fn target() -> String { + get_str("TARGET") +} + +/// The host triple of the Rust compiler. +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 +/// Cargo’s GNU Make compatible [jobserver] for sub-make invocations. +/// +/// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html +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). +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. +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 +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 +pub fn dep(name: &str, key: &str) -> Option { + 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. +pub fn rustc() -> PathBuf { + get_path("RUSTC") +} + +/// The documentation generator that Cargo has resolved to use. +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 +pub fn rustc_wrapper() -> Option { + 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 +pub fn rustc_workspace_wrapper() -> Option { + 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 +pub fn rustc_linker() -> Option { + 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 +pub fn cargo_encoded_rustflags() -> Vec { + get_str("CARGO_ENCODED_RUSTFLAGS") + .split('\x1f') + .map(str::to_owned) + .collect() +} + +/// The full version of your package. +pub fn cargo_pkg_version() -> String { + get_str("CARGO_PKG_VERSION") +} + +/// The major version of your package. +pub fn cargo_pkg_version_major() -> u64 { + get_num("CARGO_PKG_VERSION_MAJOR") +} + +/// The minor version of your package. +pub fn cargo_pkg_version_minor() -> u64 { + get_num("CARGO_PKG_VERSION_MINOR") +} + +/// The patch version of your package. +pub fn cargo_pkg_version_patch() -> u64 { + get_num("CARGO_PKG_VERSION_PATCH") +} + +/// The pre-release version of your package. +pub fn cargo_pkg_version_pre() -> String { + get_str("CARGO_PKG_VERSION_PRE") +} + +/// Colon separated list of authors from the manifest of your package. +pub fn cargo_pkg_authors() -> Vec { + get_str("CARGO_PKG_AUTHORS") + .split(':') + .map(str::to_owned) + .collect() +} + +/// The name of your package. +pub fn cargo_pkg_name() -> String { + get_str("CARGO_PKG_NAME") +} + +/// The description from the manifest of your package. +pub fn cargo_pkg_description() -> String { + get_str("CARGO_PKG_DESCRIPTION") +} + +/// 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. +pub fn cargo_pkg_rust_version() -> String { + get_str("CARGO_PKG_RUST_VERSION") +} diff --git a/crates/build-rs/src/lib.rs b/crates/build-rs/src/lib.rs new file mode 100644 index 000000000000..0637fe2565a5 --- /dev/null +++ b/crates/build-rs/src/lib.rs @@ -0,0 +1,26 @@ +//! 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. +#![cfg_attr(all(doc, feature = "unstable"), feature(doc_auto_cfg, doc_cfg))] + +#[cfg(feature = "unstable")] +macro_rules! unstable { + ($feature:ident, $issue:literal) => { + concat!( + r#"
"#, + r#"🔬"#, + r#"This is a nightly-only experimental API. ("#, + stringify!($feature), + r#" #"#, + $issue, + r#")"#, + r#"
"# + ) + }; +} + +mod allow_use; +pub mod input; +pub mod output; diff --git a/crates/build-rs/src/output.rs b/crates/build-rs/src/output.rs new file mode 100644 index 000000000000..f02734e8271e --- /dev/null +++ b/crates/build-rs/src/output.rs @@ -0,0 +1,315 @@ +//! 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: + +use crate::allow_use; +use std::{ffi::OsStr, fmt::Display, 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. +pub fn rerun_if_changed(path: impl AsRef) { + 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 +pub fn rerun_if_env_changed(key: impl AsRef) { + 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 +pub fn rustc_link_arg(flag: &str) { + if flag.contains('\n') { + panic!("cannot emit rustc-link-arg: invalid 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 +pub fn rustc_link_arg_bin(bin: &str, flag: &str) { + if bin.contains(['=', '\n']) { + panic!("cannot emit rustc-link-arg-bin: invalid bin name"); + } + 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 +pub fn rustc_link_arg_bins(flag: &str) { + if flag.contains('\n') { + panic!("cannot emit rustc-link-arg-bins: invalid 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. +pub fn rustc_link_arg_tests(flag: &str) { + if flag.contains('\n') { + panic!("cannot emit rustc-link-arg-tests: invalid 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. +pub fn rustc_link_arg_examples(flag: &str) { + if flag.contains('\n') { + panic!("cannot emit rustc-link-arg-examples: invalid 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. +pub fn rustc_link_arg_benches(flag: &str) { + if flag.contains('\n') { + panic!("cannot emit rustc-link-arg-benches: invalid 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 +pub fn rustc_link_lib(lib: &str) { + if lib.contains('\n') { + panic!("cannot emit rustc-link-lib: invalid lib"); + } + emit("rustc-link-lib", lib); +} + +/// Like [`rustc_link_lib`], but with KIND specified separately. +pub fn rustc_link_lib_kind(kind: &str, lib: &str) { + if kind.contains(['=', '\n']) { + panic!("cannot emit rustc-link-lib: invalid kind"); + } + if lib.contains('\n') { + panic!("cannot emit rustc-link-lib: invalid 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 +pub fn rustc_link_search(path: impl AsRef) { + 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. +pub fn rustc_link_search_kind(kind: &str, path: impl AsRef) { + if kind.contains(['=', '\n']) { + panic!("cannot emit rustc-link-search: invalid 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`]. +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 +pub fn rustc_cfg(key: &str) { + if key.contains('\n') { + panic!("cannot emit rustc-cfg: invalid key"); + } + emit("rustc-cfg", key); +} + +/// Like [`rustc_cfg`], but with the value specified separately. +pub fn rustc_cfg_value(key: &str, value: &str) { + let value = value.escape_default(); + if key.contains(['=', '\n']) { + panic!("cannot emit rustc-cfg-value: invalid key"); + } + emit("rustc-cfg", format_args!("{key}=\"{value}\"")); +} + +/// 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 +pub fn rustc_env(key: &str, value: &str) { + if key.contains(['=', '\n']) { + panic!("cannot emit rustc-env: invalid key"); + } + if value.contains('\n') { + panic!("cannot emit rustc-env: invalid 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 +pub fn rustc_cdylib_link_arg(flag: &str) { + if flag.contains('\n') { + panic!("cannot emit rustc-cdylib-link-arg: invalid 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/ +pub fn warning(message: &str) { + if message.contains('\n') { + panic!("cannot emit warning: message contains newline"); + } + emit("warning", message); +} + +/// Metadata, used by `links` scripts. +pub fn metadata(key: &str, val: &str) { + if key.contains(['=', '\n']) { + panic!("cannot emit metadata: invalid key"); + } + if val.contains('\n') { + panic!("cannot emit metadata: invalid value"); + } + if allow_use::double_colon_directives() { + emit("metadata", format_args!("{}={}", key, val)); + } else { + emit(key, val); + } +} diff --git a/crates/build-rs/test-lib/Cargo.toml b/crates/build-rs/test-lib/Cargo.toml new file mode 100644 index 000000000000..e354bb96416e --- /dev/null +++ b/crates/build-rs/test-lib/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "build-rs-test-lib" +version = "0.2.0" +edition = "2021" +rust-version = "1.76" +publish = false + +[build-dependencies] +build-rs = { path = ".." } diff --git a/crates/build-rs/test-lib/build.rs b/crates/build-rs/test-lib/build.rs new file mode 100644 index 000000000000..b59bb77643b5 --- /dev/null +++ b/crates/build-rs/test-lib/build.rs @@ -0,0 +1,4 @@ +fn main() { + build_rs::output::rerun_if_changed("build.rs"); + build_rs::output::rustc_cfg("did_run_build_script"); +} diff --git a/crates/build-rs/test-lib/src/lib.rs b/crates/build-rs/test-lib/src/lib.rs new file mode 100644 index 000000000000..871cc4ee586b --- /dev/null +++ b/crates/build-rs/test-lib/src/lib.rs @@ -0,0 +1,4 @@ +#[test] +fn test() { + assert!(cfg!(did_run_build_script)); +} diff --git a/publish.py b/publish.py index f01cbc666c09..cc267bd2c0fb 100755 --- a/publish.py +++ b/publish.py @@ -24,6 +24,7 @@ 'credential/cargo-credential-wincred', 'credential/cargo-credential-1password', 'credential/cargo-credential-macos-keychain', + 'crates/build-rs', 'crates/rustfix', 'crates/cargo-platform', 'crates/cargo-util',