-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
734 lines (722 loc) · 36.6 KB
/
Copy pathpytest.yml
File metadata and controls
734 lines (722 loc) · 36.6 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
# =============================================================================
# Test CI -- strategy & rationale (details inline at each step)
# =============================================================================
# BATCHING / PARALLELISM
# Tests run as parallel matrix jobs over (os x batch). They are split into 5
# marker-based batches (jvm, native, other-langs, niche, catch-all). We batch by
# pytest MARKER, not pytest-xdist (`-n`): tests share fixtures, language-server
# processes and on-disk resources, so in-process parallelism would race them.
# Each batch installs ONLY the toolchains its languages need (every install step
# is gated on matrix.batch), keeping jobs lean. catch-all is the negation of all
# named marker groups, so every test lands in exactly one batch and the partition
# cannot desync when a language is added.
#
# OS GATING
# Default is the full (os x batch) cross-product. `niche` (heavy/slow toolchains:
# lean, ocaml, nix, julia, R, perl) is ubuntu-only via matrix.exclude -- not worth
# the Windows/macOS cost. An OS-specific *language* inside an otherwise all-OS
# batch (swift -> macOS, haskell -> Linux only) is skipped one level down by the
# central conftest guard, NOT by the matrix.
#
# CONCURRENCY LIMITS (GitHub free tier)
# Hard caps: 20 concurrent jobs total and 5 concurrent macOS jobs. These bound the
# batch count -- the current matrix is 13 jobs (4 of them macOS), inside both
# limits; adding batches/OSes must keep respecting these ceilings or jobs queue.
# `concurrency: cancel-in-progress` also kills superseded runs on the same ref to
# free capacity quickly.
#
# CACHING
# Per-batch language-server cache keyed by os+batch (language-servers-<os>-<batch>),
# plus dedicated caches for the uv venv (keyed on uv.lock), swiftly, ZLS, FPC and
# the Go build/bin. Most niche toolchains restore from cache, so that batch is fast
# despite its size.
#
# TESTING / SKIPS
# All language skip logic is centralized in test/conftest.py
# (`language_tests_enabled()`): a test is skipped when its toolchain is not expected
# on the current OS/CI. Lint and type-check are batch-independent and run once per
# OS (catch-all only), not in every batch.
# =============================================================================
name: Tests
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
cpu:
name: ${{ matrix.batch }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
# Backstop against hung jobs: without this, GitHub's default limit is 6 hours (observed in
# run 29064865197, where a hung job burned the full 6h). Healthy runs peak at ~16 minutes
# warm-cache; 60 leaves ample headroom for cache-cold runs.
timeout-minutes: 60
# Marker groups are defined once here and combined in the "Compute marker expression" step.
# The catch-all batch is derived as the negation of all named groups, so adding a language to
# any group automatically removes it from catch-all -- the partition can never desync, and no
# test can fall through to "no batch". To add/rebalance: edit a MARKERS_* var, and (for a new
# group) add a case branch below + the batch name to the matrix list.
env:
MARKERS_JVM: "java or csharp or fsharp or kotlin or scala or groovy or clojure or bsl"
MARKERS_NATIVE: "cpp or rust or go or zig or pascal or swift" # swift is macOS-only (skips elsewhere via the guard)
# other-langs: grab-bag + scripting languages + haskell + qml. Haskell (GHC/HLS) and qml (qmlls) are
# installed only on Linux -- too slow/expensive to build on the macOS runner (Haskell), or not worth
# the cost of a cross-OS Qt pull (qmlls) -- and skip on Windows+macOS via the central conftest guard.
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml"
# niche: slow, mostly-cached toolchains. ocaml + haskell are the two slowest -- kept in separate
# batches (ocaml here, haskell in other-langs). nix + perl skip on Windows via the guard.
MARKERS_NICHE: "julia or r or perl or lean4 or nix or ocaml"
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.11"]
# 5 batches, each installs ONLY the toolchains its languages need (steps gated by matrix.batch).
# Python/uv stay in every batch; Node is limited to other-langs and catch-all.
# catch-all = web + python + the core framework.
batch: [jvm, native, other-langs, niche, catch-all]
# --- OS gating: which batch runs on which OS -----------------------------------------
# Default is the full cross-product (every batch on all three OSes); the `exclude` entries
# below carve out the exceptions. An OS-specific *language* inside an otherwise all-OS batch
# is handled one level down by the central conftest guard (swift -> macOS only; haskell ->
# not Windows), NOT here -- excludes drop a WHOLE batch from an OS.
# jvm, native, other-langs, catch-all -> ubuntu, windows, macOS
# niche -> ubuntu only (heavy/slow toolchains -- lean, ocaml,
# nix, julia, R, perl -- not worth the Win/macOS cost)
exclude:
- { os: windows-latest, batch: niche }
- { os: macos-latest, batch: niche }
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: "${{ matrix.python-version }}"
- uses: actions/setup-go@v5
if: matrix.batch == 'native'
with:
go-version: ">=1.17.0"
- name: Set up Node.js
if: matrix.batch == 'other-langs' || matrix.batch == 'catch-all'
uses: actions/setup-node@v7
with:
node-version: '24.x'
- name: Ensure cached directory exist before calling cache-related actions
shell: bash
run: |
mkdir -p $HOME/.serena/language_servers/static
mkdir -p $HOME/.cache/go-build
mkdir -p $HOME/go/bin
- name: Install uv
# Use the official action (not `curl | sh`): it adds uv to GITHUB_PATH (the real OS PATH),
# so the Python language server's subprocess `shutil.which("uv"/"uvx")` finds it on Windows.
# The bash installer only puts uv on the Git-Bash MSYS PATH, invisible to Windows processes.
uses: astral-sh/setup-uv@v5
- name: Cache uv virtualenv
id: cache-uv
uses: actions/cache@v3
with:
path: .venv
key: uv-venv-${{ runner.os }}-${{ matrix.python-version }}-lock-${{ hashFiles('uv.lock') }}
- name: Create virtual environment
shell: bash
run: |
if [ ! -d ".venv" ]; then
uv venv
fi
- name: Install Python environment
shell: bash
run: uv sync --extra dev --locked
- name: List Python dependencies
shell: bash
run: uv pip list
- name: Check formatting
# Lint is batch-independent; run it only once per OS instead of in every batch.
if: matrix.batch == 'catch-all'
shell: bash
run: uv run poe lint
# Add Go bin directory to PATH for this workflow
# GITHUB_PATH is a special file that GitHub Actions uses to modify PATH
# Writing to this file adds the directory to the PATH for subsequent steps
- name: Cache Go binaries
if: matrix.batch == 'native'
id: cache-go-binaries
uses: actions/cache@v3
with:
path: |
~/go/bin
~/.cache/go-build
key: go-binaries-${{ runner.os }}-gopls-latest
- name: Install gopls
if: matrix.batch == 'native' && steps.cache-go-binaries.outputs.cache-hit != 'true'
shell: bash
run: go install golang.org/x/tools/gopls@latest
# Erlang currently not tested in CI, random hangings on macos, always hangs on ubuntu
# In local tests, erlang seems to work though
# - name: Install Erlang Language Server
# if: runner.os != 'Windows'
# shell: bash
# run: |
# # Install rebar3 if not already available
# which rebar3 || (curl -fsSL https://github.com/erlang/rebar3/releases/download/3.23.0/rebar3 -o /tmp/rebar3 && chmod +x /tmp/rebar3 && sudo mv /tmp/rebar3 /usr/local/bin/rebar3)
# # Clone and build erlang_ls
# git clone https://github.com/erlang-ls/erlang_ls.git /tmp/erlang_ls
# cd /tmp/erlang_ls
# make install PREFIX=/usr/local
# # Ensure erlang_ls is in PATH
# echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install clojure tools
if: matrix.batch == 'jvm'
uses: DeLaGuardo/setup-clojure@13.4
with:
cli: latest
- name: Install ccls (C/C++ Language Server)
if: matrix.batch == 'native'
shell: bash
run: |
if [[ "${{ runner.os }}" == "Linux" ]]; then
sudo apt-get update
sudo apt-get install -y ccls
elif [[ "${{ runner.os }}" == "macOS" ]]; then
brew install ccls
elif [[ "${{ runner.os }}" == "Windows" ]]; then
choco install ccls -y
fi
# Verify installation
if command -v ccls &> /dev/null; then
echo "ccls installed: $(ccls --version 2>&1 | head -1)"
else
echo "ERROR: ccls installation failed"
exit 1
fi
- name: Setup Java (for JVM based languages)
if: matrix.batch == 'jvm'
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Setup .NET SDK (for F# and C# languages)
if: matrix.batch == 'jvm'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: List .NET runtimes
if: matrix.batch == 'jvm'
shell: bash
run: dotnet --list-runtimes
- name: Install Terraform
if: matrix.batch == 'other-langs'
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.5.0"
terraform_wrapper: false
# - name: Install swift
# if: runner.os != 'Windows'
# uses: swift-actions/setup-swift@v2
# Installation of swift with the action screws with installation of ruby on macOS for some reason
# We can try again when version 3 of the action is released, where they will also use swiftly
# Until then, we use custom code to install swift. Sourcekit-lsp is installed automatically with swift
- name: Cache Swift toolchain (swiftly)
if: runner.os == 'macOS' && matrix.batch == 'native'
id: cache-swiftly
uses: actions/cache@v3
with:
# macOS keeps the toolchain (incl. sourcekit-lsp) under ~/Library/Developer/Toolchains,
# NOT under ~/.swiftly -- both must be cached or the toolchain is lost on restore.
path: |
~/.swiftly
~/Library/Developer/Toolchains
key: swiftly-${{ runner.os }}-6.1.2-v2
- name: Install Swift with swiftly (macOS)
if: runner.os == 'macOS' && matrix.batch == 'native'
run: |
set -e
# Install swiftly itself only if its binary isn't already present (e.g. restored from cache).
if [ ! -x "$HOME/.swiftly/bin/swiftly" ]; then
echo "=== Installing swiftly ==="
curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory
"$HOME/.swiftly/bin/swiftly" init --quiet-shell-followup
fi
. "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh"
hash -r
# Always run install: it downloads on a cache miss and is a fast no-op when the toolchain is
# genuinely present from cache. No `--use` -- that triggers the interactive .swift-version prompt.
swiftly install 6.1.2
# Locate sourcekit-lsp directly (macOS toolchains live under ~/Library/Developer/Toolchains)
# and put its directory on PATH -- avoids `swiftly use`, which prompts/hangs in CI.
SK="$(find "$HOME/Library/Developer/Toolchains" "$HOME/.swiftly" -name sourcekit-lsp 2>/dev/null | head -1)"
if [ -z "$SK" ]; then
echo "ERROR: sourcekit-lsp not found after Swift install. Searched:"
ls -la "$HOME/Library/Developer/Toolchains" 2>/dev/null || echo " (no ~/Library/Developer/Toolchains)"
ls -la "$HOME/.swiftly" 2>/dev/null || echo " (no ~/.swiftly)"
exit 1
fi
echo "sourcekit-lsp: $SK"
dirname "$SK" >> "$GITHUB_PATH"
- name: Install Ruby
if: matrix.batch == 'other-langs'
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
- name: Install Ruby language server
if: matrix.batch == 'other-langs'
shell: bash
run: gem install ruby-lsp
- name: Install Zig
if: matrix.batch == 'native'
uses: mlugg/setup-zig@v2
with:
version: 0.14.1
- name: Cache ZLS (Zig Language Server)
if: matrix.batch == 'native'
id: cache-zls
uses: actions/cache@v3
with:
path: ~/.zls
key: zls-${{ runner.os }}-0.14.0
- name: Install ZLS (Zig Language Server)
if: matrix.batch == 'native'
shell: bash
run: |
ZLS_DIR="$HOME/.zls"
if [[ "${{ steps.cache-zls.outputs.cache-hit }}" != "true" ]]; then
mkdir -p "$ZLS_DIR"
if [[ "${{ runner.os }}" == "Linux" ]]; then
wget -O /tmp/zls.tar.xz https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-linux.tar.xz
tar -xf /tmp/zls.tar.xz -C "$ZLS_DIR" && rm /tmp/zls.tar.xz
elif [[ "${{ runner.os }}" == "macOS" ]]; then
wget -O /tmp/zls.tar.xz https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-macos.tar.xz
tar -xf /tmp/zls.tar.xz -C "$ZLS_DIR" && rm /tmp/zls.tar.xz
elif [[ "${{ runner.os }}" == "Windows" ]]; then
curl -L -o zls.zip https://github.com/zigtools/zls/releases/download/0.14.0/zls-x86_64-windows.zip
unzip -o zls.zip -d "$ZLS_DIR" && rm zls.zip
fi
chmod +x "$ZLS_DIR/zls" 2>/dev/null || true
fi
echo "$ZLS_DIR" >> "$GITHUB_PATH"
- name: Install verible-verilog-ls (SystemVerilog Language Server)
if: matrix.batch == 'other-langs'
shell: bash
run: |
VERIBLE_VERSION="v0.0-4051-g9fdb4057"
if [[ "${{ runner.os }}" == "Linux" ]]; then
wget https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz
tar -xzf verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz
sudo mv verible-${VERIBLE_VERSION}/bin/verible-verilog-ls /usr/local/bin/
rm -rf verible-${VERIBLE_VERSION} verible-${VERIBLE_VERSION}-linux-static-x86_64.tar.gz
elif [[ "${{ runner.os }}" == "macOS" ]]; then
wget https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-macOS.tar.gz
tar -xzf verible-${VERIBLE_VERSION}-macOS.tar.gz
sudo mv verible-${VERIBLE_VERSION}-macOS/bin/verible-verilog-ls /usr/local/bin/
rm -rf verible-${VERIBLE_VERSION}-macOS verible-${VERIBLE_VERSION}-macOS.tar.gz
elif [[ "${{ runner.os }}" == "Windows" ]]; then
curl -L -o verible.zip https://github.com/chipsalliance/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-win64.zip
unzip -o verible.zip
mkdir -p "$HOME/bin"
mv verible-${VERIBLE_VERSION}-win64/verible-verilog-ls.exe "$HOME/bin/"
echo "$HOME/bin" >> $GITHUB_PATH
rm -rf verible-${VERIBLE_VERSION}-win64 verible.zip
fi
# Verify installation
if command -v verible-verilog-ls &> /dev/null; then
echo "verible-verilog-ls installed successfully"
else
echo "WARNING: verible-verilog-ls not found in PATH"
fi
- name: Install Lua Language Server
if: matrix.batch == 'other-langs'
shell: bash
run: |
LUA_LS_VERSION="3.15.0"
LUA_LS_DIR="$HOME/.serena/language_servers/lua"
mkdir -p "$LUA_LS_DIR"
if [[ "${{ runner.os }}" == "Linux" ]]; then
if [[ "$(uname -m)" == "x86_64" ]]; then
wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz
tar -xzf lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz -C "$LUA_LS_DIR"
else
wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-arm64.tar.gz
tar -xzf lua-language-server-${LUA_LS_VERSION}-linux-arm64.tar.gz -C "$LUA_LS_DIR"
fi
chmod +x "$LUA_LS_DIR/bin/lua-language-server"
# Create wrapper script instead of symlink to ensure supporting files are found
echo '#!/bin/bash' | sudo tee /usr/local/bin/lua-language-server > /dev/null
echo 'cd "${HOME}/.serena/language_servers/lua/bin"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
echo 'exec ./lua-language-server "$@"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
sudo chmod +x /usr/local/bin/lua-language-server
rm lua-language-server-*.tar.gz
elif [[ "${{ runner.os }}" == "macOS" ]]; then
if [[ "$(uname -m)" == "x86_64" ]]; then
wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-x64.tar.gz
tar -xzf lua-language-server-${LUA_LS_VERSION}-darwin-x64.tar.gz -C "$LUA_LS_DIR"
else
wget https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-darwin-arm64.tar.gz
tar -xzf lua-language-server-${LUA_LS_VERSION}-darwin-arm64.tar.gz -C "$LUA_LS_DIR"
fi
chmod +x "$LUA_LS_DIR/bin/lua-language-server"
# Create wrapper script instead of symlink to ensure supporting files are found
echo '#!/bin/bash' | sudo tee /usr/local/bin/lua-language-server > /dev/null
echo 'cd "${HOME}/.serena/language_servers/lua/bin"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
echo 'exec ./lua-language-server "$@"' | sudo tee -a /usr/local/bin/lua-language-server > /dev/null
sudo chmod +x /usr/local/bin/lua-language-server
rm lua-language-server-*.tar.gz
elif [[ "${{ runner.os }}" == "Windows" ]]; then
curl -L -o lua-ls.zip https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-win32-x64.zip
unzip -o lua-ls.zip -d "$LUA_LS_DIR"
# For Windows, we'll add the bin directory directly to PATH
# The lua-language-server.exe can find its supporting files relative to its location
echo "$LUA_LS_DIR/bin" >> $GITHUB_PATH
rm lua-ls.zip
fi
- name: Install ansible-core and ansible-lint (for Ansible language server tests)
if: matrix.batch == 'other-langs'
shell: bash
run: uv run pip install ansible-core ansible-lint
- name: Install Haxe
if: matrix.batch == 'other-langs'
# The Haxe LS binary (server.js) is auto-downloaded and runs on Node.js,
# but it delegates to the Haxe compiler for code analysis at runtime.
uses: krdlab/setup-haxe@v2
with:
haxe-version: 4.3.7
- name: Install Elm
if: matrix.batch == 'catch-all'
shell: bash
run: npm install -g elm@0.19.1-6
- name: Install Regal (Rego Language Server)
if: matrix.batch == 'other-langs'
shell: bash
run: |
REGAL_VERSION="0.39.0"
if [[ "${{ runner.os }}" == "Linux" ]]; then
if [[ "$(uname -m)" == "x86_64" ]]; then
curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_x86_64
else
curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Linux_arm64
fi
chmod +x regal
sudo mv regal /usr/local/bin/
elif [[ "${{ runner.os }}" == "macOS" ]]; then
if [[ "$(uname -m)" == "x86_64" ]]; then
curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_x86_64
else
curl -L -o regal https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Darwin_arm64
fi
chmod +x regal
sudo mv regal /usr/local/bin/
elif [[ "${{ runner.os }}" == "Windows" ]]; then
curl -L -o regal.exe https://github.com/StyraInc/regal/releases/download/v${REGAL_VERSION}/regal_Windows_x86_64.exe
mkdir -p "$HOME/bin"
mv regal.exe "$HOME/bin/"
echo "$HOME/bin" >> $GITHUB_PATH
fi
- name: Install qmlls (QML Language Server)
# qmlls needs Qt 6.6+ for textDocument/documentSymbol (the feature these tests assert); Ubuntu
# noble's apt only has Qt 6.4.2, whose qmlls returns -32601 "method not found" for it. So install
# the official standalone build (TheQtCompanyRnD/qmlls-workflow, linked from the Qt docs) -- an 8 MB
# single binary built from recent Qt dev. Restricted to Linux (a cross-OS Qt pull isn't worth it);
# QML skips on Windows/macOS via the conftest guard. libodbc2 is its only non-bundled runtime dep.
if: matrix.batch == 'other-langs' && runner.os == 'Linux'
shell: bash
run: |
QMLLS_VERSION="0.7"
QMLLS_DIR="$HOME/.qmlls"
mkdir -p "$QMLLS_DIR"
curl -fsSL -o /tmp/qmlls.zip "https://github.com/TheQtCompanyRnD/qmlls-workflow/releases/download/${QMLLS_VERSION}/qmllanguageserver-linux-x64-${QMLLS_VERSION}.zip"
unzip -o /tmp/qmlls.zip -d "$QMLLS_DIR" >/dev/null
rm /tmp/qmlls.zip
chmod +x "$QMLLS_DIR/qmlls"
# libodbc.so.2 is qmlls's only non-bundled runtime dep; install only if absent on the image.
ldconfig -p | grep -q 'libodbc.so.2' || { sudo apt-get update && sudo apt-get install -y libodbc2; }
# Fail loudly if any shared lib is still unresolved (e.g. a missing runtime dep).
missing="$(ldd "$QMLLS_DIR/qmlls" | grep 'not found' || true)"
[ -z "$missing" ] || { echo "ERROR: qmlls has unresolved runtime libs:"; echo "$missing"; exit 1; }
echo "$QMLLS_DIR" >> "$GITHUB_PATH"
- name: Cache Free Pascal Compiler
if: matrix.batch == 'native'
id: cache-fpc
uses: actions/cache@v3
with:
path: |
~/fpc
~/fpcsrc
key: fpc-${{ runner.os }}-3.2.2
- name: Install Free Pascal Compiler
if: matrix.batch == 'native'
shell: bash
run: |
# Only the downloads are gated on the cache; the PP/FPCDIR/PATH detection always runs
# (it works the same on freshly-downloaded or cache-restored ~/fpc + ~/fpcsrc).
FPC_CACHE_HIT="${{ steps.cache-fpc.outputs.cache-hit }}"
FPC_VERSION="3.2.2"
if [[ "${{ runner.os }}" == "Linux" ]]; then
sudo apt-get update
sudo apt-get install -y fpc fpc-source
echo "PP=/usr/bin/fpc" >> $GITHUB_ENV
FPCDIR=$(ls -d /usr/share/fpcsrc/*/ 2>/dev/null | head -1 | sed 's:/$::')
[[ -z "$FPCDIR" ]] && FPCDIR="/usr/share/fpcsrc"
echo "FPCDIR=$FPCDIR" >> $GITHUB_ENV
elif [[ "${{ runner.os }}" == "macOS" ]]; then
command -v fpc >/dev/null || brew install fpc
if [[ "$FPC_CACHE_HIT" != "true" ]]; then
# FPC source from SourceForge (fpc-src-laz cask is incompatible with ARM64)
curl -L -o fpc-source.tar.gz "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.tar.gz/download"
mkdir -p "$HOME/fpcsrc"
tar -xzf fpc-source.tar.gz -C "$HOME/fpcsrc"
rm fpc-source.tar.gz
fi
if [[ -d "$HOME/fpcsrc/fpc-${FPC_VERSION}/rtl" ]]; then
FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}"
elif [[ -d "$HOME/fpcsrc/fpc-${FPC_VERSION}/fpc-${FPC_VERSION}/rtl" ]]; then
FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}/fpc-${FPC_VERSION}"
else
FPCDIR="$HOME/fpcsrc/fpc-${FPC_VERSION}"
fi
echo "PP=$(which fpc)" >> $GITHUB_ENV
echo "FPCDIR=$FPCDIR" >> $GITHUB_ENV
elif [[ "${{ runner.os }}" == "Windows" ]]; then
if [[ "$FPC_CACHE_HIT" != "true" ]]; then
# freepascal-ootb (compiler) + source (ootb ships compiled units only, not source)
curl -L -o fpc-ootb.zip https://github.com/fredvs/freepascal-ootb/releases/download/${FPC_VERSION}/fpc-ootb-322-x86_64-win64.zip
mkdir -p "$HOME/fpc"
unzip -q fpc-ootb.zip -d "$HOME/fpc"
rm fpc-ootb.zip
curl -L -o fpc-source.zip "https://sourceforge.net/projects/freepascal/files/Source/${FPC_VERSION}/fpc-${FPC_VERSION}.source.zip/download"
mkdir -p "$HOME/fpcsrc"
unzip -q fpc-source.zip -d "$HOME/fpcsrc"
rm fpc-source.zip
fi
FPC_EXE=$(find "$HOME/fpc" -name "fpc-ootb-64.exe" -type f 2>/dev/null | head -1)
echo "Found FPC executable: $FPC_EXE"
echo "PP=$FPC_EXE" >> $GITHUB_ENV
echo "FPCDIR=$HOME/fpcsrc/fpc-${FPC_VERSION}" >> $GITHUB_ENV
echo "$(dirname "$FPC_EXE")" >> $GITHUB_PATH
fi
- name: Verify FPC installation
if: matrix.batch == 'native'
shell: bash
run: |
echo "=== Environment variables ==="
echo "PP=$PP"
echo "FPCDIR=$FPCDIR"
# Create a simple test program
if [[ "${{ runner.os }}" == "Windows" ]]; then
TEST_PAS="$TEMP/fpc_test.pas"
TEST_OUT="$TEMP/fpc_test"
else
TEST_PAS="/tmp/fpc_test.pas"
TEST_OUT="/tmp/fpc_test"
fi
echo "program fpc_test; begin writeln('FPC works'); end." > "$TEST_PAS"
# Compile using PP (the compiler we actually use in tests)
echo "=== Compiling test program with PP=$PP ==="
if [[ -n "$PP" ]]; then
"$PP" "$TEST_PAS" -o"$TEST_OUT" 2>&1
else
echo "ERROR: PP environment variable is not set"
exit 1
fi
# Verify output binary exists
if [[ -f "$TEST_OUT" ]] || [[ -f "${TEST_OUT}.exe" ]]; then
echo "FPC compilation test PASSED"
else
echo "ERROR: FPC compilation failed - no output binary at $TEST_OUT"
exit 1
fi
# Verify FPCDIR exists (required for pasls)
if [[ -d "$FPCDIR" ]]; then
echo "FPCDIR exists: $FPCDIR"
else
echo "ERROR: FPCDIR does not exist: $FPCDIR"
exit 1
fi
# --- Slow / heavy language toolchains, each isolated in its own batch (OS gating via matrix excludes) ---
- name: Setup Haskell toolchain
if: matrix.batch == 'other-langs' && runner.os == 'Linux'
uses: haskell/ghcup-setup@v1
with:
ghc: '9.12.2'
cabal: '3.10.3.0'
hls: '2.11.0.0'
- name: Verify Haskell tools
if: matrix.batch == 'other-langs' && runner.os == 'Linux'
shell: bash
run: |
which ghc && ghc --version
which cabal && cabal --version
if command -v haskell-language-server-wrapper &>/dev/null; then
haskell-language-server-wrapper --version || echo "WARNING: HLS wrapper version check failed"
else
echo "WARNING: haskell-language-server-wrapper not found (may be incompatible with GHC 9.12.2)"
fi
- name: Pre-build Haskell test project for HLS
if: matrix.batch == 'other-langs' && runner.os == 'Linux'
shell: bash
run: |
cd test/resources/repos/haskell/test_repo
cabal update
cabal build --only-dependencies
cabal build
- name: Install OCaml and opam
if: matrix.batch == 'niche'
uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{ runner.os == 'Windows' && '4.14' || '5.3.x' }}
dune-cache: true
opam-repositories: |
${{ runner.os == 'Windows' && 'opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset' || '' }}
default: https://github.com/ocaml/opam-repository.git
- name: Install OCaml packages
if: matrix.batch == 'niche'
shell: bash
run: |
if [ "$RUNNER_OS" = "Windows" ]; then
opam install -y dune ocaml-lsp-server
else
opam install -y dune 'ocaml-lsp-server>=1.23.0'
fi
- name: Install Nix
if: matrix.batch == 'niche' && runner.os != 'Windows'
uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
# Authenticate any GitHub fetch Nix makes during evaluation/tests with the runner's
# GITHUB_TOKEN (raises the unauthenticated 60 req/hr-per-IP API limit to ~1000/hr).
# nixd itself is installed from the channel below, so this is just insurance.
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Install nixd (Nix Language Server)
if: matrix.batch == 'niche' && runner.os != 'Windows'
shell: bash
run: |
# Install nixd from the nixos-unstable channel (set as <nixpkgs> on NIX_PATH via nix_path
# above) using classic nix-env. `-f '<nixpkgs>'` resolves the channel tarball from
# nixos.org and selects the `nixd` attribute -- it never touches the GitHub API. (The flake
# form `nix profile install github:nix-community/nixd` resolves an unpinned HEAD through
# api.github.com/.../commits/HEAD, which GitHub 429-throttles on CI even with a token;
# and `nix-env -iA nixpkgs.nixd` fails here because no channel named `nixpkgs` is registered.)
nix-env -f '<nixpkgs>' -iA nixd
if ! command -v nixd &> /dev/null; then
echo "nixd installation failed or not in PATH"; exit 1
fi
echo "$HOME/.nix-profile/bin" >> $GITHUB_PATH
- name: Build Lean 4 test project
if: matrix.batch == 'niche'
uses: leanprover/lean-action@v1
with:
lake-package-directory: test/resources/repos/lean4/test_repo
# --- niche batch: R, Julia, Perl ---
- name: Install R
if: matrix.batch == 'niche'
uses: r-lib/actions/setup-r@v2
with:
r-version: '4.4.2'
use-public-rspm: true
- name: Cache R packages (languageserver + its deps)
if: matrix.batch == 'niche'
id: cache-r
uses: actions/cache@v3
with:
# setup-r exports R_LIBS_USER as $RUNNER_TEMP/Library (a stable path) and re-exports it on
# every run -- including cache hits -- so R finds the restored packages with no extra env.
# Bump the trailing -vN (or r-version) to invalidate after an R/OS-image upgrade.
path: ${{ env.R_LIBS_USER }}
key: r-pkgs-${{ runner.os }}-R4.4.2-languageserver-v1
- name: Install R sysdeps (libuv for fs package)
# Always run (NOT gated on the cache): the cached, compiled `fs` package links libuv at
# runtime, so the shared lib must be present even when the build below is skipped.
if: matrix.batch == 'niche' && runner.os == 'Linux'
shell: bash
run: sudo apt-get update && sudo apt-get install -y libuv1-dev
- name: Install R language server
if: matrix.batch == 'niche' && steps.cache-r.outputs.cache-hit != 'true'
shell: bash
run: Rscript -e "install.packages('languageserver', repos='https://cloud.r-project.org')"
- name: Set up Julia
if: matrix.batch == 'niche'
uses: julia-actions/setup-julia@v2
with:
version: '1.12.6'
- name: Cache Julia depot (packages + precompiled)
# Caches the depot (~/.julia: packages, artifacts, compiled, ...) with an auto-managed key,
# so the LanguageServer Pkg.add below is a near-no-op (already installed + precompiled) on
# warm runs. The action handles save/restore itself -- no cache-hit gating needed.
if: matrix.batch == 'niche'
uses: julia-actions/cache@v2
- name: Install Julia LanguageServer
if: matrix.batch == 'niche'
shell: bash
run: julia -e 'using Pkg; Pkg.add(Pkg.PackageSpec(name="LanguageServer", version="5.0.0"))'
- name: Cache Perl::LanguageServer (~/perl5)
if: matrix.batch == 'niche' && runner.os != 'Windows'
id: cache-perl
uses: actions/cache@v3
with:
path: ~/perl5
key: perl-ls-${{ runner.os }}-v1
- name: Install Perl::LanguageServer
if: matrix.batch == 'niche' && runner.os != 'Windows'
shell: bash
run: |
# System deps are cheap and needed at runtime (the cached LS links libanyevent/libio-aio),
# so install them always; the slow cpanm build (XS compile of Coro etc.) runs only on a miss.
if [[ "${{ runner.os }}" == "Linux" ]]; then
sudo apt-get update
sudo apt-get install -y cpanminus build-essential libanyevent-perl libio-aio-perl
elif [[ "${{ runner.os }}" == "macOS" ]]; then
brew install cpanminus
fi
if [[ "${{ steps.cache-perl.outputs.cache-hit }}" != "true" ]]; then
# --local-lib pins the install to ~/perl5 (matching the PERL5LIB exports below), so the
# cached directory is exactly what `perl -MPerl::LanguageServer` loads on a restore.
PERL_MM_USE_DEFAULT=1 cpanm --notest --force --local-lib="$HOME/perl5" Perl::LanguageServer
fi
echo "PERL5LIB=$HOME/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}" >> $GITHUB_ENV
echo "PERL_LOCAL_LIB_ROOT=$HOME/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}" >> $GITHUB_ENV
echo "PERL_MB_OPT=--install_base \"$HOME/perl5\"" >> $GITHUB_ENV
echo "PERL_MM_OPT=INSTALL_BASE=$HOME/perl5" >> $GITHUB_ENV
echo "$HOME/perl5/bin" >> $GITHUB_PATH
- name: Install npm deps for Angular test repo
if: matrix.batch == 'catch-all'
shell: bash
run: |
if [ -d test/resources/repos/angular/test_repo ]; then
cd test/resources/repos/angular/test_repo
npm install --no-audit --no-fund --loglevel=warn
fi
- name: Cache language servers
id: cache-language-servers
uses: actions/cache@v3
with:
path: ~/.serena/language_servers/static
key: language-servers-${{ runner.os }}-${{ matrix.batch }}-v1
restore-keys: |
language-servers-${{ runner.os }}-${{ matrix.batch }}-
- name: Compute marker expression for this batch
id: markers
shell: bash
run: |
case "${{ matrix.batch }}" in
jvm) expr="$MARKERS_JVM" ;;
native) expr="$MARKERS_NATIVE" ;;
other-langs) expr="$MARKERS_OTHER_LANGS" ;;
niche) expr="$MARKERS_NICHE" ;;
catch-all) expr="not ($MARKERS_JVM or $MARKERS_NATIVE or $MARKERS_OTHER_LANGS or $MARKERS_NICHE)" ;;
*) echo "Unknown batch: ${{ matrix.batch }}" >&2; exit 1 ;;
esac
echo "Marker expression: $expr"
echo "expr=$expr" >> "$GITHUB_OUTPUT"
- name: Test with pytest
shell: bash
run: uv run poe test -m "${{ steps.markers.outputs.expr }}" -q --tb=short
- name: Type-checking with ty
# Type-checking is batch-independent; run it only once per OS instead of in every batch.
if: matrix.batch == 'catch-all'
shell: bash
run: uv run poe type-check