mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
Make the C++ SMLib kernel opt-in at build time (feature smlib, off by default)
solidcore, the pure-Rust kernel, is always built and stays the default. The `smlib` feature (apps, engine, kernel; solidcraft-smlib's `native`) now has to be asked for to compile and link SMLib: the app, the CLI and the web app drop it from `default`, and solidcraft-gpu's dev-dependency no longer forces it on. Without it no C++ toolchain, fetch-smlib.sh, prebuilt or build.rs work is needed. On a build without it, asking for the C++ kernel (SOLIDCRAFT_KERNEL=smlib, diagnostics.kernel, the rig) is refused with Error::NotBuilt, which now says "this build doesn't include the C++ SMLib kernel; build with --features smlib", and SOLIDCRAFT_KERNEL=smlib logs that once. `cargo xtask kernel-cases` builds the CLI with --features smlib only when --kernel smlib or both asks for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdcgEtwSrdwZxqTQp5MfnU
This commit is contained in:
@@ -20,11 +20,12 @@ name = "solvecraft-cli"
|
||||
path = "src/alias.rs"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "smlib", "profile"]
|
||||
default = ["raytrace-cpu", "profile"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
# Link SMLib into the kernel (solidcraft-engine/smlib; needs scripts/fetch-smlib.sh). SMLib is the
|
||||
# only kernel, so without it the command line models nothing.
|
||||
# Also link the C++ SMLib kernel (solidcraft-engine/smlib), off by default: needs a C++ toolchain
|
||||
# and scripts/fetch-smlib.sh. solidcore, the Rust kernel, is always built and is the default either
|
||||
# way; with this on, SOLIDCRAFT_KERNEL=smlib or the Geometry kernel preference can choose C++.
|
||||
smlib = ["solidcraft-engine/smlib"]
|
||||
# The profiler, compiled in so any build can say where its time goes (`--profile`,
|
||||
# SOLIDCRAFT_PROFILE=1); it records nothing until asked.
|
||||
|
||||
@@ -695,7 +695,7 @@ fn check_case(dir: &str, dumps: Option<&std::path::Path>, validation: solidcraft
|
||||
&& solidcraft_engine::kernel::kernel_route::backend() == solidcraft_engine::kernel::kernel_route::Backend::Smlib
|
||||
{
|
||||
out["status"] = json!("unsupported");
|
||||
out["error"] = json!("this build has no SMLib: build solidcraft-cli with --features smlib");
|
||||
out["error"] = json!("this build doesn't include the C++ SMLib kernel; build solidcraft-cli with --features smlib");
|
||||
return out;
|
||||
}
|
||||
// `refused`: only a clean refusal is right; `solid_or_refused`: a clean refusal, or a result
|
||||
|
||||
@@ -14,11 +14,11 @@ name = "solidcraft-web"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["smlib", "profile"]
|
||||
default = ["profile"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
# Link SMLib into the kernel (solidcraft-engine/smlib; needs scripts/fetch-smlib.sh
|
||||
# wasm32-unknown-unknown, or the sources and wasi-sdk in WASI_SDK_PATH, crates/smlib/build.rs). SMLib
|
||||
# is the only kernel, so without it the web app models nothing.
|
||||
# Also link the C++ SMLib kernel (solidcraft-engine/smlib), off by default: needs
|
||||
# scripts/fetch-smlib.sh wasm32-unknown-unknown, or the sources and wasi-sdk in WASI_SDK_PATH
|
||||
# (crates/smlib/build.rs). solidcore, the Rust kernel, is always built and is the default either way.
|
||||
smlib = ["solidcraft-engine/smlib"]
|
||||
# The profiler, compiled in so any build can say where its time goes (the Profile operations
|
||||
# preference, SOLIDCRAFT_PROFILE=1, diagnostics.profile); it records nothing until asked.
|
||||
|
||||
@@ -8,11 +8,12 @@ publish = false
|
||||
default-run = "solidcraft"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "bundled-render-library", "smlib", "profile"]
|
||||
default = ["raytrace-cpu", "bundled-render-library", "profile"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
# Link SMLib into the kernel (solidcraft-engine/smlib; needs scripts/fetch-smlib.sh). SMLib is the
|
||||
# only kernel, so without it the app models nothing.
|
||||
# Also link the C++ SMLib kernel (solidcraft-engine/smlib), off by default: needs a C++ toolchain
|
||||
# and scripts/fetch-smlib.sh. solidcore, the Rust kernel, is always built and is the default either
|
||||
# way; with this on, SOLIDCRAFT_KERNEL=smlib or the Geometry kernel preference can choose C++.
|
||||
smlib = ["solidcraft-engine/smlib"]
|
||||
# The profiler, compiled in so any build can say where its time goes (the Profile operations
|
||||
# preference, SOLIDCRAFT_PROFILE=1, diagnostics.profile); it records nothing until asked.
|
||||
|
||||
@@ -13,7 +13,7 @@ default = []
|
||||
# Optional authored texture/HDR bytes; the command registry and metadata remain available.
|
||||
bundled-render-library = []
|
||||
raytrace-cpu = ["solidcraft-render/raytrace-cpu"]
|
||||
# Link SMLib into the kernel (solidcraft-kernel/smlib; needs scripts/fetch-smlib.sh).
|
||||
# Also link the C++ SMLib kernel (solidcraft-kernel/smlib, off by default; needs scripts/fetch-smlib.sh).
|
||||
smlib = ["solidcraft-kernel/smlib"]
|
||||
# Turns on the parameter-contract audit recorder (`SOLIDCRAFT_PARAM_AUDIT`, see
|
||||
# `src/schema/record.rs`), for checking a new contract against the whole test suite.
|
||||
|
||||
@@ -78,7 +78,7 @@ pub(crate) fn kernel_asked(choice: &Value, built: bool) -> Result<solidcraft_ker
|
||||
use solidcraft_kernel::kernel_route::Backend;
|
||||
let asked = choice.as_str().and_then(Backend::from_id).ok_or_else(|| bad("diagnostics.kernel", "`choice` is solidcore or smlib"))?;
|
||||
if asked == Backend::Smlib && !built {
|
||||
return Err(EngineError::Disabled("diagnostics.kernel".into(), "this build has no C++ SMLib kernel".into()));
|
||||
return Err(EngineError::Disabled("diagnostics.kernel".into(), "this build doesn't include the C++ SMLib kernel; build with --features smlib".into()));
|
||||
}
|
||||
Ok(asked)
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ wgpu = { workspace = true, features = ["metal", "vulkan", "dx12", "gles"] }
|
||||
[dev-dependencies]
|
||||
image = { workspace = true }
|
||||
solidcraft-kernel = { workspace = true }
|
||||
# Headless pictures of real designs (capture tests), built by the real kernel: SMLib
|
||||
# (scripts/fetch-smlib.sh), as in the apps.
|
||||
solidcraft-engine = { workspace = true, features = ["bundled-render-library", "smlib"] }
|
||||
# Headless pictures of real designs (capture tests), built by the real kernel: solidcore, as in the
|
||||
# apps (the C++ SMLib kernel only when the build turns on solidcraft-engine/smlib).
|
||||
solidcraft-engine = { workspace = true, features = ["bundled-render-library"] }
|
||||
solidcraft-io = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
# The HLSL the D3D12 backend compiles (tests check it suits FXC).
|
||||
|
||||
@@ -9,9 +9,9 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
# Link NVIDIA's SMLib (tangency-labs/smlib) into the kernel; needs a C++ toolchain and
|
||||
# scripts/fetch-smlib.sh (ADR 0002). Without it every geometric operation answers that SMLib
|
||||
# was not built.
|
||||
# Also link NVIDIA's C++ SMLib (tangency-labs/smlib), off by default; needs a C++ toolchain and
|
||||
# scripts/fetch-smlib.sh (ADR 0002). solidcore, the Rust port, answers every operation either way;
|
||||
# without this, asking for the C++ kernel (SOLIDCRAFT_KERNEL=smlib) is refused as not built.
|
||||
smlib = ["solidcraft-smlib/native"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -56,7 +56,7 @@ fn smlib_validity(body: &Body) -> Option<SmlibCheck> {
|
||||
|
||||
/// Whether this build links SMLib (the `smlib` feature).
|
||||
pub fn smlib_built() -> bool {
|
||||
cfg!(feature = "smlib")
|
||||
solidcraft_smlib::built()
|
||||
}
|
||||
|
||||
/// Where every kernel call that fails from now on leaves its inputs for replay
|
||||
|
||||
@@ -20,8 +20,10 @@ type_complexity = "allow"
|
||||
neg_cmp_op_on_partial_ord = "allow"
|
||||
|
||||
[features]
|
||||
# Compile and link SMLib. Off, the crate builds without a C++ toolchain or the SMLib sources, and
|
||||
# every call returns `Error::NotBuilt`, so workspace-wide builds and checks need neither.
|
||||
# Compile and link the C++ SMLib (the apps' and the kernel's `smlib` feature turns it on; off by
|
||||
# default). Off, the crate builds without a C++ toolchain, the SMLib sources or a prebuilt library,
|
||||
# build.rs does nothing, solidcore answers every operation, and a call asked of the C++ kernel
|
||||
# returns `Error::NotBuilt`.
|
||||
native = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
+15
-4
@@ -6,8 +6,10 @@
|
||||
//! the handles, turns status codes into [`Error`], and sends SMLib's one-line-per-call log to the
|
||||
//! `log` crate (target `smlib`).
|
||||
//!
|
||||
//! Built without the `native` feature, the crate needs no C++ toolchain and every call returns
|
||||
//! [`Error::NotBuilt`].
|
||||
//! The `native` feature (the apps' `smlib`, off by default) compiles and links the C++ kernel.
|
||||
//! Without it the crate needs no C++ toolchain, no SMLib sources and no prebuilt library: solidcore
|
||||
//! answers every operation, and asking for the C++ kernel (`SOLIDCRAFT_KERNEL=smlib`) makes every
|
||||
//! call that would reach it return [`Error::NotBuilt`], never the other kernel's answer.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
use std::ffi::CString;
|
||||
@@ -77,7 +79,8 @@ pub enum Error {
|
||||
Exception,
|
||||
/// The C API is a different version from the one this crate was written against.
|
||||
Version(i32),
|
||||
/// This build has no SMLib (the `native` feature is off).
|
||||
/// The C++ kernel was asked for on a build without it (the `native` feature, which the apps'
|
||||
/// `smlib` feature turns on, is off): [`NOT_BUILT`].
|
||||
NotBuilt,
|
||||
/// A B-rep record SMLib cannot take (see [`import::Unsupported`]).
|
||||
Record(import::Unsupported),
|
||||
@@ -123,7 +126,7 @@ impl std::fmt::Display for Error {
|
||||
}
|
||||
Error::Exception => write!(f, "SMLib raised an internal exception"),
|
||||
Error::Version(v) => write!(f, "SMLib C API version {v}, expected {}", ffi::ABI_VERSION),
|
||||
Error::NotBuilt => write!(f, "not supported yet: this build has no SMLib"),
|
||||
Error::NotBuilt => write!(f, "{NOT_BUILT}"),
|
||||
Error::Record(u) => write!(f, "{u}"),
|
||||
Error::Solidcore(e) => write!(f, "solidcore: {e}"),
|
||||
Error::Internal(e) => write!(f, "solidcore failed internally in {e}"),
|
||||
@@ -136,6 +139,14 @@ impl std::error::Error for Error {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// What [`Error::NotBuilt`] says: the C++ kernel is opt-in at build time, and how to opt in.
|
||||
pub const NOT_BUILT: &str = "this build doesn't include the C++ SMLib kernel; build with --features smlib";
|
||||
|
||||
/// Whether this build links the C++ SMLib kernel (the `native` feature).
|
||||
pub const fn built() -> bool {
|
||||
cfg!(feature = "native")
|
||||
}
|
||||
|
||||
fn error(s: i32) -> Error {
|
||||
match s {
|
||||
ffi::ERR_ARG | ffi::ERR_BUFFER => Error::Argument,
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
//! The C++ kernel is an opt-in: for a test, a rig or a user who wants the old behaviour. A call that
|
||||
//! fails on solidcore fails, and a path solidcore has no answer for says it is not supported: there is
|
||||
//! no fallback to the other kernel at run time, which would hide its bugs. Asking for the C++ kernel on
|
||||
//! a build without it (wasm included) stays on it too: every call answers [`crate::Error::NotBuilt`].
|
||||
//! a build without it (the default, wasm included) stays on it too: every call answers
|
||||
//! [`crate::Error::NotBuilt`], which says to build with `--features smlib`.
|
||||
//!
|
||||
//! `SOLIDCRAFT_KERNEL=smlib|solidcore` chooses for the whole process (the rig, the test matrix);
|
||||
//! otherwise the preference ([`set_choice`]) does, and it defaults to solidcore.
|
||||
@@ -104,6 +105,9 @@ fn environment() -> Option<Backend> {
|
||||
if c.is_none() {
|
||||
log::warn!(target: "smlib", "SOLIDCRAFT_KERNEL={v}: not smlib or solidcore; ignored");
|
||||
}
|
||||
if c == Some(Backend::Smlib) && !crate::built() {
|
||||
log::warn!(target: "smlib", "SOLIDCRAFT_KERNEL=smlib: {}; every operation will be refused", crate::NOT_BUILT);
|
||||
}
|
||||
c
|
||||
})
|
||||
}
|
||||
|
||||
@@ -144,6 +144,9 @@ fn without_native_every_call_says_so() {
|
||||
// solidcore builds a box without SMLib; asking the C++ kernel about it says there is none
|
||||
let b = core::make_box([0.0; 3], [1.0; 3]).map(Brep::made_core).unwrap();
|
||||
assert_eq!(b.cpp().err(), Some(Error::NotBuilt));
|
||||
// and says how to get it
|
||||
assert!(!built());
|
||||
assert!(Error::NotBuilt.to_string().contains("--features smlib"), "{}", Error::NotBuilt);
|
||||
}
|
||||
|
||||
/// The dump folder is process-wide: the tests that set it take turns.
|
||||
|
||||
@@ -10,12 +10,13 @@ runs them on solidcore; `--kernel smlib` runs them on C++ and writes
|
||||
cargo xtask kernel-cases # every case, on solidcore
|
||||
cargo xtask kernel-cases --cli target/release/solidcraft-cli --out /tmp/run # with a CLI already built
|
||||
cargo xtask kernel-cases --kernel both --cli target/release/solidcraft-cli # SMLib and solidcore side by side
|
||||
cargo run -p solidcraft-cli --features smlib -- kernel-cases tests/kernel-cases/booleans/<id> # one case
|
||||
cargo run -p solidcraft-cli -- kernel-cases tests/kernel-cases/booleans/<id> # one case
|
||||
```
|
||||
|
||||
The CLI links SMLib by default (`scripts/fetch-smlib.sh` fetches the prebuilt library first);
|
||||
built with `--no-default-features`, solidcore still answers every operation, and asking for SMLib
|
||||
answers that it is not built. The run's output folder (`--out`, default `target/kernel-cases`)
|
||||
The CLI builds with solidcore alone by default. The C++ SMLib kernel is opt-in at build time:
|
||||
`--features smlib` links it (`scripts/fetch-smlib.sh` fetches the prebuilt library first), and
|
||||
`--kernel smlib` or `both` without `--cli` builds the CLI that way. On a build without it, asking
|
||||
for SMLib answers that this build doesn't include it. The run's output folder (`--out`, default `target/kernel-cases`)
|
||||
gets `results.json`, every case's full result, and `dumps/<area>-<case>/`: the inputs of each
|
||||
SMLib call a case failed in, for replay (the CLI's `--dumps=DIR`, which the rig always passes).
|
||||
`--validation MODE` runs the cases' features under that mode of SMLib's check
|
||||
@@ -137,7 +138,7 @@ the frozen truck-geometry PRs, a `kernel-paused` PR), for the report's coverage
|
||||
Every area is a regression suite: each case is one small case of one issue, with its exact
|
||||
answer. `.github/workflows/kernel-rig.yml` runs them, and the ladder, on pull requests (not
|
||||
drafts). A plan job (`scripts/ci/kernel_rig_plan.py`) picks the areas from the changed files, one
|
||||
job builds `solidcraft-cli` (release, SMLib) and the rig's runner, and a matrix of shards, one per
|
||||
job builds `solidcraft-cli` (release, `--features smlib`, for the ladder's `--kernel smlib`) and the rig's runner, and a matrix of shards, one per
|
||||
selected area plus the ladder, runs them in parallel; the `kernel-rig` check passes when every
|
||||
selected shard does (and when none was selected). Pushes to main and develop, merge groups, the
|
||||
release gate (`release-gate.yml`) and a run by hand run every area. Each area's shard runs:
|
||||
|
||||
@@ -166,13 +166,20 @@ fn wait_draining(mut child: std::process::Child, timeout: Duration) -> Result<Op
|
||||
Ok(Some(std::process::Output { status, stdout: join(stdout)?, stderr: join(stderr)? }))
|
||||
}
|
||||
|
||||
/// The features `cargo run` builds the CLI with for a run on `kernels`: the C++ kernel is opt-in at
|
||||
/// build time (`--features smlib`), so only a run that asks for it builds it. One build for the
|
||||
/// whole run (`both` included), so the cases don't rebuild the CLI between kernels.
|
||||
fn cli_features(kernels: &[&str]) -> &'static [&'static str] {
|
||||
if kernels.contains(&"smlib") { &["--features", "smlib"] } else { &[] }
|
||||
}
|
||||
|
||||
fn run_case(o: &Options, case: &Path, kernel: &str) -> Value {
|
||||
let rel = case.strip_prefix(root()).unwrap_or(case).to_path_buf();
|
||||
let mut cmd = match &o.cli {
|
||||
Some(bin) => Command::new(bin),
|
||||
None => {
|
||||
let mut c = cargo();
|
||||
c.args(["run", "-q", "--release", "-p", "solidcraft-cli", "--features", "smlib", "--"]);
|
||||
c.args(["run", "-q", "--release", "-p", "solidcraft-cli"]).args(cli_features(&o.kernels)).arg("--");
|
||||
c
|
||||
}
|
||||
};
|
||||
@@ -827,6 +834,16 @@ mod tests {
|
||||
assert_eq!(options(&["--kernel", "both"], "x").unwrap().kernels, ["smlib", "solidcore"]);
|
||||
}
|
||||
|
||||
/// Only a run that asks for the C++ kernel builds the CLI with it.
|
||||
#[test]
|
||||
fn the_cli_is_built_with_the_cpp_kernel_only_when_a_run_asks_for_it() {
|
||||
let features = |args: &[&str]| cli_features(&options(args, "x").unwrap().kernels).to_vec();
|
||||
assert!(features(&[]).is_empty());
|
||||
assert!(features(&["--kernel", "solidcore"]).is_empty());
|
||||
assert_eq!(features(&["--kernel", "smlib"]), ["--features", "smlib"]);
|
||||
assert_eq!(features(&["--kernel", "both"]), ["--features", "smlib"]);
|
||||
}
|
||||
|
||||
fn gate_on(text: &str, cases: &[(&str, &str)]) -> Result<String, String> {
|
||||
let names: Vec<String> = cases.iter().map(|(c, _)| c.to_string()).collect();
|
||||
let statuses: Vec<&str> = cases.iter().map(|(_, s)| *s).collect();
|
||||
|
||||
+3
-1
@@ -58,7 +58,9 @@ commands:
|
||||
--kernel solidcore|smlib|both (default solidcore) sets SOLIDCRAFT_KERNEL per case:
|
||||
solidcore keeps results.json, dumps/ and report.md in DIR/solidcore/; smlib
|
||||
writes docs/kernel-cases.md; both runs each case on each, and also writes the
|
||||
side by side to docs/kernel-cases-backends.md and DIR/backends.json
|
||||
side by side to docs/kernel-cases-backends.md and DIR/backends.json; smlib and
|
||||
both need the C++ kernel, so without --cli they build the CLI with --features smlib
|
||||
(a --cli built without it reports every case as unsupported)
|
||||
--known-failing FILE: a regression gate instead of the report: fails on a case
|
||||
that fails and FILE does not list, and on a listed case that passes
|
||||
(tests/kernel-cases/<area>/KNOWN_FAILING, CI's Kernel rig workflow); it grades
|
||||
|
||||
Reference in New Issue
Block a user