diff --git a/ninja-build_rs/Cargo.toml b/ninja-build_rs/Cargo.toml index c187b9c..e31e712 100644 --- a/ninja-build_rs/Cargo.toml +++ b/ninja-build_rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ninja-build_rs" -version = "0.3.0" +version = "0.4.0" edition = "2024" readme = "README.md" description = "build script helpers for working with nightly" @@ -13,9 +13,9 @@ license = "MIT" [dependencies] autocfg = "1.5.1" - derive_more.version = "2.1.1" derive_more.features = ["display"] +indexmap = "2.14.0" [dev-dependencies] tempfile = "3.27.0" diff --git a/ninja-build_rs/README.md b/ninja-build_rs/README.md index 0957174..758cf38 100644 --- a/ninja-build_rs/README.md +++ b/ninja-build_rs/README.md @@ -1,7 +1,37 @@ # ninja-build_rs Designed to help create good build scripts, with a focus on ease of use for you, -valuable output in `cargo build -vv` & no annoying surprises for anyone downstream. +valuable output in cargo build -vv & no annoying surprises for anyone downstream. + +## Usage + +```rust +use ninja_build_rs::prelude::*; +// Result uses BuildError to give meaningful messages +fn main() -> Result<()> { + // get an environment variable and re-run build script if it changes. + let my_var: String = get_var("MY_VAR")?; + // get values from an environment variable, separated by the + // OS path separator and re-run build script if it changes. + let my_vals: IndexSet = split_var("MY_VALUES")?; + if my_vals.contains("some_value") { + unimplemented!("do something") + } + // get a new AutoCfg or provide a valuable error + // rather than panicing. + let ac = AutoCfg::new()?; + // check to see if the downstream crate has defined + // `unstable.allow-features` in `.cargo/config.toml`. + // It is mandatory to perform this check and pass the + // result to any calls to `emit_unstable_feature` + let allowed_features = cargo_allowed_features()?; + // We want to make use of `assert_matches` if it is available + ac.emit_unstable_feature(assert_matches, &allowed_features); + // ^^^^^^^^^^^^^^ - enum variant to avoid typos + Ok(()) +} + +``` ## Prelude @@ -19,3 +49,17 @@ provides: enum [`UnstableFeature`](nightly::UnstableFeature) to provide a safe way to identify the availability of nightly features & handle the future stabilisation process without additional effort on your part. All while respecting any `allow-feature` whitelists. + +## Note to downstream crates + +If you (transiently) depend on a crate which uses `ninja-build_rs` and have implemented a +whitelist of `allowed-features`. + +Due to limitations in the information provided by cargo: + +- This will obtain config.toml files based upon `OUT_DIR`. If this is not under the project + root, you can override by providing an alternative path via the environment variable + `NINJA_CARGO_CONFIG_DIR`. See cargo's documentation on config file hierarchical structure + for more details. +- This will not respect additional entries passed at the command line via + `cargo --config unstable.allow-features=[...]` diff --git a/ninja-build_rs/src/lib.rs b/ninja-build_rs/src/lib.rs index ac05517..85607c5 100644 --- a/ninja-build_rs/src/lib.rs +++ b/ninja-build_rs/src/lib.rs @@ -1,7 +1,44 @@ //! Designed to help create good build scripts, with a focus on ease of use for you, -//! valuable output in `cargo build -vv` & no annoying surprises for anyone downstream. +//! valuable output in cargo build -vv & no annoying surprises for anyone downstream. //! -//! ## Prelude +//! # Usage +//! +//! ```rust, no_run +//! # use indexmap::IndexSet; +//! use ninja_build_rs::prelude::*; +//! +//! // Result uses BuildError to give meaningful messages +//! fn main() -> Result<()> { +//! +//! // get an environment variable and re-run build script if it changes. +//! let my_var: String = get_var("MY_VAR")?; +//! +//! // get values from an environment variable, separated by the +//! // OS path separator and re-run build script if it changes. +//! let my_vals: IndexSet = split_var("MY_VALUES")?; +//! if my_vals.contains("some_value") { +//! unimplemented!("do something") +//! } +//! +//! // get a new AutoCfg or provide a valuable error +//! // rather than panicing. +//! let ac = AutoCfg::new()?; +//! +//! // check to see if the downstream crate has defined +//! // `unstable.allow-features` in `.cargo/config.toml`. +//! // It is mandatory to perform this check and pass the +//! // result to any calls to `emit_unstable_feature` +//! let allowed_features = cargo_allowed_features()?; +//! +//! // We want to make use of `assert_matches` if it is available +//! ac.emit_unstable_feature(assert_matches, &allowed_features); +//! // ^^^^^^^^^^^^^^ - enum variant to avoid typos +//! +//! Ok(()) +//! } +//! ``` +//! +//! # Prelude //! //! ```rust //! use ninja_build_rs::prelude::*; @@ -17,7 +54,11 @@ //! enum [`UnstableFeature`](nightly::UnstableFeature) to provide a safe way to identify the //! availability of nightly features & handle the future stabilisation process without additional //! effort on your part. All while respecting any `allow-feature` whitelists. -use std::{collections::HashSet, env::VarError, ffi::OsString}; +//! + +use std::{env::VarError, ffi::OsString}; + +use indexmap::IndexSet; /// Recommended prelude: `use ninja-build_rs::prelude::*` /// @@ -36,7 +77,8 @@ pub mod prelude { pub mod nightly; -/// Attempt to get an environment variable. +/// Attempt to get an environment variable, re-run build if it changes or provide a meaningful +/// error if missing. /// /// - Emits `cargo::rerun-if-env-changed=key` to ensure changes trigger a rebuild. /// - If not found the error returned will include the key name in the debug representation. @@ -45,11 +87,13 @@ pub fn get_var(key: &str) -> Result { std::env::var(key).map_err(|err| BuildError::from_var_error(key, err)) } -/// Attempt to get an environment variable and split the values using the OS path separator. +/// Attempt to get an environment variable and split the values using the OS path separator, +/// re-run build if it changes or provide a meaningful error if missing. /// /// - Emits `cargo::rerun-if-env-changed=key` to ensure changes trigger a rebuild. /// - If not found the error returned will include the key name in the debug representation. -pub fn split_var(key: &str) -> Result> { +/// - Returns an [IndexSet] which implements `.contains()` AND retains ordering +pub fn split_var(key: &str) -> Result> { Ok(std::env::split_paths(&get_var(key)?) .map(|p| p.to_string_lossy().to_string()) .collect()) @@ -75,7 +119,7 @@ pub enum BuildError { /// /// outputs `IOError(error details)` to stderr IOError(std::io::Error), - /// An error when creating or using [AutoCfg] + /// An error when creating or using [autocfg] /// /// outputs `AutoCfgError(error details)` AutoCfgError(autocfg::Error), diff --git a/ninja-build_rs/src/nightly.rs b/ninja-build_rs/src/nightly.rs index caf0c57..c93124b 100644 --- a/ninja-build_rs/src/nightly.rs +++ b/ninja-build_rs/src/nightly.rs @@ -1,3 +1,4 @@ +#![expect(clippy::test_attr_in_doctest)] //! Checking for experimental or stabilised features is prone to subtle errors which create issues //! for downstream users and verbose when done properly. This provides extensions to the amazing //! [autocfg::AutoCfg] (re-exported via our prelude to make your life easier) to safely identify the @@ -6,11 +7,11 @@ //! //! For a list of known features with dedicated probes see [UnstableFeature] //! -//! ## Usage +//! # Usage //! -//! ### `build.rs` +//! ## In `build.rs` //! -//! ```rust, should_panic +//! ```rust, no_run //! use ninja_build_rs::prelude::*; //! //! fn main() -> Result<()> { @@ -32,9 +33,9 @@ //! } //! ``` //! -//! ### `lib.rs` / `main.rs` +//! ## In `lib.rs` / `main.rs` //! -//! ```rust, ignore +//! ```rust //! // only enable unstable feature if it is available and has not yet been stabilised //! #![cfg_attr(unstable_assert_matches, feature(assert_matches))] //! @@ -67,7 +68,7 @@ //! } //! ``` //! -//! ## Note to downstream crates +//! # Note to downstream crates //! //! If you (transiently) depend on a crate which uses `ninja-build_rs` and have implemented a //! whitelist of `allowed-features`. @@ -87,20 +88,22 @@ use std::{ process::{Command, Output}, }; +/// re-exported from autocfg +/// pub use autocfg::AutoCfg; use derive_more::Display; use crate::{BuildError, Result, get_var}; use probes::{has, make_probe, unstable}; -/// Known features with `unstable_...` & `has_...`. +/// Known features with `unstable_...` & dedicated probes for `has_...`. /// /// If the feature you want is not in this list you can use `Other` to get `unstable_...` /// but please also raise a PR (or open an issue) to add a custom probe for `has_...`. #[allow(non_camel_case_types, reason = "shadowing feature naming")] #[derive(Debug, Clone, PartialEq, Eq, Display)] pub enum UnstableFeature { - /// ### Provides cfg flags: + /// ## Provides cfg flags: /// - `#![cfg_attr(unstable_assert_matches, feature(assert_matches))]` /// - `#[cfg(has_assert_matches)]` /// - ```rust, ignore @@ -112,23 +115,23 @@ pub enum UnstableFeature { /// use std::assert_matches::assert_matches; /// ``` assert_matches, - /// ### Provides cfg flags: + /// ## Provides cfg flags: /// - `#![cfg_attr(unstable_iterator_try_collect, feature(iterator_try_collect))]` /// - `#[cfg(has_iterator_try_collect)]` iterator_try_collect, - /// ### Provides cfg flags: + /// ## Provides cfg flags: /// - `#![cfg_attr(unstable_never_type, feature(never_type))]` /// - `#[cfg(has_never_type)]` never_type, - /// ### Provides cfg flags: + /// ## Provides cfg flags: /// - `#![cfg_attr(unstable_proc_macro_diagnostic, feature(proc_macro_diagnostic))]` /// - `#[cfg(has_proc_macro_diagnostic)]` proc_macro_diagnostic, - /// ### Provides cfg flags: + /// ## Provides cfg flags: /// - `#![cfg_attr(unstable_try_trait_v2, feature(try_trait_v2))]` /// - `#[cfg(has_try_trait_v2)]` try_trait_v2, - /// ### Provides cfg flags: + /// ## Provides cfg flags: /// - `#![cfg_attr(unstable_try_trait_v2_residual, feature(try_trait_v2_residual))]` /// - `#[cfg(has_try_trait_v2_residual)]` try_trait_v2_residual, @@ -271,25 +274,28 @@ use std::ops::Residual; } } +/// Adds [`AutoCfg::emit_unstable_feature`](Nightly::emit_unstable_feature) pub trait Nightly { /// Offers at least 2 cfg flags for all [known features](UnstableFeature) /// - /// You must pass a set of [AllowedFeatures], created by calling [cargo_allowed_features] + /// # Feature enablement: `cfg(unstable_...)` /// - /// ## cfg `unstable_...` /// - To be used at top-level crate via `#![cfg_attr(unstable_foo, feature(foo))]` /// - /// ## Cfg-gating `has_...` - /// Do **not** rely on `cfg(not(unstable_foo))` to suggest that `feature(foo)` is stable! There - /// are 3 reasons that `cfg(unstable_foo)` could be `false`: - /// 1. The build is using `stable`/`beta` or the feature is not on the `allow-features` whitelist - /// 2. The feature has been stabilised - /// 3. The compiler is from before the feature was implemented + /// # Cfg-gating: `cfg(has_...)` + /// + /// - **Do not rely on `#[cfg(not(unstable_foo))]` to suggest that `feature(foo)` is stable!** + /// - There are 3 reasons that `#[cfg(unstable_foo)]` could be `false`: + /// 1. The build is using `stable`/`beta` or the feature is not on the `allow-features` whitelist + /// 2. The feature has been stabilised + /// 3. The compiler is from before the feature was implemented + /// - All [known features](UnstableFeature) have a `#[cfg(has_...)]` for this purpose. /// - /// All [known features](UnstableFeature) have a `has_...` cfg for this purpose. + /// # Note /// - /// If you need to test that a feature is available in order to cfg-gate your code and it is not - /// on the list of [known features](UnstableFeature), please raise a PR with a suggested probe. + /// - You must pass a set of [AllowedFeatures], created by calling [cargo_allowed_features] + /// - If you need to test that a feature is available in order to cfg-gate your code and it is not + /// on the list of [known features](UnstableFeature), please raise a PR with a suggested probe. fn emit_unstable_feature(&self, feature: UnstableFeature, allowed_features: &AllowedFeatures); } diff --git a/ninja-xtask/Cargo.lock b/ninja-xtask/Cargo.lock index 5279552..8b15137 100644 --- a/ninja-xtask/Cargo.lock +++ b/ninja-xtask/Cargo.lock @@ -396,6 +396,8 @@ dependencies = [ [[package]] name = "ninja-build_rs" version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6720c3e3e82280cd954a93e999da565924aab15e13716b08cea9c0ce6f0d70" dependencies = [ "autocfg", "derive_more", @@ -848,3 +850,7 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[patch.unused]] +name = "ninja-build_rs" +version = "0.4.0"