From cf819a94350790843a86874ef513324eebbc1e47 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 7 Nov 2026 23:19:16 +0100 Subject: [PATCH] Build the library for a target without std The `cgp` crate could not be used in a `no_std` environment: `cgp-base-types`, `cgp-runtime`, and `cgp-base` lacked `#![no_std]`, so a build for a target without `std` failed on them, and two defaults turned `alloc` on, so even `default-features = false` pulled it in. The three crates now carry `#![no_std]`. `cgp-error-extra`'s default is empty and its `alloc` feature gates the providers that need an allocator; `cgp-extra`'s default is empty and forwards `alloc`; and `cgp` gained an `alloc` feature that forwards to it. With default features the crate needs neither `std` nor `alloc`. `cgp-no-std-tests` builds the library for `x86_64-unknown-none` with and without `alloc`, so a `std` or `alloc` leak fails the build. --- Cargo.lock | 7 ++ Cargo.toml | 1 + README.md | 14 ++++ crates/core/cgp-base-types/src/lib.rs | 1 + crates/extra/cgp-error-extra/Cargo.toml | 4 +- crates/extra/cgp-error-extra/src/lib.rs | 3 +- crates/extra/cgp-runtime/src/lib.rs | 1 + crates/main/cgp-base/src/lib.rs | 1 + crates/main/cgp-extra/Cargo.toml | 8 ++- crates/main/cgp-extra/src/lib.rs | 3 +- crates/main/cgp/Cargo.toml | 6 ++ crates/tests/README.md | 19 +++++- crates/tests/cgp-no-std-tests/Cargo.toml | 22 +++++++ crates/tests/cgp-no-std-tests/src/lib.rs | 82 ++++++++++++++++++++++++ 14 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 crates/tests/cgp-no-std-tests/Cargo.toml create mode 100644 crates/tests/cgp-no-std-tests/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8eabc49e7..136038e71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,13 @@ dependencies = [ "cgp-handler", ] +[[package]] +name = "cgp-no-std-tests" +version = "0.8.0-alpha" +dependencies = [ + "cgp", +] + [[package]] name = "cgp-run" version = "0.8.0-alpha" diff --git a/Cargo.toml b/Cargo.toml index 90399d73b..c74f6578d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ "crates/tests/cgp-tests", "crates/tests/cgp-macro-tests", + "crates/tests/cgp-no-std-tests", ] [workspace.package] diff --git a/README.md b/README.md index b755b1caa..96e40f500 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,20 @@ use cgp::prelude::*; CGP requires **Rust 1.89+** and runs on the **stable** toolchain, and needs neither a nightly toolchain nor a fork. The [quickstart](https://contextgeneric.dev/docs/quickstart) takes it from there. +### `no_std` + +The `cgp` crate builds for a target without `std`, and its default features need neither `std` nor +`alloc`. Turn on the `alloc` feature for the providers that need an allocator, such as +`DebugError` and `DisplayError`: + +```toml +[dependencies] +cgp = { version = "0.8.0", default-features = false, features = ["alloc"] } +``` + +The error backends are separate crates: `cgp-error-anyhow` and `cgp-error-std` need `alloc`, and +`cgp-error-eyre` needs `std`. + ## A quick look Real operations depend on infrastructure such as a database or object storage, and the usual ways of diff --git a/crates/core/cgp-base-types/src/lib.rs b/crates/core/cgp-base-types/src/lib.rs index 1d8493381..9fe32fb58 100644 --- a/crates/core/cgp-base-types/src/lib.rs +++ b/crates/core/cgp-base-types/src/lib.rs @@ -1,6 +1,7 @@ //! This crate provides CGP's type-level primitives: symbols, lists, sums, and paths. //! //! See [`Symbol!`](https://contextgeneric.dev/docs/reference/macros/symbol), [`Cons`](https://contextgeneric.dev/docs/reference/types/cons), [`Nil`](https://contextgeneric.dev/docs/reference/types/nil), [`Chars`](https://contextgeneric.dev/docs/reference/types/chars), [`PathCons`](https://contextgeneric.dev/docs/reference/types/path_cons), [`StaticFormat`](https://contextgeneric.dev/docs/reference/traits/formatting/static_format), and [`ConcatPath`](https://contextgeneric.dev/docs/reference/traits/formatting/concat_path). +#![no_std] pub mod macro_prelude; pub mod traits; diff --git a/crates/extra/cgp-error-extra/Cargo.toml b/crates/extra/cgp-error-extra/Cargo.toml index a8dcbff1b..5b53a3166 100644 --- a/crates/extra/cgp-error-extra/Cargo.toml +++ b/crates/extra/cgp-error-extra/Cargo.toml @@ -12,7 +12,9 @@ categories = { workspace = true } description = "CGP's generic error providers, which raise, return, discard, and format an error." [features] -default = [ "alloc" ] +default = [] +# The `DebugError` and `DisplayError` providers need an allocator, so they are +# behind this feature. A `no_std` build without one leaves it off. alloc = [] [dependencies] diff --git a/crates/extra/cgp-error-extra/src/lib.rs b/crates/extra/cgp-error-extra/src/lib.rs index c4d515019..97868a236 100644 --- a/crates/extra/cgp-error-extra/src/lib.rs +++ b/crates/extra/cgp-error-extra/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] //! This crate provides CGP's generic error providers, which raise, return, discard, and format an -//! error. +//! error. The `DebugError` and `DisplayError` providers need an allocator, so they are behind the +//! `alloc` feature. //! //! See [`RaiseFrom`](https://contextgeneric.dev/docs/reference/providers/error/raise_from), [`ReturnError`](https://contextgeneric.dev/docs/reference/providers/error/return_error), [`RaiseInfallible`](https://contextgeneric.dev/docs/reference/providers/error/raise_infallible), [`PanicOnError`](https://contextgeneric.dev/docs/reference/providers/error/panic_on_error), [`DiscardDetail`](https://contextgeneric.dev/docs/reference/providers/error/discard_detail), [`DebugError`](https://contextgeneric.dev/docs/reference/providers/error/debug_error), and [`DisplayError`](https://contextgeneric.dev/docs/reference/providers/error/display_error). diff --git a/crates/extra/cgp-runtime/src/lib.rs b/crates/extra/cgp-runtime/src/lib.rs index 3134ef943..fff823c69 100644 --- a/crates/extra/cgp-runtime/src/lib.rs +++ b/crates/extra/cgp-runtime/src/lib.rs @@ -1,6 +1,7 @@ //! This crate provides CGP's runtime abstraction, which lets a context choose its async runtime. //! //! See [`HasRuntime`](https://contextgeneric.dev/docs/reference/components/has_runtime) and [`HasRuntimeType`](https://contextgeneric.dev/docs/reference/components/has_runtime_type). +#![no_std] mod traits; diff --git a/crates/main/cgp-base/src/lib.rs b/crates/main/cgp-base/src/lib.rs index dd67a7949..064bc6897 100644 --- a/crates/main/cgp-base/src/lib.rs +++ b/crates/main/cgp-base/src/lib.rs @@ -2,6 +2,7 @@ //! the macros. //! //! See [`#[cgp_component]`](https://contextgeneric.dev/docs/reference/macros/cgp_component), [`Symbol!`](https://contextgeneric.dev/docs/reference/macros/symbol), and [`Cons`](https://contextgeneric.dev/docs/reference/types/cons). +#![no_std] pub mod macro_prelude; diff --git a/crates/main/cgp-extra/Cargo.toml b/crates/main/cgp-extra/Cargo.toml index 5d1c08941..79778d08b 100644 --- a/crates/main/cgp-extra/Cargo.toml +++ b/crates/main/cgp-extra/Cargo.toml @@ -12,8 +12,12 @@ categories = { workspace = true } description = "CGP's extra layer through cgp::extra: handlers, monads, dispatch, runtime, runners, the optional-field layer, and the generic error providers." [features] -default = [ "full" ] -full = [ "cgp-error-extra/alloc" ] +default = [] +# The `alloc` feature turns on the providers that need an allocator, such as +# `DebugError` and `DisplayError`. A `no_std` build without an allocator leaves it +# off, and `default-features = false` needs neither `std` nor `alloc`. +alloc = [ "cgp-error-extra/alloc" ] +full = [ "alloc" ] [dependencies] cgp-core = { workspace = true } diff --git a/crates/main/cgp-extra/src/lib.rs b/crates/main/cgp-extra/src/lib.rs index b13e7834a..3e612eb9d 100644 --- a/crates/main/cgp-extra/src/lib.rs +++ b/crates/main/cgp-extra/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] //! This crate re-exports CGP's extra layer through `cgp::extra`: handlers, monads, dispatch, -//! runtime, runners, the optional-field layer, and the generic error providers. +//! runtime, runners, the optional-field layer, and the generic error providers. The `alloc` feature +//! turns on the providers that need an allocator. //! //! See [`CanHandle`](https://contextgeneric.dev/docs/reference/components/handler/handler), [`PipeHandlers`](https://contextgeneric.dev/docs/reference/providers/handler/pipe_handlers), and [`RaiseFrom`](https://contextgeneric.dev/docs/reference/providers/error/raise_from). diff --git a/crates/main/cgp/Cargo.toml b/crates/main/cgp/Cargo.toml index 9372a7260..c177a79bb 100644 --- a/crates/main/cgp/Cargo.toml +++ b/crates/main/cgp/Cargo.toml @@ -12,6 +12,12 @@ keywords = { workspace = true } categories = { workspace = true } description = { workspace = true } +[features] +default = [] +# Turn on the providers that need an allocator. A `no_std` build without one leaves +# this off, and `default-features = false` needs neither `std` nor `alloc`. +alloc = [ "cgp-extra/alloc" ] + [dependencies] cgp-core = { workspace = true } cgp-extra = { workspace = true } diff --git a/crates/tests/README.md b/crates/tests/README.md index 34ae2c92b..cb07ad71e 100644 --- a/crates/tests/README.md +++ b/crates/tests/README.md @@ -24,7 +24,11 @@ where the canonical macro-expansion snapshots live. refuses during expansion, and for pinning the invalid tokens a macro currently emits. -A third category lives in another repository: the cases where a macro *accepts* +**`cgp-no-std-tests`** builds the `cgp` crate for a target without `std`, so a `std` or `alloc` leak +in the dependency tree fails the build. It is `#![no_std]`, uses the macros, and gates an alloc-using +provider behind its `alloc` feature. + +A fourth category lives in another repository: the cases where a macro *accepts* input whose *expansion* then fails to compile are UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md), so each is pinned as the readable error the tool renders for it. AGENTS.md's @@ -67,6 +71,19 @@ cargo insta test -p cgp-tests --review # review snapshot diffs interact cargo insta test -p cgp-tests --accept # accept intended snapshot changes ``` +The `no_std` check builds for a target whose sysroot has no `std`, so it fails if any crate in the +tree needs one. Install the target once, then run both paths: + +```sh +rustup target add x86_64-unknown-none +cargo check -p cgp-no-std-tests --target x86_64-unknown-none +cargo check -p cgp-no-std-tests --features alloc --target x86_64-unknown-none +``` + +The first proves that `cgp` with default features needs neither `std` nor `alloc`; the second turns on +the `alloc` feature and compiles the provider that needs an allocator. A `std` use planted in the +crate fails the first command with "cannot find module or crate `std`". + Many tests assert the exact code a macro generates, through the `snapshot_*!` macros from `cgp-macro-test-util`: each emits the real generated code into the module *and* generates a `#[test]` asserting a pretty-printed inline `insta` diff --git a/crates/tests/cgp-no-std-tests/Cargo.toml b/crates/tests/cgp-no-std-tests/Cargo.toml new file mode 100644 index 000000000..74f564ddd --- /dev/null +++ b/crates/tests/cgp-no-std-tests/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "cgp-no-std-tests" +version = "0.8.0-alpha" +edition = { workspace = true } +rust-version = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +homepage = { workspace = true } +keywords = { workspace = true } +categories = { workspace = true } +description = "Builds the cgp crate for a target without std, with and without alloc." +publish = false + +[features] +default = [] +# Turn on the providers that need an allocator, so the same crate checks the +# `alloc` path as well as the plain `no_std` one. +alloc = [ "cgp/alloc" ] + +[dependencies] +cgp = { workspace = true } diff --git a/crates/tests/cgp-no-std-tests/src/lib.rs b/crates/tests/cgp-no-std-tests/src/lib.rs new file mode 100644 index 000000000..da49322f1 --- /dev/null +++ b/crates/tests/cgp-no-std-tests/src/lib.rs @@ -0,0 +1,82 @@ +//! Builds the `cgp` crate for a target without `std`. +//! +//! The crate is `#![no_std]` and uses the macros, so a build for a target that has no `std` fails if +//! any crate in the tree needs it. The `alloc` feature turns on the providers that need an +//! allocator, so the same crate checks both paths: +//! +//! ```sh +//! cargo check -p cgp-no-std-tests --target x86_64-unknown-none +//! cargo check -p cgp-no-std-tests --features alloc --target x86_64-unknown-none +//! ``` +//! +//! See cgp-knowledge-base/cgp/implementation/README.md and the `no_std` inventory in the campaign +//! records. + +#![no_std] + +#[cfg(feature = "alloc")] +extern crate alloc; + +use cgp::prelude::*; + +/// A component, so the build exercises the component macros. +#[cgp_component(Greeter)] +pub trait CanGreet { + /// Greets. + fn greet(&self) -> &'static str; +} + +/// A provider for the component. +#[cgp_impl(new GreetHello)] +impl Greeter { + fn greet(&self) -> &'static str { + "Hello" + } +} + +/// A function-style trait, so the build exercises `#[cgp_fn]`. +/// +/// The implicit argument is a `u32` rather than a `&str`, because the `&str` form +/// lowers to a `String` field bound, which needs `alloc`. +#[cgp_fn] +pub fn double_count(&self, #[implicit] count: u32) -> u32 { + count * 2 +} + +/// A context that wires the component. +pub struct App; + +delegate_components! { + App { + GreeterComponent: GreetHello, + } +} + +check_components! { + App { + GreeterComponent, + } +} + +/// A type-level string, so the build exercises the type-level primitives. +pub type NameTag = Symbol!("name"); + +/// A provider that needs an allocator, compiled only with the `alloc` feature. +#[cfg(feature = "alloc")] +#[cgp_impl(new GreetWithAlloc)] +impl Greeter { + fn greet(&self) -> &'static str { + let greeting = alloc::string::String::from("Hello"); + alloc::boxed::Box::leak(greeting.into_boxed_str()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_context_greets() { + assert_eq!(App.greet(), "Hello"); + } +}