build: vendor OCCT for static releases

This commit is contained in:
2026-09-15 09:21:25 +02:00
parent 3370872e66
commit 9eae00278f
22 changed files with 971 additions and 61 deletions
+3 -1
View File
@@ -15,7 +15,9 @@ jobs:
# OCCT dev packages: headers + toolkits for the vernier-occt-sys facade.
- run: apt-get update && apt-get install -y --no-install-recommends python3 fish jq mesa-vulkan-drivers libocct-foundation-dev libocct-modeling-data-dev libocct-modeling-algorithms-dev
- run: rustup component add rustfmt clippy
# The same exact manifest as scripts/check.fish. The adapter remains
# The same exact manifest as scripts/check.fish. Its compiling Cargo
# rows explicitly select system-occt; CI never starts a vendored build.
# The adapter remains
# explicit so a Vulkan-to-GL fallback is a hard failure. Golden-bearing
# scripts run with PNG comparison skipped on software until a measured
# llvmpipe budget is committed; geometry, interaction, determinism and
+3
View File
@@ -0,0 +1,3 @@
[submodule "vendor/occt"]
path = vendor/occt
url = https://github.com/Open-Cascade-SAS/OCCT.git
+2 -2
View File
@@ -18,8 +18,8 @@ Branch: `codex/phased-next-steps`
| 10. L3 rod holder | done | task10_l3_rod_holder | this commit | approved after review-fix pass; no Critical/Important/Minor findings | public-command oracle + 261-step writer/46-step reader; complete two-pair RADV positive and exact exit-1 negative; imported historical-edit provenance regression focused/release green; first full gate failed at that now-fixed regression, second stopped partial by user-approved shortened-testing policy; no full-gate green claimed; source digest b9272299 |
| 11. Performance measurement | done | task11_performance | 582ee82 | approved after review-fix pass; no remaining findings | focused CLI contracts green; exact release artifact bd3d3834; L3 measured bottleneck about 220 ms; no full gate under shortened policy |
| 12. CI/software renderer | done | task12_ci_renderer | 064e6b7 | approved after review-fix pass; no remaining findings | shared 143-step adapter selection; Python 7/7 and focused golden green; 13.45s representative RADV positive/exact exit-1 control; lavapipe absent and explicitly unverified |
| 13. C++ sanitizer evidence | done with explicit runtime boundary | task13_sanitizers | pending commit | approved after one documentation correction | 14 generated cxxbridge warnings; no project-owned warning; one sanitizer link attempt failed before tests because runtimes were omitted under -nodefaultlibs; no sanitizer-clean claim or suppression |
| 14. Vendored static OCCT | pending | — | — | pending | — |
| 13. C++ sanitizer evidence | done with explicit runtime boundary | task13_sanitizers | 3370872 | approved after one documentation correction | 14 generated cxxbridge warnings; no project-owned warning; one sanitizer link attempt failed before tests because runtimes were omitted under -nodefaultlibs; no sanitizer-clean claim or suppression |
| 14. Vendored static OCCT | done | task14_static_occt | pending commit | approved after review-fix pass; no remaining findings | official V7_9_3 at a016080b; 26 static archives built once; app/worker have no libTK dynamic deps; 236-step RADV workflow deterministic with STEP/STL/reopen |
| 15. AppImage | pending | — | — | pending | — |
| 16. Integrated verification | pending | controller | — | pending | — |
+33
View File
@@ -0,0 +1,33 @@
# Task 14 brief — pinned vendored OCCT and static release linkage
Implement Task 14 from `docs/superpowers/plans/2026-09-14-phased-next-steps.md`.
## Goal
Pin the official OCCT `V7_9_3` source as a submodule, build the minimal complete static toolkit closure reproducibly, make that prepared static tree the non-`system-occt` release path with fail-closed validation, and keep normal local/CI development explicitly on `system-occt` through the shared manifest.
## Short testing budget
The user explicitly shortened testing. Run only:
1. the static-link checker once against current dynamic release binaries for RED;
2. one vendored OCCT static build/install;
3. one locked release build linked against that prepared tree;
4. the static-link checker plus one representative `local-linux-release` RADV workflow using those binaries.
Use focused script/unit contract tests where necessary before the expensive build. Do not run `scripts/check.fish`, the complete acceptance manifest, workspace-wide tests, AppImage work, or rebuild OCCT merely to polish evidence. Reuse the one prepared tree across all verification.
## Constraints
- Read `PROJECT_BRIEF.md` and `MISTAKES.md` before acting.
- Use `apply_patch` for tracked file edits. Normal noninteractive Git is allowed for the requested submodule pin.
- Official upstream only: tag `V7_9_3`, resolved immutable commit recorded.
- No floating download, implicit system fallback, or editing vendored source.
- No new Rust dependency. No system package installation.
- Preserve `system-occt` for explicit fast local/CI builds; ensure every normal Cargo step in `scripts/acceptance-gate.json` requests it.
- Static mode must verify source revision, direct toolkits, full transitive closure, headers/resources/licenses, and every archive before linking.
- Do not install/switch the managed app, commit, push, or touch unrelated files.
## Handoff
Create `docs/VENDORED_OCCT_RELEASE.md` and `.superpowers/sdd/task-14-report.md`. Record exact source SHA, configuration/closure, RED/GREEN commands and statuses, binary dependency evidence, representative workflow evidence, changed files/submodule state, remaining boundaries, and deterministic source digest. Leave all changes uncommitted for independent review.
+192
View File
@@ -0,0 +1,192 @@
# Task 14 implementation report
## Outcome
Completed the pinned vendored-OCCT and static-release-linkage lane within the
shortened testing budget.
The official OCCT `V7_9_3` annotated tag resolves to
`a016080bf6738d6aeae020badee4e888ad1540a5`; `vendor/occt` is an official
submodule detached at that immutable commit. The build derives a 26-toolkit
closure from OCCT's own `EXTERNLIB` metadata, installs all archives, headers,
resources and licenses, and records the inputs under `share/vernier/`.
`system-occt` is now the explicit fast local/CI path through
`vernier-kernel`, `vernier-app`, `vernier-cli`, and `vernier-drive`. Every
compiling Cargo row in the shared acceptance manifest requests it. Without the
feature, `vernier-occt-sys/build.rs` requires and validates an absolute
`VERNIER_OCCT_STATIC_ROOT`; there is no system fallback. The static closure is
linked inside GNU ld's archive group/rescan boundary, followed by dynamic
platform libraries only.
Independent review tightened two contracts without another build: every
compiling command in `scripts/check-fast.fish` now also selects `system-occt`,
and both validators require named, non-empty SHMessage, XSMessage, STEP, IGES,
Standard, and XCAF runtime resource files rather than accepting directories
alone.
The resulting app and worker have no OCCT `DT_NEEDED` entries. A representative
RADV workflow passed cross-process with save/reopen and STEP/STL export while
all OCCT runtime-resource variables pointed at the prepared vendored tree.
## Source and closure
- Repository: `https://github.com/Open-Cascade-SAS/OCCT.git`
- Tag: `V7_9_3`
- Gitlink: `a016080bf6738d6aeae020badee4e888ad1540a5`
- Direct toolkits: 17
- Derived closure: 26
- Installed archives: 26, all non-empty
- Prepared root: `target/vendored-occt`
- Build metadata: `target/vendored-occt/share/vernier/`
Sorted closure:
```text
TKBO TKBRep TKBool TKCAF TKCDF TKDE TKDESTEP TKFillet TKG2d TKG3d
TKGeomAlgo TKGeomBase TKHLR TKLCAF TKMath TKMesh TKOffset TKPrim
TKService TKShHealing TKTopAlgo TKV3d TKVCAF TKXCAF TKXSBase TKernel
```
Configuration: Release, static libraries, PIC, C++17, optional third-party
integrations off, all normal modules off, direct toolkits supplied through
`BUILD_ADDITIONAL_TOOLKITS`, samples/inspector/overview docs/Draw off.
`SOURCE_DATE_EPOCH=1764978946`; CMake 4.4.3; GCC 16.2.1.
## RED and focused contract
The checker was written before changing linkage and run once against the
existing dynamic release binaries:
```text
scripts/check-static-occt.sh \
target/release/vernier-app target/release/vernier-worker
status 1
```
It named `libTKernel.so.7.9`, the directly needed `libTK*` libraries, and their
resolutions under `/usr/lib`. Evidence:
- `target/static-occt-red.stderr`
- SHA-256 `94a5a5b1c415b12cba2f6a6cb74911d39ce696767e3110ebda3ec77dffbaf797`
The focused acceptance-manifest contract first failed on `clippy` because the
feature was absent, then passed after all compiling Cargo rows were updated:
```text
python3 -m unittest \
scripts.test-acceptance-gate.AcceptanceManifestTests.test_normal_cargo_compilation_steps_select_system_occt
RED status 1; GREEN status 0, 1 test
```
`cargo fmt` is intentionally excluded because it does not compile or link.
The same focused contract covers the shared manifest and each compiling Cargo
command in `scripts/check-fast.fish`. When that coverage was added during
review it failed first on the fast gate's `clippy` row, then passed after all
seven compiling fast-gate commands were corrected (`1 test`, status 0).
## Single expensive verification sequence
One OCCT configure/build/install was run and completed all 4,545 Ninja steps:
```text
VERNIER_OCCT_BUILD_JOBS=28 \
scripts/build-vendored-occt.sh target/vendored-occt
status 0
```
Evidence: `target/vendored-occt-build.log`, SHA-256
`d2e66b6d7db22e846934f3812a7da81dbf11f4c690d3cce5e509b0ee97efd6cb`.
The warnings in that log are upstream pinned OCCT/GCC diagnostics; vendored
source was not edited.
One locked Rust release build reused that prepared tree:
```text
VERNIER_OCCT_STATIC_ROOT="$PWD/target/vendored-occt" \
cargo build --locked --release -p vernier-app \
--bin vernier-app --bin vernier-worker
status 0; Cargo 25.39 s
```
Evidence: `target/static-occt-release-build.log`, SHA-256
`5c003a92af50394d712179cbd369a4b239a1672551c4becda71830229dd2026c`.
Its only diagnostics are the generated `cxxbridge` warnings already classified
by Task 13; no project-owned C++ warning was introduced.
The single post-link checker run passed:
```text
scripts/check-static-occt.sh \
target/release/vernier-app target/release/vernier-worker
status 0
```
- App SHA-256: `24742d7c90b98c867aab795afbe880f4bec593a9764498c54d7ae9d25ae30acb`
- Worker SHA-256: `8c90b8100cd6a179e2cc0a95ad6bb8ad6f0972e27b571763390af10dbf797985`
- Worker dynamic needs: `libstdc++.so.6`, `libgcc_s.so.1`, `libm.so.6`,
`libc.so.6`, `ld-linux-x86-64.so.2`
- OCCT dynamic needs: none
One representative workflow was invoked, with `CASROOT` and every OCCT
resource variable redirected to `target/vendored-occt/resources`:
```text
target/release/vernier-drive scripts/drive/local-linux-release.json \
--out target/drive/static-occt-release \
--require-adapter RADV \
--worker-helper target/release/vernier-worker
status 0
```
- Result: pass
- Adapter: `AMD Radeon RX 7800 XT (RADV NAVI32)`
- Steps/frames: 236 / 402
- Determinism: cross-process, PIDs 890088 and 890888
- Report SHA-256: `02bb3d9979c6aa22bb28f340e5d0aebfb6381b49ac20764147051fd816818083`
- Reopened STEP SHA-256: `b03b61bf7fbce2af8eaa15f38310582b8c425ef42d298baa7f3ea38c2d2a9e20`
- Representative STL SHA-256: `c46c174170adaaf76739f4f31b0d6585a0093b573cb0cfa6ce26e549333c74d8`
## Changed files and submodule state
- `.gitmodules`
- `vendor/occt` gitlink at `a016080bf6738d6aeae020badee4e888ad1540a5`
- `crates/vernier-occt-sys/{Cargo.toml,build.rs}`
- `crates/vernier-kernel/Cargo.toml`
- `crates/vernier-app/Cargo.toml`
- `crates/vernier-cli/Cargo.toml`
- `crates/vernier-drive/Cargo.toml`
- `scripts/build-vendored-occt.sh`
- `scripts/check-static-occt.sh`
- `scripts/acceptance-gate.json`
- `scripts/test-acceptance-gate.py`
- `scripts/check.fish`
- `scripts/check-fast.fish`
- `.gitea/workflows/ci.yaml`
- `README.md`
- `docs/VENDORED_OCCT_RELEASE.md`
- `.superpowers/sdd/task-14-report.md` (this report)
The existing `.superpowers/sdd/progress.md` modification and Task 14 brief
files were not changed as Task 14 implementation. Changes are uncommitted and
unstaged for independent review.
## Boundaries
- No complete acceptance manifest, workspace suite, AppImage build, or second
OCCT/release build was run.
- No system package or Rust dependency was added.
- No managed installation, launcher switch, commit, or push occurred.
- Relocation and AppImage runtime-resource wiring belong to Task 15.
- The one final complete gate belongs to Task 16; this report does not claim it.
- Review fixes after the release workflow were restricted to fast-command and
fail-closed resource validation contracts; per instruction, they did not
trigger another OCCT/Rust build or workflow.
## Source-manifest digest
`target/task-14-source-manifest.txt` contains sorted SHA-256 rows for the 16
changed product files plus the exact submodule gitlink row, excluding this
report and pre-existing ledger/brief files. Its SHA-256 is:
`910c8edeb9a9ca5c110ed9aceceb703d9e77c180adb369ab1f96f98e4342b0c3`
+7
View File
@@ -0,0 +1,7 @@
# Task 14 review brief
Review Task 14 read-only against the plan and implementation brief. Do not build OCCT, compile Rust, run workflows, edit, or initialize/update submodules.
Check the official immutable submodule pin; reproducible build configuration; complete direct and transitive toolkit closure; fail-closed static-root validation with no system fallback; correct Cargo feature forwarding; every normal manifest Cargo command explicitly using `system-occt`; archive grouping/platform libraries/resources; static-link audit semantics; recorded binary/workflow evidence and hashes; and no unrelated changes.
Return APPROVE or Critical/Important/Minor findings with exact file/line evidence.
+20 -3
View File
@@ -76,7 +76,7 @@ For a source checkout, use the workspace's normal Rust commands:
```sh
fish scripts/check-fast.fish # everyday development feedback
fish scripts/check.fish # exhaustive release gate
cargo run --release -p vernier-cli -- --selftest # 46 end-to-end checks, JSON report on stdout
cargo run --release -p vernier-cli --features system-occt -- --selftest # 46 end-to-end checks
```
`--selftest` runs 46 end-to-end checks headlessly and writes a JSON report; the
@@ -92,5 +92,22 @@ file with `sort -nr` to find the slowest stages. The gate still stops at the fir
unexpected failure; a negative control must return exactly 1. A cold release
build costs more than a warm run, so compare those timings separately.
The OCCT development libraries, a C++ toolchain, and a Vulkan driver are needed
for a full build and viewport run.
The acceptance manifest explicitly enables `system-occt` on every Cargo step
that compiles code. The OCCT development libraries, a C++ toolchain, and a
Vulkan driver are therefore needed for normal local development and CI.
Release builds use the pinned, statically linked OCCT source instead. Prepare
it once, then build without `system-occt`:
```sh
scripts/build-vendored-occt.sh target/vendored-occt
VERNIER_OCCT_STATIC_ROOT="$PWD/target/vendored-occt" \
cargo build --locked --release -p vernier-app \
--bin vernier-app --bin vernier-worker
scripts/check-static-occt.sh \
target/release/vernier-app target/release/vernier-worker
```
There is no implicit fallback from this release path to host OCCT. See
[`docs/VENDORED_OCCT_RELEASE.md`](docs/VENDORED_OCCT_RELEASE.md) for the pin,
closure, resource environment, and verification contract.
+3 -4
View File
@@ -32,10 +32,9 @@ wgpu.workspace = true
winit.workspace = true
[features]
# Forward to vernier-occt-sys/system-occt once the kernel dependency chain
# reaches this crate (Phase 1). Declared now so
# `cargo run -p vernier-app --features system-occt` works as documented.
system-occt = []
# Fast local/CI builds use the distribution OCCT explicitly. A build without
# this feature is the fail-closed vendored-static release path.
system-occt = ["vernier-kernel/system-occt"]
[lints]
workspace = true
+3
View File
@@ -15,5 +15,8 @@ vernier-solver = { path = "../vernier-solver" }
vernier-tess = { path = "../vernier-tess" }
vernier-ui = { path = "../vernier-ui" }
[features]
system-occt = ["vernier-kernel/system-occt"]
[lints]
workspace = true
+6
View File
@@ -26,5 +26,11 @@ vernier-kernel = { path = "../vernier-kernel" }
vernier-render = { path = "../vernier-render" }
vernier-ui = { path = "../vernier-ui" }
[features]
system-occt = [
"vernier-app/system-occt",
"vernier-kernel/system-occt",
]
[lints]
workspace = true
+3
View File
@@ -14,6 +14,9 @@ serde.workspace = true
thiserror.workspace = true
vernier-occt-sys = { path = "../vernier-occt-sys" }
[features]
system-occt = ["vernier-occt-sys/system-occt"]
[dev-dependencies]
# Tests only, and for one job: `NamingStore` persists provenance keys with
# serde_json (crates/vernier-ui/src/compile/store.rs), so the serialized form
+2 -4
View File
@@ -17,10 +17,8 @@ thiserror.workspace = true
cxx-build.workspace = true
[features]
# Link the system OCCT (fast iteration). Until the vendored submodule build
# lands (Phase 1 tail), the default build also links the system OCCT — the
# feature exists so the documented commands work and so the vendored switch
# has a home.
# Fast iteration path. With this feature absent, build.rs requires the prepared
# V7_9_3 static tree named by VERNIER_OCCT_STATIC_ROOT and never falls back.
system-occt = []
[lints]
+284 -34
View File
@@ -1,46 +1,296 @@
//! Builds the C++ façade and links OCCT.
//!
//! Include path resolution: `DEP_OCCT_ROOT` if set (per CLAUDE.md), else
//! `/usr/include/opencascade` (CachyOS and Debian both use it). The
//! vendored-submodule build arrives at the end of Phase 1; until then both
//! feature configurations link the system OCCT.
//! Builds the C++ facade and selects either the explicit system-OCCT
//! development path or the fail-closed vendored-static release path.
use std::collections::BTreeSet;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
const OCCT_SOURCE_REVISION: &str = "a016080bf6738d6aeae020badee4e888ad1540a5";
const DIRECT_TOOLKITS: &[&str] = &[
"TKernel",
"TKMath",
"TKG2d",
"TKG3d",
"TKGeomBase",
"TKGeomAlgo",
"TKBRep",
"TKTopAlgo",
"TKPrim",
"TKBO",
"TKShHealing",
"TKMesh",
"TKFillet",
"TKOffset",
"TKDESTEP",
"TKDE",
"TKXSBase",
];
const PLATFORM_LIBRARIES: &[&str] = &["pthread", "rt", "stdc++", "dl"];
fn main() {
let occt_include =
std::env::var("DEP_OCCT_ROOT").unwrap_or_else(|_| "/usr/include/opencascade".to_owned());
println!("cargo:rerun-if-env-changed=DEP_OCCT_ROOT");
println!("cargo:rerun-if-env-changed=VERNIER_OCCT_STATIC_ROOT");
println!("cargo:rerun-if-changed=src/facade.cpp");
println!("cargo:rerun-if-changed=include/facade.hpp");
cxx_build::bridge("src/lib.rs")
let system = env::var_os("CARGO_FEATURE_SYSTEM_OCCT").is_some();
let (occt_include, static_toolkits) = if system {
let include =
env::var("DEP_OCCT_ROOT").unwrap_or_else(|_| "/usr/include/opencascade".to_owned());
(PathBuf::from(include), None)
} else {
let prepared = validate_static_root();
(prepared.include, Some(prepared.toolkits))
};
let mut facade = cxx_build::bridge("src/lib.rs");
facade
.file("src/facade.cpp")
.include(&occt_include)
.std("c++17")
.compile("vernier-occt-facade");
.std("c++17");
if static_toolkits.is_some() {
facade.define("OCCT_NO_PLUGINS", None);
}
facade.compile("vernier-occt-facade");
// Only the toolkits the façade actually uses; extend as operations land.
for toolkit in [
"TKernel",
"TKMath",
"TKG2d",
"TKG3d",
"TKGeomBase",
"TKGeomAlgo",
"TKBRep",
"TKTopAlgo",
"TKPrim",
"TKBO",
"TKShHealing",
"TKMesh",
"TKFillet",
"TKOffset",
// STEP read/write. OCCT 7.9 renamed the data-exchange toolkits:
// this is TKDESTEP, not the TKSTEP older documentation names.
// These three are what M5's probe found actually resolve.
"TKDESTEP",
"TKDE",
"TKXSBase",
if let Some(toolkits) = static_toolkits {
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("linux") {
panic!("vendored static OCCT currently supports the Linux release target only");
}
// OCCT archives contain cyclic references. GNU ld's archive group is
// the defined rescan mechanism; a guessed repeated list is not.
println!("cargo:rustc-link-arg=-Wl,--start-group");
for toolkit in toolkits {
println!("cargo:rustc-link-lib=static={toolkit}");
}
println!("cargo:rustc-link-arg=-Wl,--end-group");
for library in PLATFORM_LIBRARIES {
println!("cargo:rustc-link-lib=dylib={library}");
}
} else {
for toolkit in DIRECT_TOOLKITS {
println!("cargo:rustc-link-lib=dylib={toolkit}");
}
}
}
struct PreparedStatic {
include: PathBuf,
toolkits: Vec<String>,
}
fn validate_static_root() -> PreparedStatic {
let raw_root = env::var_os("VERNIER_OCCT_STATIC_ROOT").unwrap_or_else(|| {
panic!(
"vendored static OCCT requires VERNIER_OCCT_STATIC_ROOT; \
run scripts/build-vendored-occt.sh first or explicitly enable system-occt"
)
});
let root = PathBuf::from(raw_root);
if !root.is_absolute() {
panic!(
"VERNIER_OCCT_STATIC_ROOT must be an absolute path: {}",
root.display()
);
}
require_dir(&root, "vendored OCCT static root");
let metadata = root.join("share/vernier");
require_exact(
&metadata.join("complete"),
"vernier.occt-static.v1\n",
"completion marker",
);
require_exact(
&metadata.join("occt-source-revision.txt"),
&format!("{OCCT_SOURCE_REVISION}\n"),
"OCCT source revision",
);
require_lines_exact(
&metadata.join("occt-direct-toolkits.txt"),
&DIRECT_TOOLKITS
.iter()
.map(|item| (*item).to_owned())
.collect::<Vec<_>>(),
"direct toolkit manifest",
);
require_lines_exact(
&metadata.join("occt-platform-libs.txt"),
&PLATFORM_LIBRARIES
.iter()
.map(|item| (*item).to_owned())
.collect::<Vec<_>>(),
"platform library manifest",
);
let manifest_dir = PathBuf::from(
env::var_os("CARGO_MANIFEST_DIR").expect("Cargo must set CARGO_MANIFEST_DIR"),
);
let source = manifest_dir.join("../../vendor/occt");
require_dir(&source, "initialized vendor/occt submodule");
let revision = git_revision(&source);
if revision != OCCT_SOURCE_REVISION {
panic!("vendor/occt revision mismatch: expected {OCCT_SOURCE_REVISION}, got {revision}");
}
println!("cargo:rerun-if-changed={}", source.join(".git").display());
let expected = derive_closure(&source);
let closure_path = metadata.join("occt-toolkit-closure.txt");
let recorded = read_lines(&closure_path, "toolkit closure manifest");
let expected_vec = expected.into_iter().collect::<Vec<_>>();
if recorded != expected_vec {
panic!(
"vendored OCCT toolkit closure mismatch in {}: expected {:?}, got {:?}",
closure_path.display(),
expected_vec,
recorded
);
}
let include = root.join("include/opencascade");
require_file(
&include.join("Standard_Version.hxx"),
"installed OCCT headers",
);
let resources = root.join("resources");
for resource in ["SHMessage", "XSMessage", "XSTEPResource", "StdResource"] {
require_dir(
&resources.join(resource),
"installed OCCT resource directory",
);
}
for resource in [
"SHMessage/SHAPE.us",
"XSMessage/XSTEP.us",
"XSTEPResource/STEP",
"XSTEPResource/IGES",
"StdResource/Standard",
"StdResource/XCAF",
] {
println!("cargo:rustc-link-lib=dylib={toolkit}");
require_nonempty_file(&resources.join(resource), "installed OCCT runtime resource");
}
for license in ["LICENSE_LGPL_21.txt", "OCCT_LGPL_EXCEPTION.txt"] {
require_file(
&root.join("licenses").join(license),
"installed OCCT license",
);
}
let library_dir = root.join("lib");
require_dir(&library_dir, "vendored OCCT archive directory");
for toolkit in &recorded {
require_nonempty_file(
&library_dir.join(format!("lib{toolkit}.a")),
"vendored OCCT archive",
);
}
println!("cargo:rustc-link-search=native={}", library_dir.display());
PreparedStatic {
include,
toolkits: recorded,
}
}
fn derive_closure(source: &Path) -> BTreeSet<String> {
let mut closure = BTreeSet::new();
for toolkit in DIRECT_TOOLKITS {
visit_toolkit(source, toolkit, &mut closure);
}
closure
}
fn visit_toolkit(source: &Path, toolkit: &str, closure: &mut BTreeSet<String>) {
if !closure.insert(toolkit.to_owned()) {
return;
}
let externlib = source.join("src").join(toolkit).join("EXTERNLIB");
require_file(&externlib, "OCCT EXTERNLIB metadata");
println!("cargo:rerun-if-changed={}", externlib.display());
let contents = fs::read_to_string(&externlib)
.unwrap_or_else(|error| panic!("cannot read {}: {error}", externlib.display()));
for dependency in contents.split_whitespace() {
let dependency_dir = source.join("src").join(dependency);
if dependency.starts_with("TK") && dependency_dir.is_dir() {
visit_toolkit(source, dependency, closure);
}
}
}
fn git_revision(source: &Path) -> String {
let output = Command::new("git")
.args(["-C"])
.arg(source)
.args(["rev-parse", "HEAD"])
.output()
.unwrap_or_else(|error| panic!("cannot inspect vendor/occt revision: {error}"));
if !output.status.success() {
panic!("cannot inspect vendor/occt revision");
}
String::from_utf8(output.stdout)
.expect("git revision must be UTF-8")
.trim()
.to_owned()
}
fn read_lines(path: &Path, label: &str) -> Vec<String> {
require_file(path, label);
let contents = fs::read_to_string(path)
.unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()));
if !contents.ends_with('\n') {
panic!("{label} must end with a newline: {}", path.display());
}
let lines = contents.lines().map(str::to_owned).collect::<Vec<_>>();
if lines.is_empty() || lines.iter().any(String::is_empty) {
panic!(
"{label} must contain only non-empty lines: {}",
path.display()
);
}
lines
}
fn require_lines_exact(path: &Path, expected: &[String], label: &str) {
let actual = read_lines(path, label);
if actual != expected {
panic!(
"{label} mismatch in {}: expected {:?}, got {:?}",
path.display(),
expected,
actual
);
}
}
fn require_exact(path: &Path, expected: &str, label: &str) {
require_file(path, label);
let actual = fs::read_to_string(path)
.unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()));
if actual != expected {
panic!("{label} mismatch in {}", path.display());
}
}
fn require_dir(path: &Path, label: &str) {
if !path.is_dir() {
panic!("missing {label}: {}", path.display());
}
}
fn require_file(path: &Path, label: &str) {
if !path.is_file() {
panic!("missing {label}: {}", path.display());
}
}
fn require_nonempty_file(path: &Path, label: &str) {
require_file(path, label);
let length = fs::metadata(path)
.unwrap_or_else(|error| panic!("cannot inspect {}: {error}", path.display()))
.len();
if length == 0 {
panic!("empty {label}: {}", path.display());
}
}
+118
View File
@@ -0,0 +1,118 @@
# Vendored OCCT release linkage
VernierCAD development builds deliberately use the host OCCT packages through
the explicit Cargo feature `system-occt`. Release builds omit that feature and
fail closed unless `VERNIER_OCCT_STATIC_ROOT` names a complete prepared static
tree.
## Immutable source
- Official repository: `https://github.com/Open-Cascade-SAS/OCCT.git`
- Upstream tag: `V7_9_3`
- Annotated tag target: `a016080bf6738d6aeae020badee4e888ad1540a5`
- Submodule: `vendor/occt`, detached at that exact commit
The gitlink is the authority. The build script also checks the source worktree
is unmodified and that `HEAD` equals the recorded commit. A floating tag or
branch is never used after initialization.
## Static build contract
Prepare the tree once:
```sh
git submodule update --init -- vendor/occt
scripts/build-vendored-occt.sh target/vendored-occt
```
The script configures OCCT with CMake/Ninja as Release, static, C++17, PIC, and
with samples, inspector, overview documentation, Draw, Tcl/Tk, OpenGL/Xlib, and
optional third-party integrations disabled. `SOURCE_DATE_EPOCH` comes from the
pinned source commit; GNU archive deterministic mode and source/debug prefix
maps remove checkout-path and timestamp inputs.
Only these façade toolkits are direct inputs:
```text
TKernel TKMath TKG2d TKG3d TKGeomBase TKGeomAlgo TKBRep TKTopAlgo
TKPrim TKBO TKShHealing TKMesh TKFillet TKOffset TKDESTEP TKDE TKXSBase
```
OCCT's `src/<toolkit>/EXTERNLIB` files determine the transitive closure. For
7.9.3 the sorted 26-toolkit closure is:
```text
TKBO TKBRep TKBool TKCAF TKCDF TKDE TKDESTEP TKFillet TKG2d TKG3d
TKGeomAlgo TKGeomBase TKHLR TKLCAF TKMath TKMesh TKOffset TKPrim
TKService TKShHealing TKTopAlgo TKV3d TKVCAF TKXCAF TKXSBase TKernel
```
The prepared tree records the source revision, direct set, recomputed closure,
platform libraries, CMake/compiler configuration, and completion marker under
`share/vernier/`. It installs headers under `include/opencascade`, all 26
archives under `lib`, runtime resources under `resources`, and the LGPL 2.1
license plus OCCT exception under `licenses`.
## Fail-closed release link
Build the two shipped binaries without `system-occt`:
```sh
VERNIER_OCCT_STATIC_ROOT="$PWD/target/vendored-occt" \
cargo build --locked --release -p vernier-app \
--bin vernier-app --bin vernier-worker
```
Before compiling the façade, `vernier-occt-sys/build.rs` independently checks
the root and completion marker, exact source revision, direct set, platform
set, closure recomputed from the pinned source, required headers and licenses,
every non-empty archive, and named non-empty runtime resources:
`SHMessage/SHAPE.us`, `XSMessage/XSTEP.us`, `XSTEPResource/{STEP,IGES}`, and
`StdResource/{Standard,XCAF}`. Any missing or mismatched input stops the build
by name; `DEP_OCCT_ROOT` is ignored in this mode. GNU ld's archive
group/rescan facility resolves OCCT's cyclic static references, after which
only `pthread`, `rt`, `stdc++`, and `dl` are linked dynamically.
Check the result with:
```sh
scripts/check-static-occt.sh \
target/release/vernier-app target/release/vernier-worker
```
The checker rejects any `libTK*` `DT_NEEDED` row and any `libTK*` library
resolved by `ldd` from the host.
## Runtime resources
Packaged launchers must set OCCT resource variables relative to their own
resource root. At minimum STEP operation uses:
```sh
export CASROOT=/path/to/occt-root
export CSF_OCCTResourcePath="$CASROOT/resources"
export CSF_SHMessage="$CASROOT/resources/SHMessage"
export CSF_XSMessage="$CASROOT/resources/XSMessage"
export CSF_STEPDefaults="$CASROOT/resources/XSTEPResource"
export CSF_IGESDefaults="$CASROOT/resources/XSTEPResource"
export CSF_StandardDefaults="$CASROOT/resources/StdResource"
export CSF_XCAFDefaults="$CASROOT/resources/StdResource"
```
The AppImage lane owns setting the complete installed resource environment.
Task 14 verified the native workflow with every OCCT resource variable pointed
at this prepared tree; it did not certify relocation or an AppImage.
## Recorded Task 14 evidence
The pre-change checker returned exit 1 for both dynamic binaries and named
their host `libTK*.so.7.9` dependencies. The one prepared-tree build completed
all 4,545 Ninja steps. The one locked Rust release build completed in 25.39 s.
The post-link checker returned exit 0; `vernier-worker` retained only
`libstdc++`, `libgcc_s`, `libm`, `libc`, and the ELF loader as dynamic needs.
The representative `local-linux-release` workflow passed on
`AMD Radeon RX 7800 XT (RADV NAVI32)`: 236 steps, 402 frames, deterministic in
two separate processes, with save/reopen and STEP/STL exports. This is focused
Task 14 evidence, not the complete acceptance gate; Task 16 owns the one final
full gate.
@@ -828,7 +828,7 @@ Expected: sanitizer runs contain no unexplained finding and the normal build con
- Consumes: OCCT 7.9.3 source tag `V7_9_3`, CMake, the current exact toolkit list, and the existing `system-occt` Cargo feature.
- Produces: a submodule-pinned OCCT source revision, static release archives built with data-exchange support, vendored-default release linkage, and an explicit system-OCCT development path.
- [ ] **Step 1: Pin the official OCCT source and write a failing linkage check**
- [x] **Step 1: Pin the official OCCT source and write a failing linkage check**
Add the official OCCT repository as `vendor/occt` at tag `V7_9_3`; the gitlink records the resolved immutable commit, so no branch or floating tag participates after checkout. `scripts/check-static-occt.sh BINARY...` inspects `readelf -d` and fails if any `DT_NEEDED` entry begins with `TK` or resolves beneath a system OCCT directory. Run it against the current release binaries and record RED.
@@ -838,15 +838,15 @@ scripts/check-static-occt.sh target/release/vernier-app target/release/vernier-w
Expected: non-zero with the currently dynamic `TKernel`/`TK*` dependencies named.
- [ ] **Step 2: Build the minimal static OCCT toolkit set reproducibly**
- [x] **Step 2: Build the minimal static OCCT toolkit set reproducibly**
`scripts/build-vendored-occt.sh OUT` requires the initialized git submodule and configures CMake with Release, position-independent code, no samples/tests/docs, and the modules that contain the façade's direct toolkit set `TKernel`, `TKMath`, `TKG2d`, `TKG3d`, `TKGeomBase`, `TKGeomAlgo`, `TKBRep`, `TKTopAlgo`, `TKPrim`, `TKBO`, `TKShHealing`, `TKMesh`, `TKFillet`, `TKOffset`, `TKDESTEP`, `TKDE`, and `TKXSBase`. It derives and records the complete transitive OCCT `EXTERNLIB` closure from the pinned source/install metadata; this includes dependencies such as `TKBool`, `TKXCAF`, `TKLCAF`, `TKVCAF`, `TKV3d`, `TKService`, `TKCAF`, `TKCDF`, and `TKHLR` when required by 7.9.3. It installs headers, every archive in that closure, resources, and license files under `OUT`, records OCCT source SHA plus CMake/compiler options, and uses `SOURCE_DATE_EPOCH` for archive timestamps.
- [ ] **Step 3: Make vendored static linkage the release default**
- [x] **Step 3: Make vendored static linkage the release default**
Keep `system-occt` for normal fast development and CI packages. `vernier-kernel` forwards it to `vernier-occt-sys`; `vernier-app`, `vernier-cli`, and `vernier-drive` forward it through their dependency chain. Update every normal local/CI Cargo argv in `scripts/acceptance-gate.json` to request `--features system-occt`, update its contract test to require that feature on normal Cargo steps, and keep `scripts/check.fish` plus `.gitea/workflows/ci.yaml` consuming that manifest. When the feature is absent, `build.rs` locates the prepared tree from `VERNIER_OCCT_STATIC_ROOT`, verifies the recorded direct set and full transitive `EXTERNLIB` closure, emits `rustc-link-search=native=<root>/lib`, and links that complete closure with `static=`. Use the linker platform's archive group/rescan facility for cyclic OCCT archive references, then link non-OCCT platform libraries dynamically. It must fail by name when the root, header, archive, resource, recorded source revision, closure manifest, or member archive is absent; it must not silently fall back to system OCCT. The portable build script initializes/builds the vendor tree before invoking Cargo without `system-occt`.
- [ ] **Step 4: Verify static linkage, STEP resources, and deterministic behavior**
- [x] **Step 4: Verify static linkage, STEP resources, and deterministic behavior**
```bash
scripts/build-vendored-occt.sh target/vendored-occt
@@ -857,7 +857,7 @@ target/release/vernier-drive scripts/drive/local-linux-release.json --out target
Expected: no OCCT `DT_NEEDED`, STEP read/write resources work, and the existing cross-process release workflow remains deterministic.
- [ ] **Step 5: Commit**
- [x] **Step 5: Commit**
```bash
git add .gitmodules vendor/occt crates/vernier-occt-sys/Cargo.toml crates/vernier-occt-sys/build.rs crates/vernier-kernel/Cargo.toml crates/vernier-app/Cargo.toml crates/vernier-cli/Cargo.toml crates/vernier-drive/Cargo.toml scripts/build-vendored-occt.sh scripts/check-static-occt.sh scripts/check.fish scripts/acceptance-gate.json scripts/test-acceptance-gate.py .gitea/workflows/ci.yaml README.md docs/VENDORED_OCCT_RELEASE.md
+20
View File
@@ -31,6 +31,8 @@
"cargo",
"clippy",
"--all-targets",
"--features",
"system-occt",
"--",
"-D",
"warnings"
@@ -46,6 +48,8 @@
"cargo",
"test",
"--workspace",
"--features",
"system-occt",
"--exclude",
"vernier-drive"
],
@@ -62,6 +66,8 @@
"-q",
"-p",
"vernier-app",
"--features",
"system-occt",
"--bin",
"vernier-worker"
],
@@ -78,6 +84,8 @@
"-q",
"-p",
"vernier-drive",
"--features",
"system-occt",
"--lib"
],
"expected_exit": 0,
@@ -93,6 +101,8 @@
"-q",
"-p",
"vernier-drive",
"--features",
"system-occt",
"--test",
"scripts",
"local_gate"
@@ -110,6 +120,8 @@
"-q",
"-p",
"vernier-app",
"--features",
"system-occt",
"--lib",
"headless::process_tests",
"--",
@@ -128,6 +140,8 @@
"-q",
"-p",
"vernier-app",
"--features",
"system-occt",
"--lib",
"headless::autosave_tests",
"--",
@@ -151,6 +165,8 @@
"vernier-app",
"-p",
"vernier-cli",
"--features",
"system-occt",
"--bin",
"vernier-drive",
"--bin",
@@ -2464,6 +2480,8 @@
"-q",
"-p",
"vernier-app",
"--features",
"system-occt",
"ui_half_coverage",
"--",
"--ignored"
@@ -2481,6 +2499,8 @@
"-q",
"-p",
"vernier-app",
"--features",
"system-occt",
"a_plain_pointer_move",
"--",
"--ignored"
+181
View File
@@ -0,0 +1,181 @@
#!/bin/sh
set -eu
expected_revision=a016080bf6738d6aeae020badee4e888ad1540a5
direct_toolkits='TKernel TKMath TKG2d TKG3d TKGeomBase TKGeomAlgo TKBRep TKTopAlgo TKPrim TKBO TKShHealing TKMesh TKFillet TKOffset TKDESTEP TKDE TKXSBase'
platform_libraries='pthread rt stdc++ dl'
if [ "$#" -ne 1 ] || [ -z "$1" ]; then
echo "usage: $0 OUT" >&2
exit 2
fi
repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
source=$repo/vendor/occt
mkdir -p -- "$1"
out=$(CDPATH= cd -- "$1" && pwd)
build=$out/.build
metadata=$out/share/vernier
if [ ! -d "$source" ] || ! git -C "$source" rev-parse --git-dir >/dev/null 2>&1; then
echo "vendored OCCT: vendor/occt submodule is not initialized" >&2
exit 1
fi
revision=$(git -C "$source" rev-parse HEAD)
if [ "$revision" != "$expected_revision" ]; then
echo "vendored OCCT: expected source $expected_revision, got $revision" >&2
exit 1
fi
if ! git -C "$source" diff --quiet --ignore-submodules -- ||
! git -C "$source" diff --cached --quiet --ignore-submodules --; then
echo "vendored OCCT: vendor/occt has local source changes" >&2
exit 1
fi
mkdir -p -- "$build"
expected_direct=$build/expected-direct-toolkits.txt
expected_closure=$build/expected-toolkit-closure.txt
expected_platform=$build/expected-platform-libs.txt
for toolkit in $direct_toolkits; do printf '%s\n' "$toolkit"; done >"$expected_direct"
for library in $platform_libraries; do printf '%s\n' "$library"; done >"$expected_platform"
VERNIER_OCCT_SOURCE=$source VERNIER_OCCT_DIRECT_TOOLKITS=$direct_toolkits \
python3 - <<'PY' >"$expected_closure"
import os
from pathlib import Path
source = Path(os.environ["VERNIER_OCCT_SOURCE"])
direct = os.environ["VERNIER_OCCT_DIRECT_TOOLKITS"].split()
closure = set()
def visit(toolkit):
if toolkit in closure:
return
externlib = source / "src" / toolkit / "EXTERNLIB"
if not externlib.is_file():
raise SystemExit(f"vendored OCCT: missing EXTERNLIB for {toolkit}")
closure.add(toolkit)
for dependency in externlib.read_text(encoding="utf-8").split():
if dependency.startswith("TK") and (source / "src" / dependency).is_dir():
visit(dependency)
for toolkit in direct:
visit(toolkit)
for toolkit in sorted(closure):
print(toolkit)
PY
validate_prepared() {
test "$(cat "$metadata/occt-source-revision.txt")" = "$expected_revision" || return 1
cmp -s "$expected_direct" "$metadata/occt-direct-toolkits.txt" || return 1
cmp -s "$expected_closure" "$metadata/occt-toolkit-closure.txt" || return 1
cmp -s "$expected_platform" "$metadata/occt-platform-libs.txt" || return 1
test -s "$out/include/opencascade/Standard_Version.hxx" || return 1
test -d "$out/resources/SHMessage" || return 1
test -d "$out/resources/XSMessage" || return 1
test -d "$out/resources/XSTEPResource" || return 1
test -d "$out/resources/StdResource" || return 1
for resource in \
SHMessage/SHAPE.us \
XSMessage/XSTEP.us \
XSTEPResource/STEP \
XSTEPResource/IGES \
StdResource/Standard \
StdResource/XCAF
do
if [ ! -s "$out/resources/$resource" ]; then
echo "vendored OCCT: missing or empty runtime resource: $out/resources/$resource" >&2
return 1
fi
done
test -s "$out/licenses/LICENSE_LGPL_21.txt" || return 1
test -s "$out/licenses/OCCT_LGPL_EXCEPTION.txt" || return 1
while IFS= read -r toolkit; do
test -s "$out/lib/lib${toolkit}.a" || return 1
done <"$expected_closure"
}
if [ -f "$metadata/complete" ]; then
if [ "$(cat "$metadata/complete")" != "vernier.occt-static.v1" ] || ! validate_prepared; then
echo "vendored OCCT: prepared tree is present but fails validation: $out" >&2
exit 1
fi
echo "vendored OCCT: using verified prepared tree at $out"
exit 0
fi
source_date_epoch=$(git -C "$source" show -s --format=%ct HEAD)
export SOURCE_DATE_EPOCH=$source_date_epoch
export ZERO_AR_DATE=1
prefix_map="-ffile-prefix-map=$source=/usr/src/occt -fdebug-prefix-map=$source=/usr/src/occt"
jobs=${VERNIER_OCCT_BUILD_JOBS:-$(getconf _NPROCESSORS_ONLN)}
cmake -S "$source" -B "$build" -G Ninja \
-DINSTALL_DIR="$out" \
-DINSTALL_DIR_INCLUDE=include/opencascade \
-DINSTALL_DIR_LIB=lib \
-DINSTALL_DIR_RESOURCE=resources \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_C_FLAGS_RELEASE="-O2 -DNDEBUG $prefix_map" \
-DCMAKE_CXX_FLAGS_RELEASE="-O2 -DNDEBUG $prefix_map" \
-DBUILD_LIBRARY_TYPE=Static \
-DBUILD_CPP_STANDARD=C++17 \
-DBUILD_ADDITIONAL_TOOLKITS="$direct_toolkits" \
-DBUILD_MODULE_FoundationClasses=OFF \
-DBUILD_MODULE_ModelingData=OFF \
-DBUILD_MODULE_ModelingAlgorithms=OFF \
-DBUILD_MODULE_Visualization=OFF \
-DBUILD_MODULE_ApplicationFramework=OFF \
-DBUILD_MODULE_DataExchange=OFF \
-DBUILD_MODULE_DETools=OFF \
-DBUILD_MODULE_Draw=OFF \
-DBUILD_SAMPLES_QT=OFF \
-DBUILD_Inspector=OFF \
-DBUILD_DOC_Overview=OFF \
-DBUILD_YACCLEX=OFF \
-DUSE_TK=OFF \
-DUSE_TBB=OFF \
-DUSE_FREETYPE=OFF \
-DUSE_FREEIMAGE=OFF \
-DUSE_FFMPEG=OFF \
-DUSE_OPENVR=OFF \
-DUSE_RAPIDJSON=OFF \
-DUSE_DRACO=OFF \
-DUSE_EIGEN=OFF \
-DUSE_VTK=OFF \
-DUSE_XLIB=OFF \
-DUSE_OPENGL=OFF
cmake --build "$build" --parallel "$jobs"
cmake --install "$build"
mkdir -p -- "$metadata" "$out/licenses"
install -m 0644 "$source/LICENSE_LGPL_21.txt" "$out/licenses/LICENSE_LGPL_21.txt"
install -m 0644 "$source/OCCT_LGPL_EXCEPTION.txt" "$out/licenses/OCCT_LGPL_EXCEPTION.txt"
printf '%s\n' "$revision" >"$metadata/occt-source-revision.txt"
install -m 0644 "$expected_direct" "$metadata/occt-direct-toolkits.txt"
install -m 0644 "$expected_closure" "$metadata/occt-toolkit-closure.txt"
install -m 0644 "$expected_platform" "$metadata/occt-platform-libs.txt"
{
printf 'schema=vernier.occt-static.v1\n'
printf 'source_revision=%s\n' "$revision"
printf 'source_date_epoch=%s\n' "$source_date_epoch"
printf 'build_type=Release\n'
printf 'library_type=Static\n'
printf 'cpp_standard=C++17\n'
printf 'position_independent_code=ON\n'
printf 'optional_dependencies=OFF\n'
printf 'cmake=%s\n' "$(cmake --version | sed -n '1p')"
printf 'cxx=%s\n' "$(c++ --version | sed -n '1p')"
} >"$metadata/occt-build-config.txt"
printf 'vernier.occt-static.v1\n' >"$metadata/complete"
if ! validate_prepared; then
echo "vendored OCCT: installed tree failed completeness validation: $out" >&2
exit 1
fi
if find "$out/lib" -maxdepth 1 -type f -name 'libTK*.so*' -print -quit | grep -q .; then
echo "vendored OCCT: static tree unexpectedly contains shared OCCT libraries" >&2
exit 1
fi
echo "vendored OCCT: prepared $revision at $out"
+7 -7
View File
@@ -4,16 +4,16 @@
cargo fmt --check
and fish scripts/test-gate-step.fish
and cargo clippy --all-targets -- -D warnings
and cargo clippy --all-targets --features system-occt -- -D warnings
# The drive crate's integration tests are long GUI acceptance programs. Its
# unit tests stay here; the smoke workflow below covers the real shell once.
and cargo test --workspace --exclude vernier-drive
and cargo build -q -p vernier-app --bin vernier-worker
and cargo test -q -p vernier-drive --lib
and cargo test -q -p vernier-drive --test scripts \
and cargo test --workspace --features system-occt --exclude vernier-drive
and cargo build -q -p vernier-app --features system-occt --bin vernier-worker
and cargo test -q -p vernier-drive --features system-occt --lib
and cargo test -q -p vernier-drive --features system-occt --test scripts \
the_local_gate_runs_driven_acceptance_once_and_includes_lettering -- --exact
and cargo run -q -p vernier-cli -- --selftest
and cargo build -q -p vernier-drive
and cargo run -q -p vernier-cli --features system-occt -- --selftest
and cargo build -q -p vernier-drive --features system-occt
and target/debug/vernier-drive scripts/drive/m1-smoke.json \
--out target/drive/fast-m1-smoke --once --skip-png \
--require-adapter RADV --worker-helper target/debug/vernier-worker
+42
View File
@@ -0,0 +1,42 @@
#!/bin/sh
set -eu
if [ "$#" -eq 0 ]; then
echo "usage: $0 BINARY..." >&2
exit 2
fi
status=0
for binary in "$@"; do
if [ ! -f "$binary" ]; then
echo "static OCCT check: missing binary: $binary" >&2
status=1
continue
fi
needed=$(readelf -d "$binary") || {
echo "static OCCT check: readelf failed: $binary" >&2
status=1
continue
}
occt_needed=$(printf '%s\n' "$needed" | sed -n 's/.*Shared library: \[\(libTK[^]]*\)\].*/\1/p')
if [ -n "$occt_needed" ]; then
printf 'static OCCT check: %s has OCCT DT_NEEDED entries:\n%s\n' \
"$binary" "$occt_needed" >&2
status=1
fi
resolved=$(ldd "$binary" 2>&1) || {
echo "static OCCT check: ldd failed: $binary" >&2
status=1
continue
}
system_occt=$(printf '%s\n' "$resolved" | sed -n '/libTK[^ ]*[[:space:]]*=>[[:space:]]*\//p')
if [ -n "$system_occt" ]; then
printf 'static OCCT check: %s resolves OCCT from the host:\n%s\n' \
"$binary" "$system_occt" >&2
status=1
fi
done
exit "$status"
+2 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env fish
# Full release gate. The manifest is the single step list consumed locally
# and by CI; Task 16 runs this complete selection once before installation.
# and by CI; its Cargo rows explicitly select the fast system-occt path.
# Task 16 runs this complete selection once before installation.
python3 scripts/run-acceptance-gate.py --adapter RADV
+34
View File
@@ -3,6 +3,7 @@
import json
import pathlib
import shlex
import subprocess
import sys
import tempfile
@@ -80,6 +81,39 @@ class AcceptanceManifestTests(unittest.TestCase):
self.assertEqual(by_id[negative]["expected_exit"], 1)
self.assertNotIn("--once", by_id[positive]["argv"])
def test_normal_cargo_compilation_steps_select_system_occt(self):
def assert_system_occt(argv, label):
self.assertIn("--features", argv, label)
feature_index = argv.index("--features") + 1
self.assertLess(feature_index, len(argv), label)
self.assertIn("system-occt", argv[feature_index].split(","), label)
cargo_steps = [
step
for step in self.manifest["steps"]
if step["argv"][0] == "cargo" and step["argv"][1] != "fmt"
]
self.assertTrue(cargo_steps)
for step in cargo_steps:
assert_system_occt(step["argv"], step["id"])
fast_gate = (ROOT / "scripts" / "check-fast.fish").read_text(
encoding="utf-8"
)
logical_lines = fast_gate.replace("\\\n", " ").splitlines()
fast_cargo = []
for line in logical_lines:
command = line.strip()
if command.startswith("and "):
command = command[4:]
if command.startswith("cargo "):
argv = shlex.split(command)
if argv[1] != "fmt":
fast_cargo.append(argv)
self.assertTrue(fast_cargo)
for argv in fast_cargo:
assert_system_occt(argv, "scripts/check-fast.fish: " + " ".join(argv))
def test_runner_preserves_stdout_stderr_and_exact_expected_exit(self):
with tempfile.TemporaryDirectory(prefix="vernier-acceptance-test-") as raw:
out = pathlib.Path(raw) / "out"
Vendored Submodule
+1
Submodule vendor/occt added at a016080bf6