Repository navigation
492 lines (443 loc) · 19.1 KB
/
Copy pathbuild-ruby.yml
File metadata and controls
492 lines (443 loc) · 19.1 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
name: Build and Cache Ruby
on:
push:
branches: [main]
paths:
- "versions.yml"
- ".github/workflows/build-ruby.yml"
- "scripts/**"
schedule:
# Build weekly on Sundays at 02:00 UTC
- cron: "0 2 * * 0"
workflow_dispatch:
inputs:
ruby_version:
description: "Ruby version to build (e.g., 3.3.6)"
required: false
type: string
platforms:
description: "Platforms to build (comma-separated: ubuntu-22.04,ubuntu-24.04,ubuntu-25.04,debian-bookworm,alpine,arch,macos,windows)"
required: false
default: "ubuntu-22.04,ubuntu-24.04,ubuntu-25.04,debian-bookworm,alpine,arch,macos,windows"
type: string
permissions:
contents: read
env:
BORINGCACHE_RESTORE_TOKEN: ${{ secrets.BORINGCACHE_RESTORE_TOKEN }}
BORINGCACHE_SAVE_TOKEN: ${{ secrets.BORINGCACHE_SAVE_TOKEN }}
BORINGCACHE_DEFAULT_WORKSPACE: ${{ secrets.BORINGCACHE_DEFAULT_WORKSPACE || 'ruby/ruby' }}
jobs:
token-check:
name: BoringCache Token Check
runs-on: ubuntu-24.04
steps:
- name: Require save and restore tokens
run: |
if [ -z "${BORINGCACHE_RESTORE_TOKEN}" ]; then
echo "BORINGCACHE_RESTORE_TOKEN is required for cache lookup/restore." >&2
exit 1
fi
if [ -z "${BORINGCACHE_SAVE_TOKEN}" ]; then
echo "BORINGCACHE_SAVE_TOKEN is required for cache saves." >&2
exit 1
fi
prepare:
needs: token-check
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.setup.outputs.matrix }}
versions: ${{ steps.setup.outputs.versions }}
steps:
- uses: actions/checkout@v6
- name: Setup build matrix
id: setup
run: |
# Parse versions.yml and create build matrix matching CLI platforms exactly
python3 -c "
import yaml
import json
import os
with open('versions.yml', 'r') as f:
config = yaml.safe_load(f)
# Get input parameters
specific_version = '${{ github.event.inputs.ruby_version }}'
platforms_input = '${{ github.event.inputs.platforms }}'
# Filter versions
versions = config['versions']
if specific_version:
versions = [v for v in versions if v['version'] == specific_version]
# Filter platforms
platforms = config['platforms']
if platforms_input:
requested_platforms = [p.strip() for p in platforms_input.split(',')]
platforms = {k: v for k, v in platforms.items() if k in requested_platforms}
# Define variant groups for parallel builds
# Group 1: standard,yjit (no jemalloc dependency)
# Group 2: jemalloc,jemalloc-yjit (share jemalloc compilation)
variant_groups = [
{'name': 'base', 'variants': 'standard,yjit'},
{'name': 'jemalloc', 'variants': 'jemalloc,jemalloc-yjit'}
]
# Windows only supports standard variant
windows_variant_groups = [
{'name': 'base', 'variants': 'standard'}
]
# Create matrix matching CLI structure exactly
matrix = []
for version in versions:
for platform_name, platform_config in platforms.items():
# Skip Windows for preview versions
if version.get('status') == 'preview' and platform_name == 'windows':
continue
# Skip Windows for Ruby 3.3.x series due to compilation issues
if platform_name == 'windows' and version['version'].startswith('3.3.'):
continue
# Skip problematic Ruby versions on Windows due to MSYS2/GCC issues:
# - 4.0.x: mkconfig.rb stdout file descriptor bug
# - 3.2.x: GCC incompatible pointer type errors in mini_builtin.c
if platform_name == 'windows' and (version['version'].startswith('4.0.') or version['version'].startswith('3.2.')):
continue
# Select variant groups based on platform
groups = windows_variant_groups if platform_name == 'windows' else variant_groups
for arch in platform_config['architectures']:
for vgroup in groups:
if platform_config['build_type'] == 'native':
# Native builds use specific runners per arch
runner = platform_config['runners'][arch]
# For macOS, include the runner version in the platform name
if platform_name == 'macos':
platform_id = runner # Use runner name like 'macos-15'
else:
platform_id = platform_name
matrix.append({
'version': version['version'],
'platform': platform_id,
'arch': arch,
'runner': runner,
'build_type': 'native',
'name': f'{platform_name}-{arch}-{vgroup[\"name\"]}',
'use_docker': False,
'variant_group': vgroup['variants']
})
else:
# Docker builds use architecture-specific runners
if 'runners' in platform_config:
# New format with per-arch runners
runner = platform_config['runners'][arch]
else:
# Legacy format with single runner
runner = platform_config['runner']
matrix.append({
'version': version['version'],
'platform': platform_name,
'arch': arch,
'runner': runner,
'container': platform_config['container'],
'build_type': 'docker',
'name': f'{platform_name}-{arch}-{vgroup[\"name\"]}',
'use_docker': True,
'variant_group': vgroup['variants']
})
# Output matrix
matrix_json = json.dumps({'include': matrix})
versions_json = json.dumps([v['version'] for v in versions])
print(f'matrix={matrix_json}')
print(f'versions={versions_json}')
# Set outputs
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write(f'matrix={matrix_json}\\n')
f.write(f'versions={versions_json}\\n')
"
build-ruby:
needs: prepare
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.prepare.outputs.matrix).include }}
steps:
- uses: actions/checkout@v6
- name: Setup BoringCache CLI
uses: boringcache/one@v1
with:
setup: none
cli-platform: ${{ (matrix.use_docker == true && format('{0}-{1}', matrix.platform, matrix.arch)) || '' }}
- name: Copy CLI to workspace for Docker
if: matrix.use_docker == true
run: cp "$(which boringcache)" .boringcache-container
# Docker builds for Debian/Alpine/Arch
- name: Build Ruby in Docker container
if: matrix.use_docker == true
run: |
docker run --rm \
-v "$PWD:/workspace" \
-w /workspace \
-e BORINGCACHE_RESTORE_TOKEN="${BORINGCACHE_RESTORE_TOKEN}" \
-e BORINGCACHE_SAVE_TOKEN="${BORINGCACHE_SAVE_TOKEN}" \
-e BORINGCACHE_DEFAULT_WORKSPACE="${BORINGCACHE_DEFAULT_WORKSPACE}" \
${{ matrix.container }} \
/bin/sh -c '
set -e
# Use pre-copied CLI binary
cp /workspace/.boringcache-container /usr/local/bin/boringcache
chmod +x /usr/local/bin/boringcache
# Check if all variants are already cached before installing deps
WORKSPACE="${BORINGCACHE_DEFAULT_WORKSPACE:-ruby/ruby}"
VARIANTS="${{ matrix.variant_group }}"
TAGS=""
for variant in $(echo "$VARIANTS" | tr "," " "); do
variant=$(echo "$variant" | tr -d " ")
if [ "$variant" = "standard" ]; then
TAGS="${TAGS:+${TAGS},}ruby-${{ matrix.version }}"
else
TAGS="${TAGS:+${TAGS},}ruby-${{ matrix.version }}-${variant}"
fi
done
RESULT=$(boringcache check "$WORKSPACE" "$TAGS" --json 2>/dev/null || echo "{\"misses\":1}")
MISSES=$(echo "$RESULT" | sed -n "s/.*\"misses\" *: *\([0-9]*\).*/\1/p")
MISSES="${MISSES:-1}"
if [ "$MISSES" -eq 0 ]; then
echo "All variants already cached, skipping build"
exit 0
fi
# Install build dependencies based on container type
if [ -f /etc/alpine-release ]; then
set +e
for i in 1 2 3; do
echo "Attempt $i: Updating Alpine package index..."
apk update || echo "apk update had warnings/errors but continuing..."
if [ $i -eq 3 ]; then
echo "Package index update completed (some repositories may be unavailable)"
break
fi
sleep 2
done
for i in 1 2 3; do
echo "Attempt $i: Installing Alpine packages..."
if apk add --no-cache --update-cache build-base curl bash python3 py3-yaml \
openssl-dev readline-dev zlib-dev libffi-dev yaml-dev \
gdbm-dev ncurses-dev gmp-dev pkgconfig autoconf bison \
linux-headers make jemalloc-dev valgrind-dev git; then
break
else
echo "Package installation failed on attempt $i"
if [ $i -eq 3 ]; then
apk add --no-cache build-base curl bash python3 openssl-dev \
readline-dev zlib-dev autoconf bison git make || true
break
fi
sleep 5
fi
done
for i in 1 2 3; do
echo "Attempt $i: Installing Rust via rustup..."
if curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable; then
. "$HOME/.cargo/env"
export PATH="$HOME/.cargo/bin:$PATH"
break
else
if [ $i -eq 3 ]; then exit 1; fi
sleep 10
fi
done
set -e
elif [ -f /etc/arch-release ]; then
pacman -Syu --noconfirm
pacman -S --noconfirm base-devel curl python python-yaml \
openssl readline zlib libffi libyaml gdbm ncurses gmp \
pkgconf autoconf bison jemalloc git make valgrind
for i in 1 2 3; do
echo "Attempt $i: Installing Rust via rustup..."
if curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable; then
. "$HOME/.cargo/env"
export PATH="$HOME/.cargo/bin:$PATH"
break
else
if [ $i -eq 3 ]; then exit 1; fi
sleep 10
fi
done
elif [ -f /etc/debian_version ]; then
export DEBIAN_FRONTEND=noninteractive
for i in 1 2 3; do
echo "Attempt $i: Updating package lists..."
if apt-get update; then break; fi
if [ $i -eq 3 ]; then exit 1; fi
sleep 5
done
for i in 1 2 3; do
echo "Attempt $i: Installing packages..."
if apt-get install -y --fix-missing build-essential curl python3 python3-yaml \
libssl-dev libreadline-dev zlib1g-dev libffi-dev libyaml-dev \
libgdbm-dev libncurses5-dev libgmp-dev pkg-config autoconf \
bison libjemalloc-dev git make valgrind systemtap-sdt-dev; then
break
else
if [ $i -eq 3 ]; then exit 1; fi
sleep 5
apt-get update || true
fi
done
for i in 1 2 3; do
echo "Attempt $i: Installing Rust via rustup..."
if curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable; then
. "$HOME/.cargo/env"
export PATH="$HOME/.cargo/bin:$PATH"
break
else
if [ $i -eq 3 ]; then exit 1; fi
sleep 10
fi
done
fi
if [ -f "$HOME/.cargo/env" ]; then
. "$HOME/.cargo/env"
export PATH="$HOME/.cargo/bin:$PATH"
fi
echo "Using native ${{ matrix.arch }} runner for ${{ matrix.platform }}"
make ci-build RUBY_VERSION=${{ matrix.version }} PLATFORM=${{ matrix.platform }} ARCH=${{ matrix.arch }} VARIANTS="${{ matrix.variant_group }}"
'
- name: Check if cache exists (Unix)
id: cache-check
if: matrix.use_docker != true && matrix.platform != 'windows'
run: |
WORKSPACE="${BORINGCACHE_DEFAULT_WORKSPACE:-ruby/ruby}"
VARIANTS="${{ matrix.variant_group }}"
TAGS=""
IFS=',' read -ra VARIANT_ARRAY <<< "$VARIANTS"
for variant in "${VARIANT_ARRAY[@]}"; do
variant=$(echo "$variant" | xargs)
if [[ "$variant" == "standard" ]]; then
TAGS="${TAGS:+${TAGS},}ruby-${{ matrix.version }}"
else
TAGS="${TAGS:+${TAGS},}ruby-${{ matrix.version }}-${variant}"
fi
done
RESULT=$(boringcache check "$WORKSPACE" "$TAGS" --json 2>/dev/null || echo '{"misses":1}')
MISSES=$(echo "$RESULT" | jq -r '.misses // 1')
if [[ "$MISSES" -eq 0 ]]; then
echo "⏭ All variants already cached, skipping build"
echo "skip_build=true" >> $GITHUB_OUTPUT
else
echo "skip_build=false" >> $GITHUB_OUTPUT
fi
- name: Install build dependencies (Ubuntu)
if: matrix.use_docker != true && startsWith(matrix.platform, 'ubuntu') && steps.cache-check.outputs.skip_build != 'true'
run: |
sudo apt-get update
# Install core build dependencies
sudo apt-get install -y \
build-essential \
libssl-dev \
libreadline-dev \
zlib1g-dev \
libffi-dev \
libyaml-dev \
libgdbm-dev \
libncurses5-dev \
libgmp-dev \
pkg-config \
autoconf \
bison \
rustc \
libjemalloc-dev \
git \
curl \
python3 \
python3-yaml \
valgrind \
systemtap-sdt-dev \
libdb-dev \
tk-dev \
uuid-dev \
libsqlite3-dev \
jq \
libicu-dev \
libxml2-dev \
libxslt1-dev \
libyajl-dev \
libsodium-dev \
libpq-dev \
libmagickwand-dev \
imagemagick \
libvips-dev \
libcurl4-openssl-dev
- name: Install build dependencies (macOS)
if: matrix.use_docker != true && startsWith(matrix.platform, 'macos') && steps.cache-check.outputs.skip_build != 'true'
run: |
# GitHub-hosted macOS runners have Homebrew pre-installed
# Core Ruby dependencies only
brew install openssl@3 readline libyaml gdbm libffi autoconf bison jemalloc rust
- name: Setup MSYS2 (Windows)
if: matrix.use_docker != true && matrix.platform == 'windows'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >-
git
base-devel
mingw-w64-x86_64-gcc
mingw-w64-x86_64-openssl
mingw-w64-x86_64-readline
mingw-w64-x86_64-zlib
mingw-w64-x86_64-libffi
mingw-w64-x86_64-libyaml
mingw-w64-x86_64-gdbm
mingw-w64-x86_64-rust
mingw-w64-x86_64-oniguruma
mingw-w64-x86_64-pkg-config
autoconf
bison
python3
mingw-w64-x86_64-python-yaml
mingw-w64-x86_64-jq
mingw-w64-x86_64-icu
mingw-w64-x86_64-libxml2
mingw-w64-x86_64-libxslt
mingw-w64-x86_64-curl
mingw-w64-x86_64-imagemagick
mingw-w64-x86_64-sqlite3
mingw-w64-x86_64-postgresql
mingw-w64-x86_64-gmp
- name: Build and Upload Ruby (Unix Native)
if: matrix.use_docker != true && matrix.platform != 'windows' && steps.cache-check.outputs.skip_build != 'true'
run: |
make ci-build RUBY_VERSION=${{ matrix.version }} PLATFORM=${{ matrix.platform }} ARCH=${{ matrix.arch }} VARIANTS="${{ matrix.variant_group }}"
- name: Build and Upload Ruby (Windows)
if: matrix.use_docker != true && matrix.platform == 'windows'
shell: msys2 {0}
run: |
make ci-build RUBY_VERSION=${{ matrix.version }} PLATFORM=${{ matrix.platform }} ARCH=${{ matrix.arch }} VARIANTS="${{ matrix.variant_group }}"
cleanup:
needs: [build-ruby]
runs-on: ubuntu-24.04
if: always()
steps:
- name: Setup BoringCache CLI
uses: boringcache/one@v1
with:
setup: none
- name: Clean up old cache entries
run: |
echo "Cleaning up old Ruby cache entries..."
WORKSPACE="${BORINGCACHE_DEFAULT_WORKSPACE:-ruby/ruby}"
boringcache ls "$WORKSPACE" --limit 100 | \
grep "^ruby-" | \
tail -n +11 | \
while read entry; do
echo "Deleting old entry: $entry"
boringcache delete --no-platform --no-git "$WORKSPACE" "$entry" || true
done
build-status:
name: Build Status
needs: [token-check, prepare, build-ruby, cleanup]
runs-on: ubuntu-24.04
if: always()
steps:
- name: Verify required jobs
run: |
test "${{ needs.token-check.result }}" = "success"
test "${{ needs.prepare.result }}" = "success"
test "${{ needs.build-ruby.result }}" = "success"
test "${{ needs.cleanup.result }}" = "success"