Repository navigation
421 lines (415 loc) · 25.7 KB
/
Copy pathbuild-engines.yml
File metadata and controls
421 lines (415 loc) · 25.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# ─────────────────────────────────────────────────────────────────────────────
# Build the engine binaries — every language, every platform — as artifacts.
#
# Reusable (workflow_call) so publish-npm.yml can build then publish in one run, and
# dispatchable on its own to check that the rules still compile everywhere without
# publishing anything. Soufflé is a BUILD-time dependency only: `souffle -g` turns each
# language's rules into portable C++ once on Ubuntu (the pinned .deb), and every platform
# compiles that C++ with its own C++17 compiler against Soufflé's headers. The result is
# one self-contained executable per language per platform, linked against nothing but the
# C++ runtime. Same flags as the local compile (no zlib/sqlite), portable targets. Every language
# engine is the PARALLEL (OpenMP) flavor on every platform, its OpenMP runtime linked in statically
# (Linux libgomp.a, macOS a libomp.a built here for macOS 12) or, on Windows, shipped beside it.
#
# Artifacts: engines-<platform>/ holding <lang>/axiomcode-engine-<lang>[.exe] + <lang>/ENGINE_ID,
# and queries/axiomcode-query-<name>[.exe] + queries/<name>.id for every query program the CLI
# runs (plugins/axiomcode/skills/axiomcode/scripts/dl/<name>.dl: impact, path, path-opt,
# path-every). Without those, `axiomcode impact` needs Soufflé and a C++ compiler on the user's
# machine (#1330). A query binary's id is dl_program.py's own key, so the CLI finds it by the
# same hash it would cache a local compile under.
# Platforms are named the npm way (process.platform-process.arch): darwin-arm64, darwin-x64,
# linux-x64, linux-arm64, win32-x64, each on a standard GitHub-hosted runner (free for a
# public repository; macOS builds on macos-15 for Apple Silicon and macos-15-intel for Intel).
# ─────────────────────────────────────────────────────────────────────────────
name: build-engines
on:
workflow_call:
inputs:
# fresh: compile every engine, restore nothing. The nightly and a release use it.
fresh:
type: boolean
default: false
workflow_dispatch:
inputs:
fresh:
type: boolean
default: false
# COMPILED ENGINES ARE CACHED BY ENGINE_ID. Compiling one language's C++ at -O3 takes
# about three minutes, and a run that changed no rules used to recompile all five on
# every platform. Each platform now restores its last engines and recompiles only the
# languages whose ENGINE_ID — a hash of that language's rules and the Soufflé version —
# differs from the one the restored binary records. A binary is therefore reused only
# for exactly the rules it was built from; the smoke step still runs every one.
env:
LANGUAGES: java typescript python javascript csharp
# Same pin and checksum as ci.yml: the tool that produces the published binaries is
# verified against what upstream published for that release.
SOUFFLE_SHA512: '6b86e554f6aa5abf8a8b55d8312ae37c0957c5bd6c9edeea89246db9406f645ec5e600b84fe6636b1c163da556f0da6c3d2dad46c1083413f2fcf4f95b9ac62c'
jobs:
generate:
runs-on: ubuntu-24.04
outputs:
key: ${{ steps.gen.outputs.key }}
flags: ${{ steps.gen.outputs.flags }}
steps:
- uses: actions/checkout@v4
- name: Install the pinned Soufflé
run: |
. graph/pipeline/engine.conf
deb="x86_64-ubuntu-2404-souffle-${SOUFFLE_VERSION}-Linux.deb"
curl -fsSL --retry 3 -o "/tmp/$deb" "https://github.com/souffle-lang/souffle/releases/download/${SOUFFLE_VERSION}/$deb"
echo "${SOUFFLE_SHA512} /tmp/$deb" | sha512sum -c -
sudo apt-get update -qq && sudo apt-get install -y -qq "/tmp/$deb"
souffle --version | head -2
test -f /usr/include/souffle/CompiledSouffle.h
- name: Generate portable C++ per language
id: gen
run: |
set -e
mkdir -p gen
for lang in $LANGUAGES; do
id="$(bash graph/pipeline/run-souffle.sh --language "$lang" --print-engine-id)"
echo "$lang: $id"; printf '%s' "$id" > "gen/$lang.id"
bash graph/pipeline/run-souffle.sh --language "$lang" --emit-program "gen/$lang.dl"
# -j makes souffle EMIT the parallel loops (without it the C++ has none); whether a
# platform's binary runs them in parallel is decided at compile time below.
souffle -I graph -j 8 -g "gen/$lang.cpp" "gen/$lang.dl" 2> "gen/$lang.gen.log" || { cat "gen/$lang.gen.log"; exit 1; }
awk '/No rules\/facts defined/{skip=2;next} skip>0{skip--;next} {print}' "gen/$lang.gen.log"
done
# the query programs: self-contained (no #include), keyed by dl_program.py itself
mkdir -p gen/queries
for dl in plugins/axiomcode/skills/axiomcode/scripts/dl/*.dl; do
q="$(basename "$dl" .dl)"
python3 plugins/axiomcode/skills/axiomcode/scripts/dl_program.py --print-id "$dl" | tr -d '\n' > "gen/queries/$q.id"
echo "query $q: $(cat "gen/queries/$q.id")"
cp "$dl" "gen/queries/$q.dl"
souffle -g "gen/queries/$q.cpp" "$dl" 2> "gen/queries/$q.gen.log" || { cat "gen/queries/$q.gen.log"; exit 1; }
done
# the fixture and what the interpreter derives from it; each platform's binaries must match
bash .github/scripts/query-smoke.sh expect gen/queries
cp .github/scripts/query-smoke.sh gen/queries/smoke.sh
cp -r /usr/include/souffle gen/souffle
# the seqlock fix (see souffle_overlay in run-souffle.sh): the write-entry RMW
# must be seq_cst or a weakly-ordered CPU lets data stores pass the version-odd
# store and readers validate garbage. Patched here so every platform's binary
# is built from the same fixed headers; the engine id carries +seqlock-fix-4,
# so these headers must carry the SAME patches run-souffle's overlay applies —
# a binary labeled fix-4 but built from lesser headers is the exact mislabeling
# the salt exists to prevent.
# the header layout differs by install (deb: souffle/utility; brew: souffle/souffle/utility),
# so the patched files are FOUND, not assumed — a miss fails the build here, loudly
PU=$(find gen/souffle -name ParallelUtil.h | head -1); [ -n "$PU" ]
sed -i 's/version\.fetch_or(0x1, std::memory_order_acquire)/version.fetch_or(0x1, std::memory_order_seq_cst)/g' "$PU"
grep -q 'fetch_or(0x1, std::memory_order_seq_cst)' "$PU"
! grep -q 'fetch_or(0x1, std::memory_order_acquire)' "$PU"
# fix-3: the publication fences, applied by the same anchored patch the local
# overlay uses (extracted from run-souffle.sh so the two cannot drift)
BT=$(find gen/souffle -name BTree.h | head -1); [ -n "$BT" ]
awk '/^import glob, os, sys$/,/^PYEOF$/' graph/pipeline/run-souffle.sh | sed '$d' > /tmp/fences.py
python3 /tmp/fences.py "$BT"
[ "$(grep -c 'seqlock-fix-3' "$BT")" = "4" ]
# fix-4: the speculative-fetch guards, applied by the same extracted patch
FW=$(find gen/souffle -name ConcurrentFlyweight.h | head -1); [ -n "$FW" ]
RT=$(find gen/souffle -name RecordTableImpl.h | head -1); [ -n "$RT" ]
[ "$(grep -c 'seqlock-fix-4' "$FW")" = "1" ]
[ "$(grep -c 'seqlock-fix-4' "$RT")" = "1" ]
[ "$(grep -c 'seqlock-fix-4' "$BT")" = "1" ]
BD=$(find gen/souffle -name BTreeDelete.h | head -1); [ -n "$BD" ]
[ "$(grep -c 'seqlock-fix-4' "$BD")" = "1" ]
# one key for the whole set; a partial match restores the previous set
echo "key=$(cat gen/*.id gen/queries/*.id | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT"
# The compile flags live in THIS file and ENGINE_ID does not cover them, so its
# hash prefixes the key AND the restore prefix: a flag change restores nothing.
# Computed here because the build jobs never check the repository out.
echo "flags=$(sha256sum .github/workflows/build-engines.yml | cut -c1-12)" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
with: { name: generated, path: gen, retention-days: 3, if-no-files-found: error }
build:
needs: generate
strategy:
fail-fast: false
matrix:
target:
- { os: ubuntu-24.04, platform: linux-x64, manylinux: quay.io/pypa/manylinux_2_28_x86_64 }
- { os: ubuntu-24.04-arm, platform: linux-arm64, manylinux: quay.io/pypa/manylinux_2_28_aarch64 }
- { os: windows-2025, platform: win32-x64 }
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/download-artifact@v4
with: { name: generated, path: gen }
- name: restore the engines built from these rules last time
id: restore
if: ${{ !inputs.fresh }}
uses: actions/cache/restore@v4
with:
path: engines
key: engines-${{ matrix.target.platform }}-${{ needs.generate.outputs.flags }}-${{ needs.generate.outputs.key }}
restore-keys: engines-${{ matrix.target.platform }}-${{ needs.generate.outputs.flags }}-
# THE GLIBC FLOOR. A binary links against the glibc of the machine that built it, and runs only where that
# glibc or a newer one is installed. Built on ubuntu-24.04 (glibc 2.39) every engine needed GLIBC_2.38, so it
# would not start on Ubuntu 22.04, Debian 12, RHEL 9 or Amazon Linux 2023: the official python and node
# Docker images, most CI and most servers. The compile therefore runs inside manylinux_2_28 (glibc 2.28,
# the floor Python wheels use), and the step after it fails the build if any binary asks for more.
- name: Compile every language (Linux, manylinux_2_28)
if: startsWith(matrix.target.platform, 'linux')
env:
MANYLINUX: ${{ matrix.target.manylinux }}
run: |
set -e
docker run --rm -v "$PWD:/w" -w /w -e LANGUAGES="$LANGUAGES" -u "$(id -u):$(id -g)" "$MANYLINUX" bash -ec '
c++ --version | head -1; ldd --version | head -1
for lang in $LANGUAGES; do
if cmp -s "gen/$lang.id" "engines/$lang/ENGINE_ID"; then echo "$lang: cached, rules unchanged"; continue; fi
mkdir -p "engines/$lang"
# OpenMP on BOTH architectures: the arm64 crash modes were weak-ordering holes in
# the vendored headers (write-entry RMW + unfenced node publication, seqlock-fix-3)
# and a null child read by the optimistic descent (seqlock-fix-4), all patched above
# and validated under load on linux-arm64 and darwin-arm64. The .parallel marker beside the binary is what
# run-souffle.sh reads to pass a real -j at run time.
# libgomp links STATICALLY so the binary runs on machines with no gcc runtime
# (a stock Ubuntu has no libgomp.so.1). -fopenmp on the LINK line makes the driver
# append its own dynamic -lgomp, whatever -Wl,-Bstatic says, so -fopenmp compiles
# only, and the link names libgomp.a by path; libgomp.a needs -ldl.
c++ -std=c++17 -O3 -w -fopenmp -I gen -c "gen/$lang.cpp" -o "gen/$lang.o"
c++ "gen/$lang.o" -o "engines/$lang/axiomcode-engine-$lang" -static-libstdc++ -static-libgcc "$(c++ -print-file-name=libgomp.a)" -lpthread -ldl
rm -f "gen/$lang.o"
touch "engines/$lang/axiomcode-engine-$lang.parallel"
cp "gen/$lang.id" "engines/$lang/ENGINE_ID"
done
mkdir -p engines/queries
for cpp in gen/queries/*.cpp; do
q="$(basename "$cpp" .cpp)"
if cmp -s "gen/queries/$q.id" "engines/queries/$q.id"; then echo "query $q: cached, rules unchanged"; continue; fi
c++ -std=c++17 -O3 -w -static-libstdc++ -static-libgcc -I gen "$cpp" -o "engines/queries/axiomcode-query-$q"
cp "gen/queries/$q.id" "engines/queries/$q.id"
done
'
ls -la engines/*; ldd engines/java/axiomcode-engine-java || true
- name: Linux binaries need no glibc newer than 2.28
if: startsWith(matrix.target.platform, 'linux')
run: |
set -e
bad=0
for f in engines/*/axiomcode-*; do
need="$(objdump -T "$f" | grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -V | tail -1)"
echo "$f needs glibc $need"
if [ "$(printf '%s\n2.28\n' "$need" | sort -V | tail -1)" != 2.28 ]; then echo "::error::$f needs glibc $need (> 2.28)"; bad=1; fi
done
exit $bad
# A RUNTIME A CLEAN MACHINE HAS. A binary that loads a library the build host has and a user's machine does not
# starts here and fails there: libgomp.so.1 is not on a stock Ubuntu. Every engine may need only the C library
# and the loader.
- name: Linux binaries load nothing a stock system lacks
if: startsWith(matrix.target.platform, 'linux')
run: |
set -e
bad=0
for f in engines/*/axiomcode-*; do
case "$f" in *.parallel) continue;; esac
extra="$(readelf -d "$f" | sed -n 's/.*(NEEDED).*\[\(.*\)\]/\1/p' | grep -vE '^(libc|libm|libpthread|libdl|librt|ld-linux[-a-z0-9_]*)\.so' || true)"
if [ -n "$extra" ]; then echo "::error::$f needs $(echo $extra) — not on a stock system"; bad=1; fi
done
exit $bad
- uses: ilammy/msvc-dev-cmd@v1
if: startsWith(matrix.target.platform, 'win32')
with: { arch: x64 }
# OPENMP ON WINDOWS, RUNTIME SHIPPED BESIDE THE ENGINE. /openmp links vcomp140.dll, which a Windows machine
# without the Visual C++ Redistributable does not have: alone, every language engine exits 0xC0000135 before it
# reads a fact. So the step after this copies it (and any VC runtime DLL it imports; MSVC 14.44's imports only
# kernel32) from the MSVC redist folder into each engine's own directory: Windows searches the executable's
# folder first, and the redistributable licence allows this app-local copy. The CRT itself stays static (cl's default /MT). The
# .parallel marker beside each engine makes run-souffle.sh pass a real -j. The query programs are generated
# without -j, so they need no OpenMP and stay as before.
- name: Compile every language (Windows, MSVC)
if: startsWith(matrix.target.platform, 'win32')
shell: cmd
run: |
for %%L in (java typescript python javascript csharp) do (
fc /b gen\%%L.id engines\%%L\ENGINE_ID >nul 2>&1
if errorlevel 1 (
if not exist engines\%%L mkdir engines\%%L
cl /nologo /std:c++17 /O2 /EHsc /bigobj /w /permissive- /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS /DNOMINMAX /DUSE_CUSTOM_GETOPTLONG /openmp /I gen gen\%%L.cpp /Fe:engines\%%L\axiomcode-engine-%%L.exe
if errorlevel 1 exit /b 1
type nul > engines\%%L\axiomcode-engine-%%L.exe.parallel
copy /y gen\%%L.id engines\%%L\ENGINE_ID
) else (
echo %%L: cached, rules unchanged
)
)
if not exist engines\queries mkdir engines\queries
for %%Q in (gen\queries\*.cpp) do (
fc /b gen\queries\%%~nQ.id engines\queries\%%~nQ.id >nul 2>&1
if errorlevel 1 (
cl /nologo /std:c++17 /O2 /EHsc /bigobj /w /permissive- /Zc:__cplusplus /D_CRT_SECURE_NO_WARNINGS /DNOMINMAX /DUSE_CUSTOM_GETOPTLONG /I gen %%Q /Fe:engines\queries\axiomcode-query-%%~nQ.exe
if errorlevel 1 exit /b 1
copy /y gen\queries\%%~nQ.id engines\queries\%%~nQ.id
) else (
echo query %%~nQ: cached, rules unchanged
)
)
dir /s engines
- name: Ship the OpenMP runtime beside each Windows engine
if: startsWith(matrix.target.platform, 'win32')
shell: bash
run: |
set -e
redist="$(cygpath -u "$VCToolsRedistDir")"
omp="$(ls -d "$redist"x64/Microsoft.VC*.OpenMP | tail -1)"; crt="$(ls -d "$redist"x64/Microsoft.VC*.CRT | tail -1)"
[ -f "$omp/vcomp140.dll" ]; echo "OpenMP runtime: $omp; CRT: $crt"
deps(){ dumpbin //nologo //dependents "$1" | grep -ioE '^ +[a-z0-9_.-]+\.dll' | tr -d ' ' | tr 'A-Z' 'a-z'; }
for lang in $LANGUAGES; do
cp "$omp/vcomp140.dll" "engines/$lang/"
# whatever vcomp140.dll itself imports from the VC runtime folder (none for 14.44); the UCRT is part of Windows
for d in $(deps "$omp/vcomp140.dll"); do if [ -f "$crt/$d" ]; then cp "$crt/$d" "engines/$lang/$d"; fi; done
ls "engines/$lang"
done
# A RUNTIME A CLEAN MACHINE HAS. Every DLL an engine, or a DLL shipped beside it, imports must be part of Windows
# itself or sit in the same folder. A test for "present in System32" would pass here and nowhere else: a runner
# with Visual Studio has the VC runtime in System32, a stock Windows does not.
- name: Windows binaries load nothing a stock system lacks
if: startsWith(matrix.target.platform, 'win32')
shell: bash
run: |
set -e
os='^(kernel32|kernelbase|ntdll|advapi32|user32|gdi32|ws2_32|shell32|ole32|oleaut32|bcrypt|crypt32|rpcrt4|ucrtbase|api-ms-win-[a-z0-9-]+|ext-ms-win-[a-z0-9-]+)\.dll$'
deps(){ dumpbin //nologo //dependents "$1" | grep -ioE '^ +[a-z0-9_.-]+\.dll' | tr -d ' ' | tr 'A-Z' 'a-z'; }
bad=0
for f in engines/*/*.exe engines/*/*.dll; do
for d in $(deps "$f"); do
if echo "$d" | grep -qE "$os" || [ -f "$(dirname "$f")/$d" ]; then continue; fi
echo "::error::$f needs $d — not part of Windows and not beside it"; bad=1
done
done
for lang in $LANGUAGES; do
if [ -f "engines/$lang/axiomcode-engine-$lang.exe.parallel" ] && ! deps "engines/$lang/axiomcode-engine-$lang.exe" | grep -qx vcomp140.dll; then
echo "::error::$lang carries a .parallel marker but was not built with /openmp"; bad=1
fi
done
exit $bad
- name: Smoke — every binary starts on empty inputs
shell: bash
run: |
set -e
for lang in $LANGUAGES; do
# the binary itself, not the .parallel marker (or a DLL) beside it
bin="engines/$lang/axiomcode-engine-$lang"; [ -f "$bin.exe" ] && bin="$bin.exe"; chmod +x "$bin" 2>/dev/null || true
mkdir -p "facts-$lang" "out-$lang"
sed -n 's/^\.input \([A-Za-z0-9_]*\)(.*/\1/p' "gen/$lang.dl" | while read -r r; do : > "facts-$lang/$r.facts"; done
"./$bin" -F "facts-$lang" -D "out-$lang"
echo "$lang: ok ($(ls out-$lang | wc -l) relations written)"
done
bash gen/queries/smoke.sh check gen/queries engines/queries
- name: save the engines for the next run
if: ${{ !inputs.fresh && steps.restore.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: engines
key: engines-${{ matrix.target.platform }}-${{ needs.generate.outputs.flags }}-${{ needs.generate.outputs.key }}
# publish-npm ships these binaries from the release's CI run, whenever the draft is published
- uses: actions/upload-artifact@v4
with:
name: engines-${{ matrix.target.platform }}
path: engines
retention-days: 90
if-no-files-found: error
build-macos:
needs: generate
strategy:
fail-fast: false
matrix:
target:
# each on its own architecture's runner, so the smoke step runs the binary natively
- { os: macos-15, arch: arm64, platform: darwin-arm64 }
- { os: macos-15-intel, arch: x86_64, platform: darwin-x64 }
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/download-artifact@v4
with: { name: generated, path: gen }
- name: restore the engines built from these rules last time
id: restore
if: ${{ !inputs.fresh }}
uses: actions/cache/restore@v4
with:
path: engines
key: engines-${{ matrix.target.platform }}-${{ needs.generate.outputs.flags }}-${{ needs.generate.outputs.key }}
restore-keys: engines-${{ matrix.target.platform }}-${{ needs.generate.outputs.flags }}-
# A STATIC LIBOMP FOR MACOS 12. Apple clang lowers the OpenMP pragmas (-Xclang -fopenmp) but ships no runtime.
# Homebrew's libomp is built for the runner's own macOS (minos 15 on macos-15), too new for the engines' 12.0
# floor, and its dylib lives under /opt/homebrew, which a user's Mac does not have. So the runtime is built here
# from the pinned LLVM release with a 12.0 deployment target, static only, and linked by path: otool -L of an
# engine then lists nothing but the system's libc++ and libSystem.
- name: Static libomp for macOS 12 (${{ matrix.target.arch }})
env:
ARCH: ${{ matrix.target.arch }}
LLVM: 19.1.7
run: |
set -e
u="https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM"
curl -fsSL --retry 3 -o openmp.tar.xz "$u/openmp-$LLVM.src.tar.xz"
curl -fsSL --retry 3 -o llvm-cmake.tar.xz "$u/cmake-$LLVM.src.tar.xz"
echo "bd7e6901ab086fd268750363017935fd4a717c153dad3c2aab86cb0140d9e3fe openmp.tar.xz" | shasum -a 256 -c -
echo "11c5a28f90053b0c43d0dec3d0ad579347fc277199c005206b963c19aae514e3 llvm-cmake.tar.xz" | shasum -a 256 -c -
mkdir -p omp-src && tar -xf openmp.tar.xz -C omp-src && tar -xf llvm-cmake.tar.xz -C omp-src && mv "omp-src/cmake-$LLVM.src" omp-src/cmake
cmake -S "omp-src/openmp-$LLVM.src" -B omp-build -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="$ARCH" \
-DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 -DLIBOMP_ENABLE_SHARED=OFF -DOPENMP_ENABLE_LIBOMPTARGET=OFF \
-DOPENMP_ENABLE_OMPT_TOOLS=OFF -DLIBOMP_OMPT_SUPPORT=OFF -DLIBOMP_USE_HWLOC=OFF -DOPENMP_STANDALONE_BUILD=ON \
-DCMAKE_INSTALL_PREFIX="$PWD/omp" > omp-cmake.log
cmake --build omp-build -j 4 > omp-build.log && cmake --install omp-build > /dev/null
lipo -info omp/lib/libomp.a | grep -q "architecture: $ARCH"
echo "OMP=$PWD/omp" >> "$GITHUB_ENV"
- name: Compile every language (macOS ${{ matrix.target.arch }})
env:
ARCH: ${{ matrix.target.arch }}
run: |
set -e
for lang in $LANGUAGES; do
if cmp -s "gen/$lang.id" "engines/$lang/ENGINE_ID"; then echo "$lang: cached, rules unchanged"; continue; fi
mkdir -p "engines/$lang"
# -Xclang -fopenmp lowers the pragmas without the driver adding -lomp; libomp.a is named by path
c++ -std=c++17 -O3 -w -arch "$ARCH" -mmacosx-version-min=12.0 -Xclang -fopenmp -I "$OMP/include" -I gen \
"gen/$lang.cpp" "$OMP/lib/libomp.a" -o "engines/$lang/axiomcode-engine-$lang"
touch "engines/$lang/axiomcode-engine-$lang.parallel"
cp "gen/$lang.id" "engines/$lang/ENGINE_ID"
done
mkdir -p engines/queries
for cpp in gen/queries/*.cpp; do
q="$(basename "$cpp" .cpp)"
if cmp -s "gen/queries/$q.id" "engines/queries/$q.id"; then echo "query $q: cached, rules unchanged"; continue; fi
c++ -std=c++17 -O3 -w -arch "$ARCH" -mmacosx-version-min=12.0 -I gen "$cpp" -o "engines/queries/axiomcode-query-$q"
cp "gen/queries/$q.id" "engines/queries/$q.id"
done
file engines/java/axiomcode-engine-java
otool -L engines/java/axiomcode-engine-java
# A RUNTIME A CLEAN MAC HAS: only what lives under /usr/lib and /System (libc++, libSystem). A libomp.dylib, or
# anything under /opt/homebrew or /usr/local, starts here and fails on a user's Mac. A parallel engine must also
# hold the OpenMP runtime (a pragma that was never lowered leaves a silently serial binary).
- name: macOS binaries load nothing a stock system lacks
run: |
set -e
bad=0
for f in engines/*/axiomcode-*; do
case "$f" in *.parallel) continue;; esac
extra="$(otool -L "$f" | tail -n +2 | awk '{print $1}' | grep -vE '^(/usr/lib/|/System/)' || true)"
if [ -n "$extra" ]; then echo "::error::$f loads $(echo $extra) — not on a stock Mac"; bad=1; fi
minos="$(otool -l "$f" | awk '/LC_BUILD_VERSION/{b=1} b&&/minos/{print $2; exit}')"
if [ "$minos" != 12.0 ]; then echo "::error::$f needs macOS $minos (> 12.0)"; bad=1; fi
if [ -f "$f.parallel" ] && ! nm "$f" | grep -q ' T ___kmpc_fork_call$'; then echo "::error::$f carries a .parallel marker but holds no OpenMP runtime"; bad=1; fi
done
exit $bad
- name: Smoke — every binary starts on empty inputs
run: |
set -e
for lang in $LANGUAGES; do
mkdir -p "facts-$lang" "out-$lang"
sed -n 's/^\.input \([A-Za-z0-9_]*\)(.*/\1/p' "gen/$lang.dl" | while read -r r; do : > "facts-$lang/$r.facts"; done
"./engines/$lang/axiomcode-engine-$lang" -F "facts-$lang" -D "out-$lang"
done
bash gen/queries/smoke.sh check gen/queries engines/queries
- name: save the engines for the next run
if: ${{ !inputs.fresh && steps.restore.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: engines
key: engines-${{ matrix.target.platform }}-${{ needs.generate.outputs.flags }}-${{ needs.generate.outputs.key }}
- uses: actions/upload-artifact@v4
with: { name: 'engines-${{ matrix.target.platform }}', path: engines, retention-days: 90, if-no-files-found: error }