Skip to content

Commit fa3d45e

Browse files
committed
Add a freestanding compile check
1 parent c25b253 commit fa3d45e

4 files changed

Lines changed: 192 additions & 2 deletions

File tree

.github/freestanding/smoketest.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Translation unit for the freestanding compile check
2+
// (.github/scripts/check_freestanding.sh).
3+
//
4+
// It pulls in the modules the downstream flight software uses, because the
5+
// freestanding configuration is guarded module by module: a macro or shim that
6+
// is wrong for Geometry or Eigenvalues will not show up from Eigen/Core alone.
7+
// Nothing here needs to run -- the check is compile-only, and the bugs it exists
8+
// to catch (a leaked macro, a stray #pragma message) surface while the
9+
// preprocessor and front end walk these headers.
10+
11+
#include <Eigen/Core>
12+
#include <Eigen/Geometry>
13+
#include <Eigen/Eigenvalues>
14+
#include <Eigen/SVD>
15+
#include <Eigen/LU>
16+
17+
// Instantiate a little of each module so the definitions are actually walked
18+
// rather than just parsed. Kept to float and double, the two scalar types the
19+
// flight software uses.
20+
namespace {
21+
22+
template <typename Scalar>
23+
void touchEveryModule() {
24+
using Vector3 = Eigen::Matrix<Scalar, 3, 1>;
25+
using Matrix3 = Eigen::Matrix<Scalar, 3, 3>;
26+
27+
Vector3 a = Vector3::Zero();
28+
Vector3 b = Vector3::UnitZ();
29+
Matrix3 m = Matrix3::Identity();
30+
31+
// Core
32+
(void)a.dot(b);
33+
(void)a.norm();
34+
(void)(m * a);
35+
36+
// Geometry: declared in Eigen/Core, defined in Eigen/src/Geometry.
37+
(void)a.cross(b);
38+
(void)b.unitOrthogonal();
39+
40+
// LU
41+
(void)m.determinant();
42+
43+
// Eigenvalues
44+
Eigen::SelfAdjointEigenSolver<Matrix3> eig(m);
45+
(void)eig.eigenvalues();
46+
47+
// SVD
48+
Eigen::JacobiSVD<Matrix3> svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV);
49+
(void)svd.singularValues();
50+
}
51+
52+
} // namespace
53+
54+
int main() {
55+
touchEveryModule<float>();
56+
touchEveryModule<double>();
57+
return 0;
58+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env bash
2+
# check_freestanding.sh
3+
#
4+
# Compiles .github/freestanding/smoketest.cpp in both configurations this fork
5+
# has to support, and fails if the compiler says anything at all.
6+
#
7+
# Why "anything at all" rather than just a non-zero exit: the defects this check
8+
# exists to catch are not errors. When the freestanding guards were added,
9+
# GeneralBlockPanelKernel.h's four #undef PACKET_DECL_COND* lines ended up inside
10+
# an #ifndef EIGEN_FREESTANDING block, so in freestanding builds the macros
11+
# leaked out of Eigen/Core and one of them was redefined in
12+
# GeneralMatrixVector.h. Every downstream translation unit warned; nothing
13+
# failed. Same for the unconditional #pragma message in all_freestanding.hpp.
14+
# Both are invisible to a check that only looks at the exit status.
15+
#
16+
# Both configurations are checked because the two can break independently: the
17+
# macro leak appeared only with EIGEN_FREESTANDING defined, and a careless fix
18+
# could break the hosted path instead.
19+
#
20+
# Usage: check_freestanding.sh
21+
# Environment:
22+
# EIGEN3_DIR Root of the Eigen checkout (the directory containing 'Eigen/').
23+
# Defaults to the repository root, so it normally needs no setting.
24+
# CXX Compiler to use. Defaults to g++-13, the version the downstream
25+
# flight software builds with.
26+
27+
set -euo pipefail
28+
29+
CXX="${CXX:-g++-13}"
30+
31+
if ! REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"; then
32+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
33+
fi
34+
EIGEN3_DIR="${EIGEN3_DIR:-$REPO_ROOT}"
35+
36+
if [[ ! -d "${EIGEN3_DIR}/Eigen" ]]; then
37+
echo "Eigen not found under ${EIGEN3_DIR}" >&2
38+
exit 2
39+
fi
40+
if ! command -v "${CXX}" >/dev/null 2>&1; then
41+
echo "Compiler '${CXX}' not found; set CXX to an available compiler." >&2
42+
exit 2
43+
fi
44+
45+
TU="${REPO_ROOT}/.github/freestanding/smoketest.cpp"
46+
if [[ ! -f "$TU" ]]; then
47+
echo "Translation unit not found: $TU" >&2
48+
exit 2
49+
fi
50+
51+
FORCE_INCLUDE="${EIGEN3_DIR}/Eigen/src/Freestanding/all_freestanding.hpp"
52+
# -Wundef matters specifically: Eigen's own test suite compiles with it, and the
53+
# freestanding guards are the kind of thing that gets written as `!EIGEN_FREESTANDING`
54+
# instead of `!defined(EIGEN_FREESTANDING)`, which silently evaluates to 0 in every
55+
# hosted translation unit. Not -Wall/-Wextra yet: the freestanding stubs in
56+
# Memory.h have pre-existing unused-parameter warnings that need fixing first.
57+
COMMON=(-std=gnu++23 -fsyntax-only -Wundef -I"${EIGEN3_DIR}")
58+
59+
FAILURES=0
60+
61+
# $1 = human-readable configuration name, remaining args = extra compiler flags
62+
check_config() {
63+
local name="$1"
64+
shift
65+
66+
local output
67+
if ! output="$("${CXX}" "${COMMON[@]}" "$@" "$TU" 2>&1)" || [[ -n "$output" ]]; then
68+
echo "::error::${name} configuration is not clean"
69+
echo "$output" | sed 's/^/ /'
70+
FAILURES=$((FAILURES + 1))
71+
else
72+
echo " ${name}: clean"
73+
fi
74+
}
75+
76+
echo "Checking ${TU#"$REPO_ROOT"/} with ${CXX}"
77+
check_config "freestanding" -DEIGEN_FREESTANDING=1 -include "${FORCE_INCLUDE}"
78+
check_config "hosted"
79+
80+
# Approximates a non-x86 target on an x86 runner. EIGEN_HAS_CXX11_MATH is 1 on
81+
# x86 (Macros.h requires EIGEN_ARCH_i386_OR_x86_64), which short-circuits
82+
# MathFunctions.h:503 and hides anything the branch below it depends on. Forcing
83+
# it to 0 exercises that path without a cross toolchain -- it is how the
84+
# EIGEN_HAS_C99_MATH block was found to be trapped inside a freestanding guard.
85+
check_config "freestanding, non-x86 math path" \
86+
-DEIGEN_FREESTANDING=1 -DEIGEN_HAS_CXX11_MATH=0 -include "${FORCE_INCLUDE}"
87+
88+
if [[ $FAILURES -gt 0 ]]; then
89+
echo ""
90+
echo "${FAILURES} configuration(s) produced compiler output."
91+
exit 1
92+
fi
93+
94+
echo "All configurations compile silently."

.github/workflows/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# CI in this fork
22

3-
This is a LASP fork of Eigen 3.4. The CI here is deliberately small: it covers what
4-
the fork actually needs rather than upstream's full validation matrix.
3+
This is a LASP fork of Eigen 3.4 whose purpose is freestanding support, so the CI
4+
here covers that, not upstream's full validation matrix.
55

66
| Workflow | What it does |
77
|---|---|
8+
| `freestanding.yml` | Compiles `Eigen/{Core,Geometry,Eigenvalues,SVD,LU}` in three configurations — freestanding, hosted, and freestanding with `EIGEN_HAS_CXX11_MATH=0` (which approximates a non-x86 target) — and fails on *any* compiler output. Seconds to run. |
89
| `smoketests.yml` | Builds and runs Eigen's smoke test subset (`cmake/EigenSmokeTestList.cmake`, 105 resolve in the CI configuration) under gcc-13 with `EIGEN_TEST_CXX11` on and off, and under clang-18 with it on. |
910

1011
Upstream's own CI is GitLab and lives in `.gitlab-ci.yml` plus `ci/`. It still runs

.github/workflows/freestanding.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Freestanding
2+
3+
# Compiles Eigen in the freestanding configuration this fork exists to provide,
4+
# and in the hosted configuration, failing if the compiler emits anything.
5+
#
6+
# This is the only CI here that covers the fork's actual purpose. Upstream's test
7+
# matrix never defines EIGEN_FREESTANDING, so it cannot catch a defect that only
8+
# appears in freestanding builds -- which is exactly what the leaked
9+
# PACKET_DECL_COND* macros were. See .github/scripts/check_freestanding.sh.
10+
#
11+
# Cheap enough (two -fsyntax-only compiles) to run on every push.
12+
13+
on:
14+
pull_request:
15+
types: [opened, reopened, synchronize]
16+
push:
17+
branches: [feature/freestanding]
18+
workflow_dispatch:
19+
20+
jobs:
21+
freestanding:
22+
name: freestanding compile check
23+
runs-on: ubuntu-24.04
24+
timeout-minutes: 15
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Install compiler
30+
run: |
31+
sudo apt-get update -y
32+
sudo apt-get install -y --no-install-recommends g++-13
33+
34+
- name: Check freestanding and hosted configurations
35+
env:
36+
CXX: g++-13
37+
run: ./.github/scripts/check_freestanding.sh

0 commit comments

Comments
 (0)