-
Notifications
You must be signed in to change notification settings - Fork 36
1417 lines (1330 loc) · 72.8 KB
/
Copy pathci.yml
File metadata and controls
1417 lines (1330 loc) · 72.8 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
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
name: CI
# Phase 02.2 — early 3-OS build + render-smoke regression net.
# Triggers (D-10): push + pull_request on the milestone branches ONLY — not all
# branches (keeps CI noise down). `master` is the eventual merge target;
# `v1.6-modernization` is the active milestone branch.
on:
push:
branches: [master, v1.6-modernization, v1.7-modernization, v1.7.1-modernization]
# Docs-only commits don't change build inputs — skip the full 4-OS rebuild
# (saves ~45-60 min of runner time per ROADMAP/CLAUDE.md push). Source-of-
# truth for "what affects the build" is anything outside .planning/ and
# top-level .md files. The list is paths-IGNORE (deny-list) rather than
# paths (allow-list) so any new code dir is in-scope by default — fail-safe
# toward retriggering, not toward skipping.
paths-ignore:
- '.planning/**'
- '**.md'
- 'LICENSE*'
- 'doc/**'
pull_request:
branches: [master, v1.6-modernization, v1.7-modernization, v1.7.1-modernization]
paths-ignore:
- '.planning/**'
- '**.md'
- 'LICENSE*'
- 'doc/**'
# CI-HARDEN-W1 (Stage 2.5 — v1.7-PLAN.md): weekly cross-platform run
# catches Windows/Linux surprises BEFORE Stage 3 closure-gate promotion.
# Cheaper than full-matrix-always; eliminates the v1.6.2-style closure
# crunch by warming Windows + ARM Linux every Monday morning UTC.
schedule:
- cron: '0 6 * * 1'
# Manual trigger — any maintainer can force a full-matrix run before
# high-risk phase work, e.g. before tagging an RC. Choose extra-matrix
# axes via input if needed in the future.
workflow_dispatch:
# Read-only token: this workflow only builds + checks, it never writes back.
permissions:
contents: read
# One in-flight run per ref — a newer push cancels the older run so pushes
# don't pile up on the hosted runners.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# --------------------------------------------------------------------------
# detect-platforms — CI-HARDEN-W1 (v1.7-PLAN Stage 2.5).
#
# Computes which OS matrix entries to dispatch based on the trigger:
# - Stage 1 default (push/PR on v1.7-modernization): macOS only
# - Schedule (weekly Monday 06:00 UTC): full matrix (macOS + Linux + Windows)
# - workflow_dispatch (manual): full matrix
# - push/PR touching high-risk paths (cmake/, ci.yml, vcpkg.json, CIFParser,
# atom.h, VIEW/, CMakeLists.txt): full matrix
# - master, v1.6-modernization branches: full matrix (legacy + maintenance)
#
# The high-risk paths list is the empirical surface where cross-platform
# regressions historically hide:
# - cmake/**, CMakeLists.txt, CMakePresets.json build system
# - .github/workflows/** CI workflow itself
# - vcpkg.json Windows dep graph
# - source/FORMAT/CIFParser* Bison parser (gcc -Wformat-truncation)
# - include/BALL/KERNEL/atom.h MSVC C4910 pointer-vector
# - include/BALL/VIEW/**, source/VIEW/** Qt 6 / GL surface
#
# The dynamic matrix outputs JSON consumed by the `build` job below via
# `fromJSON()`. This is the cleanest GHA pattern for trigger-aware matrix
# composition; matrix-entry-level `if:` is not supported by the runner.
# --------------------------------------------------------------------------
detect-platforms:
name: detect-platforms (CI-HARDEN-W1)
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.set.outputs.matrix }}
cross_platform: ${{ steps.set.outputs.cross_platform }}
steps:
# fetch-depth: 0 needed so the path-change diff (next step) can
# reach the base SHA. dorny/paths-filter@v3 hit "fatal: shallow
# file has changed since we read it" on the first CI-HARDEN-W1 run
# (commit 9750fdd) because the default shallow clone (depth 1)
# can't reach the push event's `before` SHA. Full clone avoids the
# paths-filter action's internal git-deepen race entirely.
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: set
shell: bash
run: |
# Decide whether to dispatch the full matrix. Replaces the
# earlier dorny/paths-filter@v3 step with explicit git-diff
# logic for robustness + zero third-party action surface.
#
# Phase 999.48 §8.7 step (b) — ui_v2 axis REMOVED. The
# BALL_UI_V2 flag no longer exists; doubling the matrix on the
# flag value is dead weight. The remaining axes (macOS-only
# vs. cross-platform) preserve the Stage 1 / full-matrix
# dispatch logic unchanged.
cross_platform="false"
changed_paths_match="false"
# Path-aware detection is meaningful only for push + pull_request.
# schedule, workflow_dispatch, and maintenance branches force full
# matrix unconditionally below.
if [[ "${{ github.event_name }}" == "push" ]] || \
[[ "${{ github.event_name }}" == "pull_request" ]]; then
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_SHA="${{ github.event.pull_request.base.sha }}"
else
BASE_SHA="${{ github.event.before }}"
fi
# On first-push-to-new-branch the before SHA is all-zeros;
# in that case there's no diff to compute → force full matrix.
if [[ "$BASE_SHA" == "0000000000000000000000000000000000000000" ]]; then
echo "::notice::First push to new branch — forcing cross_platform=true"
changed_paths_match="true"
elif ! git rev-parse --verify --quiet "$BASE_SHA" >/dev/null 2>&1; then
echo "::warning::Base SHA $BASE_SHA not reachable; defaulting to cross_platform=true (safe fallback)"
changed_paths_match="true"
else
CHANGED=$(git diff --name-only "$BASE_SHA" HEAD)
echo "Changed files in this push:"
echo "$CHANGED" | sed 's/^/ /'
# Match against the high-risk path patterns (regex, anchored
# at start). Any match → cross_platform=true.
if echo "$CHANGED" | grep -qE '^(cmake/|CMakeLists\.txt|CMakePresets\.json|\.github/workflows/|vcpkg\.json|source/FORMAT/CIFParser|include/BALL/KERNEL/atom\.h|include/BALL/VIEW/|source/VIEW/)'; then
changed_paths_match="true"
fi
fi
fi
if [[ "${{ github.event_name }}" == "schedule" ]] || \
[[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \
[[ "${{ github.ref_name }}" == "master" ]] || \
[[ "${{ github.ref_name }}" == "v1.6-modernization" ]] || \
[[ "$changed_paths_match" == "true" ]]; then
cross_platform="true"
fi
echo "cross_platform=${cross_platform}" >> "$GITHUB_OUTPUT"
# The macOS entry is ALWAYS included (Stage 1 default).
# The Linux + Windows entries are added when cross_platform=true.
MAC_ENTRY='{
"os": "macos-arm64",
"runner": "macos-14",
"blocking": true,
"smoke": true,
"ccache_dir": "~/Library/Caches/ccache"
}'
LINUX_X64_ENTRY='{
"os": "linux-x64",
"runner": "ubuntu-24.04",
"blocking": true,
"smoke": true,
"ccache_dir": "~/.cache/ccache",
"qt_host": "linux",
"qt_version": "6.8.*"
}'
LINUX_ARM64_ENTRY='{
"os": "linux-arm64",
"runner": "ubuntu-24.04-arm",
"blocking": true,
"smoke": true,
"ccache_dir": "~/.cache/ccache",
"qt_host": "linux_arm64",
"qt_version": "6.8.*"
}'
WINDOWS_ENTRY='{
"os": "windows-x64",
"runner": "windows-2025",
"blocking": true,
"smoke": false,
"ccache_dir": "C:\\Users\\runneradmin\\AppData\\Local\\ccache",
"qt_host": "windows",
"qt_version": "6.8.*"
}'
if [[ "${cross_platform}" == "true" ]]; then
INCLUDE="[${MAC_ENTRY},${LINUX_X64_ENTRY},${LINUX_ARM64_ENTRY},${WINDOWS_ENTRY}]"
else
INCLUDE="[${MAC_ENTRY}]"
fi
# Compact + strip newlines so the JSON fits on one line for $GITHUB_OUTPUT
INCLUDE_ONELINE=$(echo "$INCLUDE" | jq -c '.' | tr -d '\n')
echo "matrix={\"include\":${INCLUDE_ONELINE}}" >> "$GITHUB_OUTPUT"
echo "::notice::cross_platform=${cross_platform}; matrix dispatch:"
echo "$INCLUDE_ONELINE" | jq -r '.[] | " - " + .os' || echo "$INCLUDE_ONELINE"
# --------------------------------------------------------------------------
# build — the dynamic-matrix Release build (D-07, D-11).
#
# Matrix shape now driven by detect-platforms above. macOS always runs
# (Stage 1 default); Linux + Windows run on schedule, workflow_dispatch,
# high-risk-path push/PR, or maintenance branches.
# --------------------------------------------------------------------------
build:
needs: detect-platforms
name: build (${{ matrix.os }})
runs-on: ${{ matrix.runner }}
# D-03: continue-on-error driven off matrix.blocking. macOS/Linux/Windows
# are all "blocking: true" in the JSON above; the field is preserved so
# future axes (e.g. spike backends) can flip without rewriting downstream.
continue-on-error: ${{ !matrix.blocking }}
# Phase 999.16 (BUILD-ACCEL-01): ccache + PCH compatibility requires these
# sloppiness keys. Without them, PCH-enabled builds silently bust ccache hits.
# See cmake/PCH.md §"ccache sloppiness contract" for per-flag rationale.
#
# v1.7.0-rc2 fix (2026-05-18): include the generated config.h in
# CCACHE_EXTRAFILES so PROJECT VERSION bumps (and any other CMake-
# configured macros) properly invalidate cached compilations. Without
# this the VersionInfo_test fails after a version bump because libBALL
# is rebuilt from cache against the OLD BALL_RELEASE_STRING while the
# test TU compiles fresh against the NEW one. Diagnosed from CI run
# 26016813212 linux-x64 build (test #6/295 VersionInfo_test FAILED).
env:
CCACHE_SLOPPINESS: "pch_defines,time_macros,include_file_mtime"
CCACHE_EXTRAFILES: "${{ github.workspace }}/include/BALL/CONFIG/config.h.in"
strategy:
# Don't let one OS failing cancel the others — we want the full picture.
fail-fast: false
matrix: ${{ fromJSON(needs.detect-platforms.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v5
# ----------------------------------------------------------------------
# ccache (D-06, D-11) — warm the compiler cache across runs. The configure
# steps below wire CMAKE_<LANG>_COMPILER_LAUNCHER=ccache.
# ----------------------------------------------------------------------
- name: Cache ccache
uses: actions/cache@v5
with:
path: ${{ matrix.ccache_dir }}
# Phase 999.48 §8.7 step (b) — ui_v2 suffix removed from the
# cache key with the BALL_UI_V2 flag's removal. Single-cell
# per OS now reuses the same line every build.
# v1.7.0-rc2 (2026-05-18) — bump cache key prefix v1 → v2 to
# invalidate 1.6.0-era cached object files that survived the
# PROJECT VERSION 1.6.0 → 1.7.0 bump and caused
# VersionInfo_test to fail on Linux. Paired with the
# CCACHE_EXTRAFILES addition above for the long-term fix.
key: ccache-${{ matrix.os }}-pch-v2-${{ github.sha }}
restore-keys: |
ccache-${{ matrix.os }}-pch-v2-
ccache-${{ matrix.os }}-
# Phase 999.17 (BUILD-ACCEL-02): cache the Windows CMake build tree so warm-cache
# Configure step skips the 2m32s vcpkg+CMake-configure rebuild when nothing
# structural changed (CMakeLists.txt / cmake/** / vcpkg.json / CMakePresets.json).
# Cache scope is the configure-output set ONLY — object files come from ccache,
# not from this cache. Windows-only; macOS+Linux configure in <30s already.
# ci.yml itself is NOT in hashFiles() — editing this comment triggers a warm run
# that exercises the cache-HIT path without invalidating the cmake-tree key.
- name: Cache Windows vcpkg deps
if: matrix.os == 'windows-x64'
uses: actions/cache@v5
with:
# Cache ONLY vcpkg_installed/ — the expensive Boost/FFTW/etc. build.
# Do NOT cache CMakeCache.txt / CMakeFiles/ / build.ninja: those embed
# the absolute MSVC compiler path (e.g. .../MSVC/14.44.35207/.../cl.exe),
# while the key below hashes only source files, not the runner image.
# When GitHub bumps the windows runner image (the 2026-06-15 windows-2025
# → vs2026 migration bumped MSVC past 14.44.35207), the pinned compiler
# path disappears and a restored CMakeCache makes PROJECT() fail with
# "not a full path to an existing compiler tool" (see scheduled run
# 27544121035). Letting CMake re-detect the compiler each run — cheap
# once vcpkg_installed is warm; objects still come from the separate
# ccache — eliminates that whole failure class. (Supersedes the deferred
# Phase 999.36 per-source-hash invalidation idea, which this makes moot.)
# v3: drops the compiler-pinned paths from v2 and invalidates poisoned
# entries left by the runner-image bump.
path: |
build/ci-windows/vcpkg_installed/
key: ci-windows-vcpkg-v3-${{ hashFiles('vcpkg.json', 'CMakePresets.json') }}
restore-keys: |
ci-windows-vcpkg-v3-
# ======================================================================
# macOS — Homebrew deps + the verbatim BUILD-macos.md flow.
# ======================================================================
- name: Cache Homebrew downloads (macOS)
if: matrix.os == 'macos-arm64'
uses: actions/cache@v5
with:
path: ~/Library/Caches/Homebrew
key: brew-${{ matrix.os }}-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: |
brew-${{ matrix.os }}-
- name: Install dependencies (macOS / Homebrew)
if: matrix.os == 'macos-arm64'
run: |
# D-06: the verbatim BUILD-macos.md dependency line + ccache.
# Phase 5 (Plan 05-01 D-03): Homebrew unversioned `qt` is the Qt 6
# source (currently 6.11.x, well above the D-01 6.5 floor). The
# `macos-homebrew` CMake preset's CMAKE_PREFIX_PATH already points
# at /opt/homebrew/opt/qt (not qt@5) — see Plan 05-01 SUMMARY.
# Phase 999.2: `ninja` provisioned alongside ccache — the ci-macos
# preset now declares "generator": "Ninja" so `ninja` must be on PATH
# at configure time. Homebrew installs ninja as `/opt/homebrew/bin/ninja`
# which is already on the runner PATH.
brew install qt boost eigen fftw tbb glew open-babel lp_solve libsvm flex bison
brew install ccache ninja
- name: Configure (macOS)
if: matrix.os == 'macos-arm64'
run: |
# D-07: preset is the single source of truth — mirrors BUILD-macos.md.
# bison/flex executable paths are embedded in the ci-macos preset.
# -DBALL_LICENSE=GPL: OpenBabel is GPL-gated (maintainer decision, Plan 04-01).
# BALL_HAS_OPENBABEL=ON is already in the ci-macos preset's cacheVariables
# (Plan 04-02) — the -DBALL_LICENSE=GPL flag is what actually enables the
# GPL-block FIND_PACKAGE(OpenBabel3) call in CMakeLists.txt (Plan 04-03).
# Phase 999.48 §8.7 step (b) — -DBALL_UI_V2 axis flag dropped here
# with the BALL_UI_V2 CMake option removal.
cmake --preset ci-macos -DBALL_LICENSE=GPL
- name: Build (macOS)
if: matrix.os == 'macos-arm64'
run: |
# D-07: the BUILD-macos.md target set, Release.
# Phase 999.2: zero ccache stats before the Build, dump them after,
# so the per-run cache-hit ratio is visible in the workflow log
# (the warm-vs-cold delta is the key metric for the Ninja switch).
ccache --zero-stats
cmake --build --preset ci-macos --target BALL VIEW BALLView -j$(sysctl -n hw.ncpu)
ccache --show-stats --verbose
# ----------------------------------------------------------------------
# Phase 999.19 (BUILD-ACCEL-04): per-TU build profiling instrumentation.
# Runs after the Build step (even on Build failure) so the artifact is
# available for diagnosing slow-then-failed runs. Uses bash shell on
# macOS/Linux, pwsh on Windows. Artifact retention: 90 days.
# ----------------------------------------------------------------------
- name: Top-20 slowest TUs (${{ matrix.os }})
if: (success() || failure()) && matrix.os == 'macos-arm64'
shell: bash
run: |
# Disable pipefail for this block — `sort -rn | head -20` causes
# `sort` to receive SIGPIPE when `head` exits after 20 lines, and
# under GH Actions bash's default pipefail this becomes exit code
# 2 which fails the step (and the whole job, now that ARM is
# blocking). The telemetry itself is purely informational.
set +o pipefail
if [ -f build/ci-macos/.ninja_log ]; then
echo "=== Top-20 slowest TUs (${{ matrix.os }}) ==="
# ninja log v7: start_ms TAB end_ms TAB mtime TAB output_path TAB cmd_hash
# Filter to CMakeFiles/ entries (source files only) to exclude
# generated files (autogen/mocs_compilation.cpp etc).
awk -F'\t' 'NF>=5 && $4 ~ /CMakeFiles\// {printf "%8d ms %s\n", $2-$1, $4}' build/ci-macos/.ninja_log \
| sort -rn | head -20
else
echo "WARN: .ninja_log not found at build/ci-macos/.ninja_log"
fi
- name: Upload .ninja_log artifact (${{ matrix.os }})
if: (success() || failure()) && matrix.os == 'macos-arm64'
uses: actions/upload-artifact@v6
with:
# ui_v2 in artifact name: doubled matrix would collide on the
# shared `ninja-log-<os>-<sha>` key otherwise (actions/upload-
# artifact@v4+ rejects duplicates).
name: ninja-log-${{ matrix.os }}-${{ github.sha }}
path: build/ci-macos/.ninja_log
retention-days: 90
include-hidden-files: true
# ----------------------------------------------------------------------
# Phase 9 gatekeeper (BLOCKING — TEST-CLOSE-02) — the BALL unit-test
# tree (`test/`, 295 files) is now a load-bearing CI gate on macOS +
# Linux. Three baseline failures triaged in 09-TRIAGE.md (Phase 9,
# 2026-05-16): Directory_test KNOWN-PASSING; AmberFF_test FIX (ARM FP
# tolerance); AssignBondOrderProcessor_test2 QUARANTINE (WILL_FAIL TRUE).
# Windows test gatekeeper landed in Phase 999.34 (see Windows block below).
# ----------------------------------------------------------------------
# TEST-CLOSE-02 (Phase 9, 2026-05-16): gatekeeper flipped to blocking
# after 3 baseline failures triaged in 09-TRIAGE.md.
- name: Build BALL test suite (macOS — Phase 9 gatekeeper, blocking)
if: matrix.os == 'macos-arm64'
continue-on-error: false
timeout-minutes: 45
run: |
cmake --build build/ci-macos --target build_tests -j$(sysctl -n hw.ncpu)
# TEST-CLOSE-02 (Phase 9, 2026-05-16): gatekeeper flipped to blocking
# after 3 baseline failures triaged in 09-TRIAGE.md.
- name: Run BALL test suite (macOS — Phase 9 gatekeeper, blocking)
if: matrix.os == 'macos-arm64'
continue-on-error: false
timeout-minutes: 60
run: |
cd build/ci-macos
# BALL_DATA_PATH MUST be set so tests find FragmentDB, force-field
# parameter files, rotamer libraries, etc. v1.6.0 local baseline:
# 50/294 fail without it (= "data path unset" noise), 3/294 fail
# with it (= actual triage candidates: Directory_test, AmberFF_test,
# AssignBondOrderProcessor_test2 — Phase 9 main work).
# --timeout 120 caps individual hangs (most BALL tests run in <10s;
# ScoringFunction_test + AssignBondOrderProcessor_test{1,2} have
# 2h timeouts set in test/CMakeLists.txt and will hit this cap —
# acceptable for the gatekeeper, Phase 9 decides which to skip).
export BALL_DATA_PATH="${{ github.workspace }}/data"
# TEST-CLOSE-02: || true removed — this step is now blocking.
# ctest exits non-zero if any non-WILL_FAIL test fails.
# ui_v2 in the JUnit filename so ON + OFF cells don't clobber each
# other's output (each cell runs on its own runner so the file
# collision would only manifest at the test-report job which
# `merge-multiple`s these — same-name files would race).
ctest --output-on-failure --timeout 120 \
--output-junit ${{ github.workspace }}/ball-tests-macos.xml
- name: Upload test results (macOS)
if: always() && matrix.os == 'macos-arm64'
uses: actions/upload-artifact@v6
with:
name: ball-tests-macos-${{ github.run_id }}
path: ball-tests-macos.xml
if-no-files-found: warn
retention-days: 30
# ======================================================================
# Linux — apt deps + the BUILD-macos.md flow adapted to Linux paths.
# ======================================================================
- name: Cache apt archives (Linux)
if: startsWith(matrix.os, 'linux-')
uses: actions/cache@v5
with:
# Phase 5.1 Plan 12 (Task D3): scope the cached path to `*.deb` only.
# Caching the full `/var/cache/apt/archives` directory was triggering a
# post-job warning on every Linux run:
# /usr/bin/tar: ../../../../../var/cache/apt/archives/lock: Cannot open: Permission denied
# /usr/bin/tar: ../../../../../var/cache/apt/archives/partial: Cannot open: Permission denied
# ##[warning]Failed to save: "/usr/bin/tar" failed ... exit code 2
# The runner is unprivileged; APT's exclusive lock file and partial/
# subdirectory are root-only (0640 / 0700), so tar fails on them. The
# cache action's fallback re-tars without the unreadable entries and
# the cache *is* still saved ("Cache saved with key: ..." follows the
# warning), so this was definitionally cosmetic — but the warning
# surfaced on 7/7 recent successful runs (chronic recurrence, not
# transient). Narrowing the cached path to the .deb archives — which
# is the only content `apt-get install` actually downloads and the
# only content the cache restore needs — silences the warning by
# construction and avoids the slower fallback tar pass.
path: /var/cache/apt/archives/*.deb
key: apt-${{ matrix.os }}-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: |
apt-${{ matrix.os }}-
- name: Install dependencies (Linux / apt)
if: startsWith(matrix.os, 'linux-')
run: |
# D-06: apt equivalents of the Homebrew set, + build tooling + ccache,
# + xvfb/software-Mesa for the headless render smoke check (D-08).
#
# Phase 5 (Plan 05-04): the Qt5 -dev packages (qtbase5-dev /
# qtbase5-dev-tools / libqt5opengl5-dev / qttools5-dev) are REMOVED.
# Ubuntu 24.04 apt ships Qt 6.4.2 which is below the D-01 6.5 floor;
# Qt is installed through jurplel/install-qt-action in the dedicated
# step below (aqtinstall, pinned to Qt 6.8.*). xvfb + Mesa stay for
# the headless smoke check; non-Qt deps unchanged.
# Phase 999.2: `ninja-build` provisioned for the Ninja generator
# switch — the ci-linux preset now declares "generator": "Ninja".
# `ninja-build` is in the Ubuntu main archive; apt no-ops cleanly
# if already present in the runner image. `make` retained for any
# ad-hoc sub-invocation; harmless under Ninja.
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
cmake make ninja-build g++ ccache \
libboost-all-dev libeigen3-dev libfftw3-dev libtbb-dev libglew-dev \
libopenbabel-dev libsvm-dev flex bison \
xvfb mesa-utils libgl1-mesa-dri
# NOTE: liblpsolve55-dev is intentionally NOT installed. Ubuntu ships
# liblpsolve55.a built WITHOUT -fPIC, which cannot be linked into
# BALL's shared libBALL (ld: "relocation R_X86_64_PC32 ... can not be
# used when making a shared object"). lp_solve is an OPTIONAL feature
# (see the Feature Matrix in REQUIREMENTS.md) — Linux CI builds without
# it; macOS keeps it (Homebrew's lp_solve is fine). Phase 4 (FEAT-01)
# confirms the per-platform optional-dependency policy.
- name: Install Qt 6 (Linux — jurplel/install-qt-action / aqtinstall)
if: startsWith(matrix.os, 'linux-')
# D-01 + D-02 (Plan 05-04): Ubuntu 24.04 apt ships Qt 6.4.2, below the
# D-01 6.8 LTS floor (revised 2026-05-16, was 6.5). jurplel/install-qt-action wraps aqtinstall and
# downloads the official Qt build from download.qt.io into the runner
# workspace; the install location is exposed as Qt6_DIR so CMake's
# FIND_PACKAGE(Qt6 6.5 ...) resolves cleanly.
#
# `modules:` is intentionally omitted. aqt v3.3.0 on Qt 6.8 LTS rejects
# qtbase, qtopengl, AND qttools as module names — they are all part of
# the default Qt 6 install on Linux, not separately-installable add-on
# modules. BALL's actual Qt 6 requirements (Core, Network, Xml, OpenGL,
# PrintSupport, Test, Widgets, OpenGLWidgets, LinguistTools) are all
# in the default install, so no `modules:` entry is needed.
# Phase 5.1 carry-forward: this finding came from CI runs 25925481471
# and 25934288544 where aqt rejected each module list in turn.
# D-05: NO qt5compat — clean break.
uses: jurplel/install-qt-action@v4
with:
# matrix.qt_version = '6.8.*' on both x86_64 and ARM (post-2026-05-16
# D-01 floor revision). matrix.qt_host = 'linux' / 'linux_arm64'.
# Both pull official Qt 6.8+ desktop binaries from download.qt.io
# — Qt 6.8 is the first version with Linux ARM64 desktop binaries.
version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }}
target: 'desktop'
cache: true
- name: Configure (Linux)
if: startsWith(matrix.os, 'linux-')
run: |
# D-07: preset is the single source of truth for Linux paths.
# bison/flex at /usr/bin, USE_LPSOLVE=OFF (non-PIC liblpsolve55.a),
# ccache, and BALL_HAS_OPENBABEL=ON are all set by ci-linux preset.
# -DBALL_LICENSE=GPL: OpenBabel is GPL-gated (maintainer decision, Plan 04-01).
#: see macOS Configure step + the
# detect-platforms job comment for axis rationale + drop conditions.
cmake --preset ci-linux -DBALL_LICENSE=GPL
- name: Build (Linux)
if: startsWith(matrix.os, 'linux-')
run: |
# D-07: the BUILD-macos.md target set, Release.
# Phase 999.2: zero ccache stats before / dump after — see the
# matching block in "Build (macOS)" for rationale.
ccache --zero-stats
cmake --build --preset ci-linux --target BALL VIEW BALLView -j$(nproc)
ccache --show-stats --verbose
# Phase 999.19 (BUILD-ACCEL-04): per-TU build profiling (Linux).
- name: Top-20 slowest TUs (${{ matrix.os }})
if: (success() || failure()) && startsWith(matrix.os, 'linux-')
shell: bash
run: |
# Disable pipefail — see macOS sibling step for rationale.
# `sort | head` SIGPIPE under default pipefail = step fails =
# blocking job fails on purely informational telemetry.
set +o pipefail
if [ -f build/ci-linux/.ninja_log ]; then
echo "=== Top-20 slowest TUs (${{ matrix.os }}) ==="
# ninja log v7: field 4 = output_path, field 5 = cmd_hash.
awk -F'\t' 'NF>=5 && $4 ~ /CMakeFiles\// {printf "%8d ms %s\n", $2-$1, $4}' build/ci-linux/.ninja_log \
| sort -rn | head -20
else
echo "WARN: .ninja_log not found at build/ci-linux/.ninja_log"
fi
- name: Upload .ninja_log artifact (${{ matrix.os }})
if: (success() || failure()) && startsWith(matrix.os, 'linux-')
uses: actions/upload-artifact@v6
with:
# ui_v2 in artifact name — see macOS sibling rationale.
name: ninja-log-${{ matrix.os }}-${{ github.sha }}
path: build/ci-linux/.ninja_log
retention-days: 90
include-hidden-files: true
# ----------------------------------------------------------------------
# Phase 9 gatekeeper (BLOCKING — TEST-CLOSE-02) — see macOS counterpart.
# Windows test gatekeeper landed in Phase 999.34 (see Windows block below).
# ----------------------------------------------------------------------
# TEST-CLOSE-02 (Phase 9, 2026-05-16): gatekeeper flipped to blocking
# after 3 baseline failures triaged in 09-TRIAGE.md.
- name: Build BALL test suite (Linux — Phase 9 gatekeeper, blocking)
if: startsWith(matrix.os, 'linux-')
continue-on-error: false
timeout-minutes: 45
run: |
cmake --build build/ci-linux --target build_tests -j$(nproc)
# TEST-CLOSE-02 (Phase 9, 2026-05-16): gatekeeper flipped to blocking
# after 3 baseline failures triaged in 09-TRIAGE.md.
- name: Run BALL test suite (Linux — Phase 9 gatekeeper, blocking)
if: startsWith(matrix.os, 'linux-')
continue-on-error: false
timeout-minutes: 60
run: |
cd build/ci-linux
# See macOS counterpart — BALL_DATA_PATH is the make-or-break env var.
export BALL_DATA_PATH="${{ github.workspace }}/data"
# TEST-CLOSE-02: || true removed — this step is now blocking.
# ui_v2 in JUnit filename — see macOS sibling.
ctest --output-on-failure --timeout 120 \
--output-junit ${{ github.workspace }}/ball-tests-linux.xml
- name: Upload test results (Linux)
if: always() && startsWith(matrix.os, 'linux-')
uses: actions/upload-artifact@v6
with:
name: ball-tests-linux-${{ github.run_id }}
path: ball-tests-linux.xml
if-no-files-found: warn
retention-days: 30
# ----------------------------------------------------------------------
# Phase 999.60 (v1.7.x-25) — contract-tests gate (BLOCKING, Linux-only).
#
# The cross-surface contract-test harness + compile-fail infra
# (.planning/v1.7.4-ARCHITECTURE-CONTRACT.md §11; test/contract/).
# Runs as an ISOLATED, named, blocking gate via `ctest -L contract` so
# a future dual-path bypass fails CI on its own line — distinct from the
# general test-suite gate above. Reuses this job's ccache / Qt / preset
# lineage (the plan's "same ccache lineage as the existing test job").
#
# Linux-only at v1.7.4 (matches tabOrder_test discipline — headless
# BALLView is brittle on macOS; macOS/Windows coverage is v1.7.5).
#
# HARNESS-FIRST: the per-controller parity / bool-apply / re-entrancy
# fixtures ship DISABLED and report STATUS-skips today; they flip green
# as 999.59 cuts each controller over. The harness MUST compile + run.
# ----------------------------------------------------------------------
- name: Build contract tests (Linux — 999.60 gate, blocking)
if: startsWith(matrix.os, 'linux-')
continue-on-error: false
timeout-minutes: 30
run: |
cmake --build build/ci-linux --target contract_tests -j$(nproc)
- name: Run contract tests (Linux — 999.60 gate, blocking)
if: startsWith(matrix.os, 'linux-')
continue-on-error: false
timeout-minutes: 15
run: |
cd build/ci-linux
# BALL_DATA_PATH lets the selection-consumer fixture load 1BNA.pdb;
# fixtures that need the sample skip cleanly if it is absent.
export BALL_DATA_PATH="${{ github.workspace }}/data"
ctest -L contract --output-on-failure --timeout 120 \
--output-junit ${{ github.workspace }}/contract-tests-linux.xml
- name: Upload contract-test results (Linux)
if: always() && startsWith(matrix.os, 'linux-')
uses: actions/upload-artifact@v6
with:
name: contract-tests-linux-${{ github.run_id }}
path: contract-tests-linux.xml
if-no-files-found: warn
retention-days: 30
# ======================================================================
# Windows — real vcpkg + choco provisioning (Phase 4 / Plan 04-04).
#
# Provisioning strategy:
# 1. vcpkg: GitHub windows-2022 runners ship vcpkg at C:\vcpkg (exposed
# as VCPKG_INSTALLATION_ROOT). The windows-vcpkg preset toolchainFile
# picks it up via $env{VCPKG_ROOT}, bridged in the Configure step.
# Build speed (T-04-17 mitigation):
# - vcpkg's default file binary cache (%LOCALAPPDATA%\vcpkg\archives)
# is cached via actions/cache. In MANIFEST mode vcpkg installs into
# <binaryDir>/vcpkg_installed — NOT C:\vcpkg\installed — so caching
# that path is useless; the binary archives dir is the reusable
# artifact. Split restore/save with `save` at `if: always()` so a
# failing Build step still persists the (expensive) port builds.
# (vcpkg's x-gha cache was tried and dropped: the current GitHub
# Actions cache service no longer exposes ACTIONS_CACHE_URL.)
# - the `x64-windows-release` overlay triplet (cmake/vcpkg-triplets)
# builds Release-only, skipping the debug half of every port —
# roughly halves a cold build.
# 2. flex/bison: not vcpkg ports; provisioned via winflexbison3 (choco).
# winflexbison3 installs win_flex.exe + win_bison.exe in
# C:\ProgramData\chocolatey\bin\. BALL's bison logic (CMakeLists.txt
# lines 113-124) looks for BALL-bison.bat first (not present in this
# repo) then falls through to FindBISON which searches the PATH/cache;
# we set -DBISON_EXECUTABLE and -DFLEX_EXECUTABLE explicitly so the
# IF(WIN32) FIND_PROGRAM doesn't shadow the cache values.
# ======================================================================
- name: Restore vcpkg binary cache (Windows)
id: vcpkg-cache
if: matrix.os == 'windows-x64'
uses: actions/cache/restore@v5
with:
path: C:\Users\runneradmin\AppData\Local\vcpkg\archives
key: vcpkg-${{ matrix.os }}-${{ hashFiles('vcpkg.json', 'cmake/vcpkg-triplets/x64-windows-release.cmake') }}
restore-keys: |
vcpkg-${{ matrix.os }}-
# Windows tooling install: flex/bison + ccache + ninja via a single
# composite action so each workflow has one call site for "the Windows
# deps." See .github/actions/setup-windows-deps/action.yml — ninja
# was folded in 2026-05-16 (Phase 999.2) so the ci-windows CMake
# preset can declare "generator": "Ninja" with no per-workflow drift.
- name: Set up Windows build deps (flex/bison + ccache + ninja)
if: matrix.os == 'windows-x64'
uses: ./.github/actions/setup-windows-deps
# Phase 999.2: with Ninja replacing MSBuild, `cl.exe` is invoked
# directly and needs the `vcvars64.bat` environment (PATH, INCLUDE,
# LIB, LIBPATH, etc.). MSBuild had this implicit via the VS project
# files; Ninja does not. `ilammy/msvc-dev-cmd@v1` is the canonical
# GitHub Actions wrapper around `vcvarsall.bat x64` — sets every env
# var for the remaining steps of THIS job (it modifies the runner's
# exported env, not just the current shell).
- name: Set up MSVC environment (Windows — vcvars64 for Ninja)
if: matrix.os == 'windows-x64'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
# Port of v1.6.2 release.yml commit 046271e: cold vcpkg of Qt 6 + Boost
# easily consumes 30+ GB of buildtrees and OOM-kills the runner's
# diag-log writer on the windows-2025 standard runner. Required for the
# FIRST cross-platform Windows CI run on v1.7-modernization (cache miss)
# which hit `error: building qtbase:x64-windows-release failed with:
# BUILD_FAILED` + `There is not enough space on the disk` in run
# 25984453099. Subsequent warm-cache runs skip vcpkg compile entirely
# and this cleanup is a no-op (~2s); kept always-on for resilience
# against actions/cache eviction.
- name: Free disk space before vcpkg (Windows)
if: matrix.os == 'windows-x64'
shell: pwsh
run: |
$before = (Get-PSDrive C).Free / 1GB
Write-Host ("Free space before cleanup: {0:N1} GB" -f $before)
$targets = @(
# Hosted toolcache languages (each typically 1-3 GB)
"C:\hostedtoolcache\windows\PyPy",
"C:\hostedtoolcache\windows\Ruby",
"C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk",
"C:\hostedtoolcache\windows\Java_Adopt_jdk",
"C:\hostedtoolcache\windows\go",
"C:\hostedtoolcache\windows\node",
# Old Python versions (keep 3.12+ in case vcpkg or cmake helper needs)
"C:\hostedtoolcache\windows\Python\3.7.*",
"C:\hostedtoolcache\windows\Python\3.8.*",
"C:\hostedtoolcache\windows\Python\3.9.*",
"C:\hostedtoolcache\windows\Python\3.10.*",
"C:\hostedtoolcache\windows\Python\3.11.*",
# Browser installs (cmake build doesn't use them)
"C:\Program Files\Google\Chrome",
"C:\Program Files\Mozilla Firefox",
"C:\Program Files (x86)\Google",
"C:\Program Files (x86)\Mozilla Firefox",
# Pre-installed DB servers (BALL build doesn't use them)
"C:\Program Files\PostgreSQL",
"C:\Program Files\MongoDB",
"C:\Program Files\MySQL",
"C:\Program Files\Microsoft SQL Server",
# Strawberry Perl (cmake's Perl finder picks chocolatey or Git Bash perl first)
"C:\Strawberry",
# JDK variants outside hostedtoolcache
"C:\Program Files\Java",
"C:\Program Files\Eclipse Adoptium",
"C:\Program Files\Microsoft\jdk*",
# Old VS variants if present (keep 2022, primary)
"C:\Program Files (x86)\Microsoft Visual Studio\2019"
)
foreach ($t in $targets) {
if (Test-Path $t) {
try {
Remove-Item -Recurse -Force $t -ErrorAction Stop
Write-Host (" removed: {0}" -f $t)
} catch {
Write-Host (" skip (in use): {0}" -f $t)
}
}
}
$after = (Get-PSDrive C).Free / 1GB
Write-Host ("Free space after cleanup: {0:N1} GB (gained {1:N1} GB)" -f $after, ($after - $before))
- name: Install Qt 6 (Windows — jurplel/install-qt-action / aqtinstall)
if: matrix.os == 'windows-x64'
# 999.66 — Windows Qt via aqtinstall. Mirrors the Linux step above:
# install the official prebuilt Qt 6.8 desktop kit (MSVC 2022 x64)
# instead of building qtbase/qtsvg/qttools from source via vcpkg.
# This eliminates the ~1.5-2h cold Qt-from-source build and the
# windeployqt-missing failure class. The action exposes Qt6_DIR +
# QT_ROOT_DIR so FIND_PACKAGE(Qt6) resolves (same as Linux).
# `modules:` intentionally omitted — BALL's Qt modules (Core/Network/
# Xml/OpenGL/PrintSupport/Test/Widgets/OpenGLWidgets/Svg/LinguistTools)
# are all in the default install; aqt 6.8 rejects qtbase/qtopengl/qttools
# as module names.
uses: jurplel/install-qt-action@v4
with:
version: ${{ matrix.qt_version }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2022_64'
cache: true
- name: Configure (Windows)
if: matrix.os == 'windows-x64'
shell: pwsh
run: |
# GitHub windows-2022 runners ship vcpkg at C:\vcpkg but expose it as
# VCPKG_INSTALLATION_ROOT, not VCPKG_ROOT. The windows-vcpkg preset's
# toolchainFile uses $env{VCPKG_ROOT}, so bridge the two here. (The
# runner OS env var is not visible via the ${{ env.* }} context, so
# it must be read inside the shell.)
$env:VCPKG_ROOT = $env:VCPKG_INSTALLATION_ROOT
Write-Host "VCPKG_ROOT=$env:VCPKG_ROOT"
# 999.66 — Windows Qt via aqtinstall. Qt is no longer in vcpkg.json,
# so the vcpkg toolchain no longer provides Qt6 config files. The aqt
# action exports QT_ROOT_DIR (+ Qt6_DIR); pass it as CMAKE_PREFIX_PATH
# so FIND_PACKAGE(Qt6) resolves the prebuilt kit (same effect as Linux,
# where Qt6_DIR alone suffices). vcpkg toolchain is still used for
# Boost/Eigen/FFTW/TBB/GLEW/etc.
Write-Host "QT_ROOT_DIR=$env:QT_ROOT_DIR"
# D-07: ci-windows preset inherits windows-vcpkg (vcpkg toolchainFile,
# VCPKG_TARGET_TRIPLET=x64-windows-release + overlay triplet,
# USE_LPSOLVE=OFF, BALL_HAS_OPENBABEL=OFF, ccache) and installs
# vcpkg.json deps at configure time.
# win_bison.exe / win_flex.exe from winflexbison3 are passed explicitly
# because FindBISON/FindFLEX search for 'bison'/'flex' by default.
#
# Phase 5 (Plan 05-04): USE_QTWEBENGINE=OFF on Windows — Plan 05-01
# carried the CMakeLists USE_QTWEBENGINE OPTION default `ON` for
# Phase 1+2+3+4 backward compatibility. The vcpkg manifest does NOT
# declare `qtwebengine` (it bloats cold-build by ~30min and Windows
# CI has no consumer for it), so the FIND_PACKAGE(Qt6 WebEngineCore)
# call would fail. Flipping the option OFF at configure time is the
# least-invasive fix; macOS Homebrew has qtwebengine so it stays ON
# there. See Plan 05-01 SUMMARY §"Open Items for Downstream Plans"
# for the carry-forward note.
# Pass --clean-after-build via CMake cache variable (not env var):
# vcpkg's manifest-mode cmake integration honors VCPKG_INSTALL_OPTIONS
# as a CMake cache variable; the env-var spelling is for the
# standalone `vcpkg install` CLI and gets silently ignored here.
# Deletes buildtrees + packages + downloads after each port finishes,
# bounding peak disk during cold installs to ~3 GB/port instead of
# the cumulative ~30+ GB. Required to fit on the windows-2025
# standard runner; ported from v1.6.2 release.yml commit 046271e.
# Phase 999.48 §8.7 step (b) — -DBALL_UI_V2=<matrix.ui_v2>
# dropped here along with the axis removal.
cmake --preset ci-windows `
-DCMAKE_PREFIX_PATH="$env:QT_ROOT_DIR" `
-DUSE_QTWEBENGINE=OFF `
-DBISON_EXECUTABLE="C:/ProgramData/chocolatey/bin/win_bison.exe" `
-DFLEX_EXECUTABLE="C:/ProgramData/chocolatey/bin/win_flex.exe" `
"-DVCPKG_INSTALL_OPTIONS=--clean-after-build"
# Persist the vcpkg binary cache as soon as Configure finishes — runs
# even if a later Build step fails, so iterating on build errors does
# not re-pay the ~80min Qt5/Boost source build each time.
- name: Save vcpkg binary cache (Windows)
if: always() && matrix.os == 'windows-x64' && steps.vcpkg-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: C:\Users\runneradmin\AppData\Local\vcpkg\archives
key: vcpkg-${{ matrix.os }}-${{ hashFiles('vcpkg.json', 'cmake/vcpkg-triplets/x64-windows-release.cmake') }}
- name: Build (Windows)
if: matrix.os == 'windows-x64'
shell: pwsh
run: |
# Phase 999.2: zero ccache stats before / dump after — under Ninja,
# CMAKE_<LANG>_COMPILER_LAUNCHER=ccache (from the ci-windows preset)
# is finally honored, so the post-build stats are the load-bearing
# measurement for whether the switch is paying off.
#
# 2026-05-16: dropped `--parallel $env:NUMBER_OF_PROCESSORS` so Ninja
# uses its tuned default (`nproc + 2`) instead of capping at `nproc`.
# On the new windows-2022-xlarge runner that means more concurrent
# cl.exe processes per cycle. Vestigial flag from the MSBuild era
# (where `--parallel N` mapped to `/maxcpucount:N`); under Ninja the
# flag's cap was actively reducing concurrency vs the default.
& ccache --zero-stats
cmake --build --preset ci-windows --target BALL VIEW BALLView --config Release
& ccache --show-stats --verbose
# Phase 999.28 follow-up — release-pipeline cross-ref vcpkg cache seed.
# Why: release.yml runs on tag-push refs, whose GitHub Actions cache
# scope is disjoint from this v1.6-modernization branch's scope. CI's
# warm vcpkg binary cache is invisible to the tag-triggered release
# workflow → release always cold-builds Qt6 + Boost from source on
# Windows (~2h, OOM-prone). Workaround: publish CI's vcpkg/archives
# as a workflow ARTIFACT (cross-ref accessible via `gh run download`).
# release.yml's seed step downloads the most recent successful
# artifact and stages it under %LOCALAPPDATA%\vcpkg\archives before
# cmake configure; vcpkg then hits the local archives and skips the
# from-source compile.
#
# Upload on every successful Windows build (cache hit or miss):
# the archives directory contains the same content regardless of
# source — what matters is that the artifact exists for release.yml
# to pull. `overwrite: true` ensures each successful CI run refreshes
# the artifact in place; no stale 14-day-old artifacts.
# `if-no-files-found: warn` because on a partial-failure run the
# archives dir may be empty (and we don't want to fail the whole
# job just for the artifact upload).
- name: Upload vcpkg archives for release-pipeline reuse (Windows)
# Windows-only artifact (vcpkg toolchain cache). Phase 999.48 §8.7
# step (b) — the prior ui_v2=OFF cell guard is gone with the
# BALL_UI_V2 axis removal; there's only one cell per OS now.
if: success() && matrix.os == 'windows-x64'
uses: actions/upload-artifact@v6
with:
name: vcpkg-archives-windows
path: C:\Users\runneradmin\AppData\Local\vcpkg\archives
retention-days: 14
if-no-files-found: warn
compression-level: 0 # archives are already deflated .zips
overwrite: true
# Phase 999.19 (BUILD-ACCEL-04): per-TU build profiling (Windows).
- name: Top-20 slowest TUs (windows-x64)
if: (success() || failure()) && matrix.os == 'windows-x64'
shell: pwsh
run: |
$logPath = "build\ci-windows\.ninja_log"
if (Test-Path $logPath) {
Write-Host "=== Top-20 slowest TUs (windows-x64) ==="
# ninja log v7: field[3] = output_path, field[4] = cmd_hash (0-indexed).
# Filter to CMakeFiles\ entries (source compilations only).
Get-Content $logPath | ForEach-Object {
$f = $_ -split "`t"
if ($f.Length -ge 5 -and $f[3] -match 'CMakeFiles\\') {
[PSCustomObject]@{ ms = [int]$f[1] - [int]$f[0]; tu = $f[3] }
}
} | Sort-Object -Property ms -Descending | Select-Object -First 20 | Format-Table -AutoSize
} else {
Write-Host "WARN: .ninja_log not found at $logPath"
}
- name: Upload .ninja_log artifact (windows-x64)
if: (success() || failure()) && matrix.os == 'windows-x64'
uses: actions/upload-artifact@v6
with:
# ui_v2 in artifact name — see macOS sibling rationale.
name: ninja-log-windows-x64-${{ github.sha }}
path: build\ci-windows\.ninja_log
retention-days: 90
include-hidden-files: true
# ----------------------------------------------------------------------
# Phase 9 gatekeeper (BLOCKING — Phase 999.34, 2026-05-17) — Windows
# parity with the macOS + Linux gatekeepers above. Promoted from BACKLOG
# in Phase 999.34 (Windows test gatekeeper bring-up); see ROADMAP.md and
# .planning/phases/999.34-windows-test-gatekeeper/999.34-01-SUMMARY.md.
#
# Quarantine inheritance (test/CMakeLists.txt):
# - AssignBondOrderProcessor_test2 — Apple-arm64-only WILL_FAIL (FP
# fine-penalty bug specific to Apple Silicon FMA); MSVC x64 uses the
# Intel-compatible FP path so this test PASSES on Windows like Linux.
# - PeptideCapProcessor_test / Peptides_test / RotamerLibrary_test —
# platform-independent OOS PR-merge data regressions (Phase 999.35),
# unconditional WILL_FAIL TRUE; expected-fail on Windows the same way.
#
# Windows DLL discovery: the test EXEs land in build\ci-windows\bin\TEST\
# (test/CMakeLists.txt) but BALL.dll + VIEW.dll live in
# build\ci-windows\bin\, and Qt6 + Boost DLLs live in
# build\ci-windows\vcpkg_installed\x64-windows-release\bin\. Windows
# searches the EXE's own dir first and then PATH, so we must extend PATH
# with BOTH of those directories before invoking ctest.
# ----------------------------------------------------------------------
- name: Build BALL test suite (Windows — Phase 9 gatekeeper, blocking)
if: matrix.os == 'windows-x64'
continue-on-error: false
timeout-minutes: 45
shell: pwsh
run: |
cmake --build build\ci-windows --target build_tests --config Release
- name: Run BALL test suite (Windows — Phase 9 gatekeeper, blocking)
if: matrix.os == 'windows-x64'
continue-on-error: false
timeout-minutes: 60
shell: pwsh
run: |
cd build\ci-windows
# BALL_DATA_PATH MUST be set — see macOS gatekeeper for the post-mortem.
$env:BALL_DATA_PATH = "${{ github.workspace }}\data"
# Windows DLL search path: extend PATH so test EXEs (in bin\TEST\)
# find BALL.dll + VIEW.dll (in bin\), Qt6 DLLs (in the aqt kit's
# $env:QT_ROOT_DIR\bin\) and Boost DLLs (in
# vcpkg_installed\x64-windows-release\bin\). Without this, every
# test exits with STATUS_DLL_NOT_FOUND (0xc0000135) and ctest reports
# mass failures unrelated to the BALL code under test.
# 999.66 — Windows Qt via aqtinstall: Qt DLLs now live in the aqt kit,
# not in vcpkg_installed, so $env:QT_ROOT_DIR\bin is prepended here.
$env:PATH = "$PWD\bin;$env:QT_ROOT_DIR\bin;$PWD\vcpkg_installed\x64-windows-release\bin;$env:PATH"
# Phase 999.34 (2026-05-17): gatekeeper is BLOCKING; ctest exits
# non-zero if any non-WILL_FAIL test fails. ui_v2 in JUnit filename
# so ON + OFF cells don't clobber each other's output at the
# test-report merge step (same rationale as macOS sibling).
# Contract tests are Linux-only by design — they have a dedicated
# `ctest -L contract` gate (Linux job above). The offscreen-Qt harness
# hangs on the Windows runner (12 fixtures timed out) and the §11
# compile-checks are Linux-targeted; macOS/Windows contract coverage is
# a v1.7.5 concern. Exclude the `contract` label so the general Windows
# gatekeeper doesn't sweep them in. They still BUILD here (compile cover).
ctest --output-on-failure --timeout 120 -LE contract `
--output-junit "${{ github.workspace }}\ball-tests-windows.xml"
- name: Upload test results (Windows)
if: always() && matrix.os == 'windows-x64'
uses: actions/upload-artifact@v6
with:
name: ball-tests-windows-${{ github.run_id }}
path: ball-tests-windows.xml
if-no-files-found: warn
retention-days: 30