build: vendor OCCT for static releases
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "vendor/occt"]
|
||||
path = vendor/occt
|
||||
url = https://github.com/Open-Cascade-SAS/OCCT.git
|
||||
@@ -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 | — |
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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`
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Executable
+181
@@ -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"
|
||||
@@ -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
|
||||
|
||||
Executable
+42
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+1
Submodule vendor/occt added at a016080bf6
Reference in New Issue
Block a user