SMLib links prebuilt: one build per commit, pinned by SHA-256

tangency-labs/smlib's prebuilt workflow builds the C API once per commit for every Rust target
SolidCraft builds (Linux x86_64 and aarch64, macOS arm64 and x86_64, Windows x64, x86 and ARM64,
wasm32 with wasi-sdk's C++ runtime) and releases it as prebuilt-<commit>. SMLIB_PREBUILT pins each
archive's SHA-256. scripts/fetch-smlib.sh now downloads the host's archive (or the named
targets'), checks its hash and the commit it was built from, and falls back to the sources for a
target with no archive; --source fetches the sources, --pin writes SMLIB_PREBUILT from the
release. build.rs links a fetched archive whose hash matches the pin, else compiles as before,
now with -ffp-contract=off like the archives: every platform does the same arithmetic. No
network access in build.rs.

SMLIB_REV moves to 8b57e7e: the workflow, and fixes for MSVC (no directive inside a macro
argument, SM_PI for M_PI), the no-fused-multiply-add build and a test that needed a POSIX shell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoZgkCdAVrscuw2kjQTWDs
This commit is contained in:
jyviko
2026-10-26 22:57:25 -04:00
parent e476aba94f
commit e1ad6ec1aa
4 changed files with 161 additions and 26 deletions
+10
View File
@@ -0,0 +1,10 @@
# Rust target, SHA-256 of smlib-<target>.tar.gz in the release prebuilt-<SMLIB_REV> of
# tangency-labs/smlib. Written by scripts/fetch-smlib.sh --pin; never edited by hand.
aarch64-apple-darwin 09ce8b7cc7598f2600ab2b117b9d1a5ebfecddaad0ce21b80c731bc6671b3673
aarch64-pc-windows-msvc e36e4810b4cfdcdcf7fae1d30f5f25313aba92668fcda68452415fb30174d4a3
aarch64-unknown-linux-gnu 1e6d149632160bc298b3b7c43b029e4155569c8172c4e59c9b8d614ca78893e1
i686-pc-windows-msvc 5c731edbd96e7d7b608aad755bbb0cc18c2bd90699862cf227cc3338ec22dc3b
wasm32-unknown-unknown 9dceb0fb9b9253e231cc5b929f93924c4ee80566685302bf6bbb906988ade46e
x86_64-apple-darwin a30676477cd12e7f9e3c98744fb6f96a27b8fcbfe6dfb2478a6c79e6dac7a29a
x86_64-pc-windows-msvc 72f3ba519fe7a76c278cda772c00a6189c211f52d4c4c1584f7dc2da8c7fc2cb
x86_64-unknown-linux-gnu 4c00ac33f3feb547fb151104580d56c291e7879c4773805539b0033fe27a4962
+1 -1
View File
@@ -1 +1 @@
a15b5a2885384a9d10e76a68ba8b79526faccb09
8b57e7e8ace9e7886ce9936cfe45f3ed947c4006
+59 -9
View File
@@ -1,15 +1,20 @@
//! Builds SMLib, SM_API and their C API (tangency-labs/smlib, at the commit in `SMLIB_REV`) into
//! static libraries, when the `native` feature is on. The sources come from
//! `SOLIDCRAFT_SMLIB_DIR`, or from `target/smlib-<rev>` as `scripts/fetch-smlib.sh` leaves them.
//! Links SMLib, SM_API and their C API (tangency-labs/smlib, at the commit in `SMLIB_REV`) as
//! static libraries, when the `native` feature is on. In order:
//! - `SOLIDCRAFT_SMLIB_DIR`: a checkout, compiled here;
//! - the prebuilt library for the target, as `scripts/fetch-smlib.sh` leaves it in
//! `target/smlib-<rev>-prebuilt/<target>`, when its SHA-256 is the one in `SMLIB_PREBUILT`;
//! - the sources in `target/smlib-<rev>` (`scripts/fetch-smlib.sh --source`), compiled here.
//!
//! The file lists, defines and include order follow the fork's `capi/CMakeLists.txt`: SMLib/inc
//! and SM_API/inc each have a StdAfx.h, so each part must find its own first.
//! The prebuilt libraries come from the fork's `capi/CMakeLists.txt`. The compiled path follows
//! it: the same file lists, defines and include order (SMLib/inc and SM_API/inc each have a
//! StdAfx.h, so each part must find its own first).
use std::path::{Path, PathBuf};
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=SMLIB_REV");
println!("cargo:rerun-if-changed=SMLIB_PREBUILT");
println!("cargo:rerun-if-env-changed=SOLIDCRAFT_SMLIB_DIR");
println!("cargo:rerun-if-env-changed=WASI_SDK_PATH");
if std::env::var_os("CARGO_FEATURE_NATIVE").is_none() {
@@ -25,14 +30,26 @@ fn main() {
fn build() -> Result<(), String> {
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").map_err(|e| e.to_string())?);
let rev = std::fs::read_to_string(manifest.join("SMLIB_REV")).map_err(|e| format!("SMLIB_REV: {e}"))?.trim().to_string();
let rev12 = rev.get(..12).unwrap_or(&rev);
let target = std::env::var("TARGET").unwrap_or_default();
let fetched = manifest.join("../../target");
let root = match std::env::var_os("SOLIDCRAFT_SMLIB_DIR") {
Some(d) => PathBuf::from(d),
None => manifest.join("../../target").join(format!("smlib-{}", rev.get(..12).unwrap_or(&rev))),
None => {
let prebuilt = fetched.join(format!("smlib-{rev12}-prebuilt")).join(&target);
if let Some(sha) = pinned(&manifest, &target)? {
println!("cargo:rerun-if-changed={}", prebuilt.join(".sha256").display());
let have = std::fs::read_to_string(prebuilt.join(".sha256")).unwrap_or_default();
if have.trim() == sha {
return link_prebuilt(&prebuilt, &target);
}
}
fetched.join(format!("smlib-{rev12}"))
}
};
if !root.join("capi/include/smlib.h").is_file() {
return Err(format!(
"the SMLib sources are not at {} (feature `native`): run scripts/fetch-smlib.sh, or set SOLIDCRAFT_SMLIB_DIR to a tangency-labs/smlib checkout at {rev}",
root.display()
"SMLib {rev} for {target} is not fetched (feature `native`): run scripts/fetch-smlib.sh {target}, or set SOLIDCRAFT_SMLIB_DIR to a tangency-labs/smlib checkout at that commit"
));
}
println!("cargo:rerun-if-changed={}", root.join("capi").display());
@@ -41,7 +58,6 @@ fn build() -> Result<(), String> {
let src = root.join("source");
let core = sources(&src.join("SMLib/src"), &["StdAfx.cpp", "SmDllMain.cpp"])?;
let api = sources(&src.join("SM_API/src"), &["StdAfx.cpp"])?;
let target = std::env::var("TARGET").unwrap_or_default();
let msvc = target.contains("msvc");
let wasi = if target == "wasm32-unknown-unknown" { Some(wasi_sdk()?) } else { None };
@@ -51,6 +67,10 @@ fn build() -> Result<(), String> {
// Rust links the MSVC runtime as a DLL; the cc crate's default (/MD) matches.
if msvc {
b.flag("/EHsc").flag("/bigobj");
} else {
// No fused multiply-add, as the prebuilt libraries (capi/CMakeLists.txt): SMLib's
// tangent cases decide on the last bits. MSVC does not contract by default.
b.flag("-ffp-contract=off");
}
if let Some(sdk) = &wasi {
b.compiler(sdk.join("bin/clang++")).archiver(sdk.join("bin/llvm-ar")).target(WASI_TARGET);
@@ -100,6 +120,36 @@ fn build() -> Result<(), String> {
Ok(())
}
/// The SHA-256 `SMLIB_PREBUILT` pins for `target`'s prebuilt archive, if there is one.
fn pinned(manifest: &Path, target: &str) -> Result<Option<String>, String> {
let pins = std::fs::read_to_string(manifest.join("SMLIB_PREBUILT")).map_err(|e| format!("SMLIB_PREBUILT: {e}"))?;
Ok(pins.lines().filter(|l| !l.starts_with('#')).find_map(|l| {
let mut f = l.split_whitespace();
(f.next() == Some(target)).then(|| f.next().map(str::to_string)).flatten()
}))
}
/// Links the prebuilt C API in `dir` (one archive, `smlib`) and the C++ runtime it was compiled
/// against: the system's, or for wasm wasi-sdk's, which the archive carries.
fn link_prebuilt(dir: &Path, target: &str) -> Result<(), String> {
let lib = dir.join("lib");
if !lib.is_dir() {
return Err(format!("{} has no lib folder: run scripts/fetch-smlib.sh {target} again", dir.display()));
}
println!("cargo:rustc-link-search=native={}", lib.display());
println!("cargo:rustc-link-lib=static=smlib");
if target == "wasm32-unknown-unknown" {
for l in ["c++", "c++abi", "unwind", "c"] {
println!("cargo:rustc-link-lib=static={l}");
}
} else if target.contains("apple") {
println!("cargo:rustc-link-lib=c++");
} else if !target.contains("msvc") {
println!("cargo:rustc-link-lib=stdc++");
}
Ok(())
}
/// The C target SMLib is compiled for inside a `wasm32-unknown-unknown` build: wasi-sdk's, the
/// one with a C++ standard library.
const WASI_TARGET: &str = "wasm32-wasip1";
+91 -16
View File
@@ -1,20 +1,95 @@
#!/usr/bin/env bash
# Fetches the SMLib sources solidcraft-smlib builds (feature `native`): tangency-labs/smlib at
# the commit in crates/smlib/SMLIB_REV, only the parts the build reads (source/SMLib,
# source/SM_API, capi), into target/smlib-<rev12>. OpenCASCADE is not part of the fork.
# Fetches SMLib for solidcraft-smlib (feature `native`), at the commit in crates/smlib/SMLIB_REV.
#
# scripts/fetch-smlib.sh [TARGET...] the prebuilt C API library for each Rust target (default:
# the host's), checked against crates/smlib/SMLIB_PREBUILT,
# into target/smlib-<rev12>-prebuilt/<target>; the sources
# for a target with no prebuilt library
# scripts/fetch-smlib.sh --source the sources (source/SMLib, source/SM_API, capi), into
# target/smlib-<rev12> or SOLIDCRAFT_SMLIB_DIR, to compile
# scripts/fetch-smlib.sh --pin writes SMLIB_PREBUILT from the release of SMLIB_REV
#
# The libraries are the release prebuilt-<rev> of tangency-labs/smlib (its prebuilt.yml).
# OpenCASCADE is not part of the fork.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd)"
rev="$(tr -d '[:space:]' < "$root/crates/smlib/SMLIB_REV")"
dest="${SOLIDCRAFT_SMLIB_DIR:-$root/target/smlib-${rev:0:12}}"
if [ -f "$dest/capi/include/smlib.h" ] && [ "$(git -C "$dest" rev-parse HEAD 2>/dev/null)" = "$rev" ]; then
echo "SMLib $rev already at $dest"
exit 0
fi
rm -rf "$dest"
mkdir -p "$dest"
git -C "$dest" init -q
git -C "$dest" remote add origin https://github.com/tangency-labs/smlib.git
git -C "$dest" sparse-checkout set --no-cone /source/SMLib/ /source/SM_API/ /capi/ /LICENSE
git -C "$dest" fetch -q --depth 1 --filter=blob:none origin "$rev"
git -C "$dest" checkout -q FETCH_HEAD
echo "SMLib $rev at $dest"
pins="$root/crates/smlib/SMLIB_PREBUILT"
release="https://github.com/tangency-labs/smlib/releases/download/prebuilt-$rev"
sha256() {
if command -v sha256sum >/dev/null; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi
}
fetch_source() {
local dest="${SOLIDCRAFT_SMLIB_DIR:-$root/target/smlib-${rev:0:12}}"
if [ -f "$dest/capi/include/smlib.h" ] && [ "$(git -C "$dest" rev-parse HEAD 2>/dev/null)" = "$rev" ]; then
echo "SMLib sources $rev already at $dest"
return
fi
rm -rf "$dest"
mkdir -p "$dest"
git -C "$dest" init -q
git -C "$dest" remote add origin https://github.com/tangency-labs/smlib.git
git -C "$dest" sparse-checkout set --no-cone /source/SMLib/ /source/SM_API/ /capi/ /LICENSE
git -C "$dest" fetch -q --depth 1 --filter=blob:none origin "$rev"
git -C "$dest" checkout -q FETCH_HEAD
echo "SMLib sources $rev at $dest"
}
fetch_prebuilt() {
local target="$1" want dest tmp
want="$(awk -v t="$target" '$1 == t { print $2 }' "$pins")"
if [ -z "$want" ]; then
echo "no prebuilt SMLib for $target: fetching the sources to compile"
fetch_source
return
fi
dest="$root/target/smlib-${rev:0:12}-prebuilt/$target"
if [ "$(cat "$dest/.sha256" 2>/dev/null)" = "$want" ]; then
echo "SMLib $rev for $target already at $dest"
return
fi
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' RETURN
curl -fsSL --retry 3 -o "$tmp/smlib.tar.gz" "$release/smlib-$target.tar.gz"
if [ "$(sha256 "$tmp/smlib.tar.gz")" != "$want" ]; then
echo "smlib-$target.tar.gz does not match its SHA-256 in crates/smlib/SMLIB_PREBUILT" >&2
exit 1
fi
tar -xzf "$tmp/smlib.tar.gz" -C "$tmp"
if [ "$(tr -d '[:space:]' < "$tmp/smlib-$target/REV")" != "$rev" ]; then
echo "smlib-$target.tar.gz was built from another commit than SMLIB_REV" >&2
exit 1
fi
rm -rf "$dest"
mkdir -p "$(dirname "$dest")"
mv "$tmp/smlib-$target" "$dest"
echo "$want" > "$dest/.sha256"
echo "SMLib $rev for $target at $dest"
}
case "${1:-}" in
--source)
fetch_source
;;
--pin)
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
curl -fsSL --retry 3 -o "$tmp/SHA256SUMS" "$release/SHA256SUMS"
{
echo "# Rust target, SHA-256 of smlib-<target>.tar.gz in the release prebuilt-<SMLIB_REV> of"
echo "# tangency-labs/smlib. Written by scripts/fetch-smlib.sh --pin; never edited by hand."
sed -E 's/^([0-9a-f]{64}) +smlib-(.*)\.tar\.gz$/\2 \1/' "$tmp/SHA256SUMS" | sort
} > "$pins"
echo "pinned $(grep -vc '^#' "$pins") prebuilt targets of $rev"
;;
*)
if [ $# -eq 0 ]; then
set -- "$(rustc -vV | sed -n 's/^host: //p')"
fi
for target in "$@"; do
fetch_prebuilt "$target"
done
;;
esac