Skip to content

Commit 4785ba2

Browse files
committed
Add native gem precompilation support
Package precompiled native gems for Ruby 3.3, 3.4, and 4.0 across 10 platforms: aarch64-linux-gnu, aarch64-linux-musl, aarch64-mingw-ucrt, arm-linux-gnu, arm-linux-musl, arm64-darwin, x64-mingw-ucrt, x86_64-darwin, x86_64-linux-gnu, and x86_64-linux-musl. - Add Ruby-version-aware extension loader in lib/prism.rb with GLIBC error message for musl/glibc mismatches - Add cross-compilation support to Rakefile using rake-compiler-dock - Add a reusable `build-gems.yml` workflow called by both the CI workflow (`native-gem-precompilation.yml`) and the publish workflow, building source and native gems across all platforms and Ruby versions - Add scripts for building, verifying, and testing gems Update `publish-gem.yml` to handle multi-gem publishing: `rubygems/release-gem` only supports a single gem, so this uses `rubygems/configure-rubygems-credentials` and loops `gem push` over each built `.gem`. Add a `workflow_dispatch` trigger alongside the existing tag-push trigger, and create/update a GitHub release for the tag with all gems plus a `CHECKSUMS.txt` manifest. Move testing of the "ruby" platform gem (compile-from-source) out of `main.yml` into `test-gems.yml` so we're testing the gem built by the workflow tooling. Pin all GitHub Actions to commit SHAs and apply zizmor-style hardening across the new and modified workflows.
1 parent 92242d1 commit 4785ba2

15 files changed

Lines changed: 886 additions & 93 deletions

File tree

.github/scripts/gem-build

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#! /usr/bin/env bash
2+
#
3+
# run as part of CI, or as part of the release pipeline with --release
4+
#
5+
RELEASE=false
6+
if [[ "${1:-}" == "--release" ]] ; then
7+
RELEASE=true
8+
shift
9+
fi
10+
11+
if [[ $# -lt 2 ]] ; then
12+
echo "usage: $(basename $0) [--release] <output_dir> <platform>"
13+
exit 1
14+
fi
15+
16+
set -e -u
17+
18+
OUTPUT_DIR=$1
19+
BUILD_NATIVE_GEM=$2
20+
21+
test -e /etc/os-release && cat /etc/os-release
22+
23+
set -x
24+
25+
bundle config set without development
26+
27+
if [[ "${RELEASE}" == "true" ]] ; then
28+
bundle install --local || bundle install
29+
else
30+
bundle config set frozen false
31+
bundle install --local || bundle install
32+
bundle exec rake gemspec:fake-version
33+
fi
34+
35+
if [[ "${BUILD_NATIVE_GEM}" == "ruby" ]] ; then
36+
bundle exec rake build
37+
else
38+
bundle exec rake gem:${BUILD_NATIVE_GEM}:build
39+
fi
40+
41+
./test/prism/packaging/test-gem-file-contents pkg/*.gem
42+
43+
mkdir -p ${OUTPUT_DIR}
44+
cp -v pkg/*.gem ${OUTPUT_DIR}
45+
ls -l ${OUTPUT_DIR}/*

.github/scripts/gem-install-test

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#! /usr/bin/env sh
2+
#
3+
# run as part of CI
4+
#
5+
if [ $# -lt 1 ] ; then
6+
echo "usage: $(basename $0) <gem-file>"
7+
exit 1
8+
fi
9+
10+
gemfile=$1
11+
shift
12+
13+
test -e /etc/os-release && cat /etc/os-release
14+
15+
set -e -x -u
16+
17+
ls -l ${gemfile}
18+
gem install --no-document ${gemfile}
19+
gem list -d prism
20+
21+
bundle config set without development
22+
bundle install --local || bundle install
23+
24+
rm -rf lib ext # ensure we don't use the local files
25+
rake test

.github/workflows/build-gems.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: Build gems
2+
on:
3+
workflow_call:
4+
inputs:
5+
release:
6+
description: "Pass --release to .github/scripts/gem-build (skip timestamp version)"
7+
type: boolean
8+
default: false
9+
artifact_prefix:
10+
description: "Prefix for artifact names (e.g., 'release-v1.10.0-')"
11+
type: string
12+
default: ""
13+
ref:
14+
description: "Git ref to check out (e.g., a version tag). Defaults to the caller's ref."
15+
type: string
16+
default: ""
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
build_source_gem:
23+
name: "source gem"
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
27+
with:
28+
ref: ${{ inputs.ref || github.ref }}
29+
persist-credentials: false
30+
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
31+
with:
32+
ruby-version: "3.4"
33+
bundler-cache: true
34+
- name: Build source gem
35+
run: |
36+
# shellcheck disable=SC2086 # RELEASE_FLAG intentionally word-splits (empty or "--release")
37+
./.github/scripts/gem-build ${RELEASE_FLAG} gems ruby
38+
env:
39+
RELEASE_FLAG: ${{ inputs.release && '--release' || '' }}
40+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
41+
with:
42+
name: "${{ inputs.artifact_prefix }}source-gem"
43+
path: gems
44+
retention-days: 1
45+
46+
build_native_setup:
47+
name: "setup"
48+
runs-on: ubuntu-latest
49+
outputs:
50+
rcd_image_version: ${{ steps.rcd_image_version.outputs.rcd_image_version }}
51+
steps:
52+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
53+
with:
54+
ref: ${{ inputs.ref || github.ref }}
55+
persist-credentials: false
56+
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
57+
with:
58+
ruby-version: "3.4"
59+
bundler-cache: true
60+
- id: rcd_image_version
61+
run: bundle exec ruby -e 'require "rake_compiler_dock"; puts "rcd_image_version=#{RakeCompilerDock::IMAGE_VERSION}"' >> "$GITHUB_OUTPUT"
62+
63+
build_native_gem:
64+
needs: build_native_setup
65+
name: "native gem ${{ matrix.platform }}"
66+
strategy:
67+
# CI runs want to surface every platform's failure (fail-fast: false);
68+
# release runs should stop on first failure to avoid pushing a partial
69+
# set of gems.
70+
fail-fast: ${{ inputs.release }}
71+
matrix:
72+
platform:
73+
- aarch64-linux-gnu
74+
- aarch64-linux-musl
75+
- aarch64-mingw-ucrt
76+
- arm-linux-gnu
77+
- arm-linux-musl
78+
- arm64-darwin
79+
- x64-mingw-ucrt
80+
- x86_64-darwin
81+
- x86_64-linux-gnu
82+
- x86_64-linux-musl
83+
runs-on: ubuntu-latest
84+
steps:
85+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
86+
with:
87+
ref: ${{ inputs.ref || github.ref }}
88+
persist-credentials: false
89+
- name: Build native gem
90+
run: |
91+
# shellcheck disable=SC2086 # RELEASE_FLAG intentionally word-splits (empty or "--release")
92+
docker run --rm -v "$PWD:/work" -w /work \
93+
"ghcr.io/rake-compiler/rake-compiler-dock-image:${RCD_IMAGE_VERSION}-mri-${PLATFORM}" \
94+
./.github/scripts/gem-build ${RELEASE_FLAG} gems "${PLATFORM}"
95+
env:
96+
RCD_IMAGE_VERSION: ${{ needs.build_native_setup.outputs.rcd_image_version }}
97+
RELEASE_FLAG: ${{ inputs.release && '--release' || '' }}
98+
PLATFORM: ${{ matrix.platform }}
99+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
100+
with:
101+
name: "${{ inputs.artifact_prefix }}cruby-${{ matrix.platform }}-gem"
102+
path: gems
103+
retention-days: 1

.github/workflows/main.yml

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -272,82 +272,6 @@ jobs:
272272
- name: Run Ruby tests with valgrind
273273
run: bundle exec rake test:valgrind
274274

275-
gem-package:
276-
runs-on: ubuntu-latest
277-
steps:
278-
- uses: actions/checkout@v6
279-
- uses: ruby/setup-ruby@v1
280-
with:
281-
ruby-version: head
282-
bundler-cache: true
283-
- run: bundle config --local frozen false
284-
- run: bundle exec rake build:dev
285-
- uses: actions/upload-artifact@v7
286-
with:
287-
name: gem-package
288-
path: pkg
289-
retention-days: 1
290-
291-
gem-install:
292-
needs: ["gem-package"]
293-
strategy:
294-
fail-fast: false
295-
matrix:
296-
target:
297-
- { ruby: "2.7", os: "ubuntu-latest", gemfile: "2.7" }
298-
- { ruby: "3.0", os: "ubuntu-latest", gemfile: "3.0" }
299-
- { ruby: "3.1", os: "ubuntu-latest", gemfile: "3.1" }
300-
- { ruby: "3.2", os: "ubuntu-latest", gemfile: "3.2" }
301-
- { ruby: "3.3", os: "ubuntu-latest", gemfile: "3.3" }
302-
- { ruby: "3.4", os: "ubuntu-latest", gemfile: "3.4" }
303-
- { ruby: "4.0", os: "ubuntu-latest", gemfile: "4.0" }
304-
- { ruby: "head", os: "ubuntu-latest", gemfile: "4.1" }
305-
- { ruby: "jruby", os: "ubuntu-latest", gemfile: ".." }
306-
- { ruby: "truffleruby", os: "ubuntu-latest", gemfile: ".." }
307-
308-
- { ruby: "2.7", os: "macos-latest", gemfile: "2.7" }
309-
- { ruby: "3.0", os: "macos-latest", gemfile: "3.0" }
310-
- { ruby: "3.1", os: "macos-latest", gemfile: "3.1" }
311-
- { ruby: "3.2", os: "macos-latest", gemfile: "3.2" }
312-
- { ruby: "3.3", os: "macos-latest", gemfile: "3.3" }
313-
- { ruby: "3.4", os: "macos-latest", gemfile: "3.4" }
314-
- { ruby: "4.0", os: "macos-latest", gemfile: "4.0" }
315-
- { ruby: "head", os: "macos-latest", gemfile: "4.1" }
316-
- { ruby: "jruby", os: "macos-latest", gemfile: ".." }
317-
- { ruby: "truffleruby", os: "macos-latest", gemfile: ".." }
318-
319-
- { ruby: "2.7", os: "windows-latest", gemfile: "2.7" }
320-
- { ruby: "3.0", os: "windows-latest", gemfile: "3.0" }
321-
- { ruby: "3.1", os: "windows-latest", gemfile: "3.1" }
322-
- { ruby: "3.2", os: "windows-latest", gemfile: "3.2" }
323-
- { ruby: "3.3", os: "windows-latest", gemfile: "3.3" }
324-
- { ruby: "3.4", os: "windows-latest", gemfile: "3.4" }
325-
- { ruby: "4.0", os: "windows-latest", gemfile: "4.0" }
326-
- { ruby: "head", os: "windows-latest", gemfile: "4.1" }
327-
- { ruby: "jruby", os: "windows-latest", gemfile: ".." }
328-
env:
329-
BUNDLE_GEMFILE: gemfiles/${{ matrix.target.gemfile }}/Gemfile
330-
runs-on: ${{ matrix.target.os }}
331-
steps:
332-
- uses: actions/checkout@v6
333-
- uses: ruby/setup-ruby@v1
334-
with:
335-
ruby-version: ${{ matrix.target.ruby }}
336-
- uses: actions/download-artifact@v8
337-
with:
338-
name: gem-package
339-
path: pkg
340-
- run: |
341-
gem install --local pkg/prism-*.gem
342-
gem list -d prism
343-
shell: bash
344-
- name: Run tests
345-
run: |
346-
bundle install
347-
rm -rf lib ext # ensure we don't use the local files
348-
rake test
349-
shell: bash
350-
351275
gcc-analyzer:
352276
runs-on: ubuntu-latest
353277
steps:

.github/workflows/publish-gem.yml

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,87 @@
11
name: Publish gem to rubygems.org
2+
concurrency:
3+
group: "release-${{ inputs.version_tag || github.ref }}"
4+
cancel-in-progress: false
25

36
on:
47
push:
58
tags:
69
- 'v*'
10+
workflow_dispatch:
11+
inputs:
12+
version_tag:
13+
description: "Version tag to release (e.g., v1.10.0)"
14+
required: true
15+
type: string
716

817
permissions:
918
contents: read
1019

1120
jobs:
21+
build:
22+
uses: ./.github/workflows/build-gems.yml
23+
with:
24+
release: true
25+
artifact_prefix: "release-${{ inputs.version_tag || github.ref_name }}-"
26+
ref: ${{ inputs.version_tag || github.ref }}
27+
1228
push:
29+
name: "Push gems and create GitHub release"
30+
needs: build
1331
if: github.repository == 'ruby/prism'
1432
runs-on: ubuntu-latest
15-
1633
environment:
1734
name: rubygems.org
1835
url: https://rubygems.org/gems/prism
19-
2036
permissions:
21-
contents: write
22-
id-token: write
23-
37+
contents: write # create/update GitHub releases and upload .gem assets
38+
id-token: write # OIDC token for rubygems.org trusted publishing
2439
steps:
2540
- name: Harden Runner
26-
uses: step-security/harden-runner@v2
41+
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
2742
with:
2843
egress-policy: audit
2944

30-
- uses: actions/checkout@v6
45+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
46+
with:
47+
ref: ${{ inputs.version_tag || github.ref }}
48+
persist-credentials: false
3149

3250
- name: Set up Ruby
33-
uses: ruby/setup-ruby@v1
51+
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 # zizmor: ignore[cache-poisoning] -- cache is for gem-push tooling only; release artifacts come from prior job's uploaded artifacts, not this Ruby env
3452
with:
3553
ruby-version: "3.4"
3654
bundler-cache: true
3755

38-
- name: Publish to RubyGems
39-
uses: rubygems/release-gem@v1
56+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
57+
with:
58+
path: gems
59+
pattern: "release-${{ inputs.version_tag || github.ref_name }}-*"
60+
merge-multiple: true
61+
62+
- name: Print and record checksums
63+
run: |
64+
cd gems
65+
sha256sum ./*.gem | tee CHECKSUMS.txt
66+
67+
- uses: rubygems/configure-rubygems-credentials@762a4b77c3300434bb57c7ce80b20e36231927aa # v2.0.0
68+
69+
# TODO: once RubyGems >= 4.1 is in setup-ruby, `gem push` will auto-attest (ruby/rubygems#9325).
70+
- name: Push gems to RubyGems.org
71+
run: |
72+
for gem in gems/*.gem; do
73+
echo "Pushing ${gem} ..."
74+
gem push "${gem}" || echo "WARNING: Failed to push ${gem} (may already exist)"
75+
done
76+
77+
- name: Create or update GitHub release
78+
env:
79+
GH_TOKEN: ${{ github.token }}
80+
TAG: ${{ inputs.version_tag || github.ref_name }}
81+
REPO: ${{ github.repository }}
82+
run: |
83+
if gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1 ; then
84+
gh release upload "$TAG" --repo "$REPO" --clobber gems/*.gem gems/CHECKSUMS.txt
85+
else
86+
gh release create "$TAG" --repo "$REPO" --title "$TAG" --generate-notes gems/*.gem gems/CHECKSUMS.txt
87+
fi

0 commit comments

Comments
 (0)