Skip to content

Commit 4075aa9

Browse files
authored
feat(ruby): add build-from-source workflow to fill R2 mirror gaps (#229)
1 parent 29f6d1b commit 4075aa9

7 files changed

Lines changed: 844 additions & 3 deletions

File tree

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
name: Build Ruby from Source
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: 'Ruby version to build (leave empty to auto-detect gaps)'
8+
required: false
9+
type: string
10+
dry_run:
11+
description: 'Only detect gaps, do not build'
12+
required: false
13+
type: boolean
14+
default: false
15+
workflow_run:
16+
workflows: ["Mirror Sync"]
17+
types: [completed]
18+
19+
jobs:
20+
detect-gaps:
21+
name: Detect gaps
22+
runs-on: ubuntu-latest
23+
outputs:
24+
matrix: ${{ steps.detect.outputs.matrix }}
25+
has_gaps: ${{ steps.detect.outputs.has_gaps }}
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Setup Go
31+
uses: actions/setup-go@v5
32+
with:
33+
go-version-file: 'go.mod'
34+
35+
- name: Build gap detector
36+
run: |
37+
cd scripts/detect-ruby-gaps
38+
go build -o detect-ruby-gaps .
39+
40+
- name: Detect gaps
41+
id: detect
42+
env:
43+
R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
44+
R2_BUCKET: ${{ secrets.CLOUDFLARE_R2_BUILDS_BUCKET }}
45+
R2_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
46+
R2_SECRET_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
47+
run: |
48+
ARGS=""
49+
if [ -n "${{ inputs.version }}" ]; then
50+
ARGS="--version=${{ inputs.version }}"
51+
else
52+
ARGS="--r2-endpoint=$R2_ENDPOINT --r2-bucket=$R2_BUCKET --r2-access-key=$R2_ACCESS_KEY --r2-secret-key=$R2_SECRET_KEY"
53+
fi
54+
55+
MATRIX=$(./scripts/detect-ruby-gaps/detect-ruby-gaps $ARGS)
56+
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
57+
58+
# Check if there are any gaps
59+
COUNT=$(echo "$MATRIX" | jq '.include | length')
60+
if [ "$COUNT" -gt 0 ]; then
61+
echo "has_gaps=true" >> "$GITHUB_OUTPUT"
62+
echo "Found $COUNT gaps to fill"
63+
else
64+
echo "has_gaps=false" >> "$GITHUB_OUTPUT"
65+
echo "No gaps detected"
66+
fi
67+
68+
- name: Summary
69+
run: |
70+
echo "## Gap Detection Results" >> $GITHUB_STEP_SUMMARY
71+
echo "" >> $GITHUB_STEP_SUMMARY
72+
MATRIX='${{ steps.detect.outputs.matrix }}'
73+
COUNT=$(echo "$MATRIX" | jq '.include | length')
74+
echo "Found **$COUNT** version+platform gaps" >> $GITHUB_STEP_SUMMARY
75+
echo "" >> $GITHUB_STEP_SUMMARY
76+
if [ "$COUNT" -gt 0 ]; then
77+
echo "| Version | Platform | Runner |" >> $GITHUB_STEP_SUMMARY
78+
echo "|---------|----------|--------|" >> $GITHUB_STEP_SUMMARY
79+
echo "$MATRIX" | jq -r '.include[] | "| \(.version) | \(.platform) | \(.runner) |"' >> $GITHUB_STEP_SUMMARY
80+
fi
81+
82+
build:
83+
name: Build Ruby ${{ matrix.version }} (${{ matrix.platform }})
84+
needs: detect-gaps
85+
if: needs.detect-gaps.outputs.has_gaps == 'true' && inputs.dry_run != true
86+
runs-on: ${{ matrix.runner }}
87+
timeout-minutes: 60
88+
strategy:
89+
fail-fast: false
90+
matrix: ${{ fromJson(needs.detect-gaps.outputs.matrix) }}
91+
steps:
92+
# ─── Unix builds (Linux & macOS) ───
93+
- name: Install ruby-build (Unix)
94+
if: matrix.build_os != 'windows'
95+
run: |
96+
git clone https://github.com/rbenv/ruby-build.git "$RUNNER_TEMP/ruby-build"
97+
echo "$RUNNER_TEMP/ruby-build/bin" >> "$GITHUB_PATH"
98+
99+
- name: Install build dependencies (Linux)
100+
if: matrix.build_os == 'linux'
101+
run: |
102+
sudo apt-get update
103+
sudo apt-get install -y autoconf bison build-essential libssl-dev libyaml-dev \
104+
libreadline-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev libgmp-dev rustc
105+
106+
- name: Install build dependencies (macOS)
107+
if: matrix.build_os == 'darwin'
108+
run: |
109+
brew install openssl@3 readline libyaml gmp rust
110+
111+
- name: Build Ruby (Unix)
112+
if: matrix.build_os != 'windows'
113+
env:
114+
RUBY_CONFIGURE_OPTS: >-
115+
${{ matrix.platform == 'darwin-arm64'
116+
&& '--disable-shared --disable-install-doc'
117+
|| '--enable-shared --disable-install-doc' }}
118+
CPPFLAGS: "-DENABLE_PATH_CHECK=0"
119+
run: |
120+
ruby-build ${{ matrix.version }} "$RUNNER_TEMP/ruby-install/ruby"
121+
122+
- name: Verify Ruby (Unix)
123+
if: matrix.build_os != 'windows'
124+
run: |
125+
"$RUNNER_TEMP/ruby-install/ruby/bin/ruby" --version
126+
127+
- name: Package Ruby (Unix)
128+
if: matrix.build_os != 'windows'
129+
run: |
130+
cd "$RUNNER_TEMP/ruby-install"
131+
tar -czf "$RUNNER_TEMP/ruby-${{ matrix.version }}-${{ matrix.platform }}.tar.gz" ruby/
132+
133+
# ─── Windows builds (MSYS2/MinGW) ───
134+
- name: Setup MSYS2 (Windows)
135+
if: matrix.build_os == 'windows'
136+
uses: msys2/setup-msys2@v2
137+
with:
138+
msystem: ${{ matrix.arch == 'amd64' && 'MINGW64' || 'MINGW32' }}
139+
update: true
140+
install: >-
141+
base-devel
142+
${{ matrix.arch == 'amd64'
143+
&& 'mingw-w64-x86_64-toolchain mingw-w64-x86_64-openssl mingw-w64-x86_64-libyaml mingw-w64-x86_64-libffi mingw-w64-x86_64-readline mingw-w64-x86_64-zlib mingw-w64-x86_64-gmp'
144+
|| 'mingw-w64-i686-toolchain mingw-w64-i686-openssl mingw-w64-i686-libyaml mingw-w64-i686-libffi mingw-w64-i686-readline mingw-w64-i686-zlib mingw-w64-i686-gmp' }}
145+
autoconf bison git wget
146+
147+
- name: Build Ruby (Windows)
148+
if: matrix.build_os == 'windows'
149+
shell: msys2 {0}
150+
run: |
151+
VERSION="${{ matrix.version }}"
152+
MAJOR_MINOR="${VERSION%.*}"
153+
INSTALL_DIR="$RUNNER_TEMP/ruby-install/ruby-$VERSION"
154+
155+
# Download Ruby source
156+
wget -q "https://cache.ruby-lang.org/pub/ruby/${MAJOR_MINOR}/ruby-${VERSION}.tar.gz"
157+
tar xf "ruby-${VERSION}.tar.gz"
158+
cd "ruby-${VERSION}"
159+
160+
# Configure and build
161+
./configure --prefix="$(cygpath -u "$INSTALL_DIR")" --enable-shared --disable-install-doc
162+
make -j$(nproc)
163+
make install
164+
165+
- name: Verify Ruby (Windows)
166+
if: matrix.build_os == 'windows'
167+
shell: msys2 {0}
168+
run: |
169+
"$RUNNER_TEMP/ruby-install/ruby-${{ matrix.version }}/bin/ruby.exe" --version
170+
171+
- name: Package Ruby (Windows)
172+
if: matrix.build_os == 'windows'
173+
run: |
174+
cd "$env:RUNNER_TEMP\ruby-install"
175+
7z a "$env:RUNNER_TEMP\ruby-${{ matrix.version }}-${{ matrix.platform }}.7z" "ruby-${{ matrix.version }}"
176+
177+
# ─── Upload to R2 (all platforms) ───
178+
- name: Determine archive path
179+
id: archive
180+
shell: bash
181+
run: |
182+
if [ "${{ matrix.build_os }}" = "windows" ]; then
183+
ARCHIVE="$RUNNER_TEMP/ruby-${{ matrix.version }}-${{ matrix.platform }}.7z"
184+
EXT=".7z"
185+
else
186+
ARCHIVE="$RUNNER_TEMP/ruby-${{ matrix.version }}-${{ matrix.platform }}.tar.gz"
187+
EXT=".tar.gz"
188+
fi
189+
echo "path=$ARCHIVE" >> "$GITHUB_OUTPUT"
190+
echo "ext=$EXT" >> "$GITHUB_OUTPUT"
191+
192+
- name: Calculate SHA256
193+
id: checksum
194+
shell: bash
195+
run: |
196+
if [ "${{ matrix.build_os }}" = "windows" ]; then
197+
SHA256=$(sha256sum "${{ steps.archive.outputs.path }}" | awk '{print $1}')
198+
else
199+
SHA256=$(shasum -a 256 "${{ steps.archive.outputs.path }}" | awk '{print $1}')
200+
fi
201+
echo "sha256=$SHA256" >> "$GITHUB_OUTPUT"
202+
203+
- name: Create metadata
204+
shell: bash
205+
run: |
206+
SIZE=$(wc -c < "${{ steps.archive.outputs.path }}" | tr -d ' ')
207+
cat > "$RUNNER_TEMP/meta.json" << EOF
208+
{
209+
"sha256": "${{ steps.checksum.outputs.sha256 }}",
210+
"sha256_source": "dtvem",
211+
"source_url": "built-from-source",
212+
"mirrored_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
213+
"size": $SIZE
214+
}
215+
EOF
216+
217+
- name: Install AWS CLI (macOS)
218+
if: matrix.build_os == 'darwin'
219+
run: brew install awscli
220+
221+
- name: Upload binary to R2
222+
shell: bash
223+
env:
224+
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
225+
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
226+
AWS_DEFAULT_REGION: auto
227+
R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
228+
R2_BUCKET: ${{ secrets.CLOUDFLARE_R2_BUILDS_BUCKET }}
229+
run: |
230+
aws s3 cp "${{ steps.archive.outputs.path }}" \
231+
"s3://$R2_BUCKET/ruby/${{ matrix.version }}/${{ matrix.platform }}${{ steps.archive.outputs.ext }}" \
232+
--endpoint-url "$R2_ENDPOINT"
233+
234+
- name: Upload metadata to R2
235+
shell: bash
236+
env:
237+
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
238+
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
239+
AWS_DEFAULT_REGION: auto
240+
R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
241+
R2_BUCKET: ${{ secrets.CLOUDFLARE_R2_BUILDS_BUCKET }}
242+
run: |
243+
aws s3 cp "$RUNNER_TEMP/meta.json" \
244+
"s3://$R2_BUCKET/ruby/${{ matrix.version }}/${{ matrix.platform }}.meta.json" \
245+
--endpoint-url "$R2_ENDPOINT" \
246+
--content-type "application/json"
247+
248+
trigger-manifests:
249+
name: Trigger manifest regeneration
250+
needs: build
251+
if: needs.build.result == 'success'
252+
runs-on: ubuntu-latest
253+
steps:
254+
- name: Trigger manifest generation
255+
env:
256+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
257+
run: |
258+
gh workflow run generate-manifests-from-r2.yml \
259+
--repo CodingWithCalvin/dtvem.cli \
260+
--field runtime=ruby

schemas/manifest.schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
"type": "string",
6565
"enum": ["upstream", "dtvem"],
6666
"description": "Origin of the SHA256 checksum: 'upstream' if from the original provider, 'dtvem' if generated by us during mirroring"
67+
},
68+
"source": {
69+
"type": "string",
70+
"description": "Build source indicator: 'built-from-source' when the binary was compiled by dtvem rather than obtained from upstream"
6771
}
6872
}
6973
}

scripts/detect-ruby-gaps/go.mod

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module github.com/CodingWithCalvin/dtvem.cli/scripts/detect-ruby-gaps
2+
3+
go 1.23.0
4+
5+
require (
6+
github.com/aws/aws-sdk-go-v2 v1.32.6
7+
github.com/aws/aws-sdk-go-v2/config v1.28.6
8+
github.com/aws/aws-sdk-go-v2/credentials v1.17.47
9+
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0
10+
)
11+
12+
require (
13+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
14+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect
15+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
16+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect
17+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
18+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect
19+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
20+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect
21+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect
22+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect
23+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect
24+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect
25+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect
26+
github.com/aws/smithy-go v1.22.1 // indirect
27+
)

scripts/detect-ruby-gaps/go.sum

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4=
2+
github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
3+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
4+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
5+
github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo=
6+
github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko=
7+
github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw=
8+
github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w=
9+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0=
10+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY=
11+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU=
12+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU=
13+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA=
14+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE=
15+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
16+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
17+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE=
18+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE=
19+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
20+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
21+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI=
22+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw=
23+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4=
24+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug=
25+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk=
26+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI=
27+
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw=
28+
github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY=
29+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw=
30+
github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc=
31+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4=
32+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY=
33+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA=
34+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8=
35+
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
36+
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=

0 commit comments

Comments
 (0)