CRuby Dev Builds #1959
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: CRuby Dev Builds | |
permissions: | |
contents: write | |
on: | |
workflow_dispatch: | |
inputs: | |
skip_slow: | |
type: boolean | |
default: false | |
push: | |
tags: | |
- '*' | |
schedule: | |
- cron: '0 19 * * *' | |
jobs: | |
prepare: | |
name: Check if the latest ruby commit is already built | |
runs-on: ubuntu-latest | |
outputs: | |
should_build: ${{ steps.check_commit.outputs.should_build }} | |
should_build_3_4_asan: ${{ steps.check_commit.outputs.should_build_3_4_asan }} | |
commit: ${{ steps.latest_commit.outputs.commit }} | |
commit_3_4_asan: ${{ steps.latest_commit_3_4_asan.outputs.commit }} | |
previous_release: ${{ steps.check_commit.outputs.previous_release }} | |
build_matrix: ${{ steps.matrix.outputs.build_matrix }} | |
reuse_matrix: ${{ steps.matrix.outputs.reuse_matrix }} | |
steps: | |
- name: Clone ruby | |
uses: actions/checkout@v4 | |
with: | |
repository: ruby/ruby | |
path: ruby | |
- name: Set latest_commit | |
id: latest_commit | |
working-directory: ruby | |
run: echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT | |
- name: Set latest commit (3.4-asan) | |
id: latest_commit_3_4_asan | |
working-directory: ruby | |
run: | | |
git fetch origin --depth=1 --no-tags '+refs/tags/v3_4_*:refs/tags/v3_4_*' | |
LATEST_TAG=$(git tag --list | grep -E "v3_4_[0-9]+$" | sort -V | tail -n1) | |
git checkout "$LATEST_TAG" | |
echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT | |
- name: Check if latest commit already built | |
uses: actions/github-script@v7 | |
id: check_commit | |
with: | |
script: | | |
const latestDevCommit = "${{ steps.latest_commit.outputs.commit }}" | |
const latest34ASan = "${{ steps.latest_commit_3_4_asan.outputs.commit }}" | |
const { owner, repo } = context.repo | |
let { data: release } = await github.rest.repos.getLatestRelease({ owner, repo }) | |
const latestReleaseCommit = release.body.split('\n')[0].split('@')[1].trim() | |
// Entry in body may not exist, but if it doesn't the should_build_3_4_asan is still correct | |
const latestRelease34ASanCommit = ((release.body.split('\n')[1] ?? "").split('@')[1] ?? "").trim() | |
console.log(` Latest release commit: ${latestReleaseCommit}`) | |
console.log(` Latest ruby commit: ${latestDevCommit}`) | |
console.log(`Latest 3.4-asan release commit: ${latestRelease34ASanCommit}`) | |
console.log(` Latest 3.4-asan: ${latest34ASan}`) | |
core.setOutput('should_build', latestReleaseCommit !== latestDevCommit) | |
core.setOutput('should_build_3_4_asan', latestRelease34ASanCommit !== latest34ASan) | |
core.setOutput('previous_release', release.tag_name) | |
- name: Compute build and reuse matrix | |
uses: actions/github-script@v7 | |
id: matrix | |
with: | |
script: | | |
const osList = ['ubuntu-22.04', 'ubuntu-24.04', 'ubuntu-22.04-arm', 'ubuntu-24.04-arm', 'macos-13', 'macos-14'] | |
const asanHead = { os: 'ubuntu-24.04', name: 'asan' } | |
const asan34 = { os: 'ubuntu-24.04', name: '3.4-asan' } | |
const skipSlow = "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.skip_slow == 'true' }}" | |
const skip34ASan = "${{ steps.check_commit.outputs.should_build_3_4_asan == 'false' }}" | |
const buildMatrix = JSON.stringify( | |
skipSlow === 'false' ? | |
{ os: osList, name: ['head', 'debug'], include: (skip34ASan === 'true' ? [asanHead] : [asanHead, asan34]) } : | |
{ os: osList, name: ['head'] } | |
) | |
core.setOutput('build_matrix', buildMatrix) | |
// Note: GitHub doesn't like having an empty matrix, so make sure at least noop is left | |
const reuseMatrix = JSON.stringify( | |
skipSlow === 'false' ? | |
(skip34ASan === 'true' ? { include: [asan34] } : { os: ['ubuntu-latest'], name: ['noop'] }) : | |
{ os: osList, name: ['debug'], include: [asanHead, asan34] } | |
) | |
core.setOutput('reuse_matrix', reuseMatrix) | |
console.log(`build_matrix: ${buildMatrix}, reuse_matrix: ${reuseMatrix}`) | |
release: | |
name: Create GitHub Release | |
needs: [prepare] | |
if: needs.prepare.outputs.should_build == 'true' | |
runs-on: ubuntu-latest | |
outputs: | |
tag: ${{ steps.tag.outputs.tag }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
if: github.event_name != 'push' | |
- name: Set tag name | |
id: tag | |
run: | | |
if [[ "${{ github.event_name }}" != "push" ]]; then | |
tag=v$(date +%Y%m%d.%H%M%S) | |
else | |
tag=$(basename "${{ github.ref }}") | |
fi | |
echo "tag=$tag" >> $GITHUB_OUTPUT | |
- name: Set release description to built hash | |
run: echo "master ➜ ruby/ruby@${{ needs.prepare.outputs.commit }}" >> release-description.md | |
- name: Set release description to 3.4-asan built hash | |
run: echo "3.4-asan ➜ ruby/ruby@${{ needs.prepare.outputs.commit_3_4_asan }}" >> release-description.md | |
- name: Append note if 3.4-asan build was reused | |
if: ${{ needs.prepare.outputs.should_build_3_4_asan == 'false' }} | |
run: echo "Skipped building and reused build from ${{ needs.prepare.outputs.previous_release }} for 3.4-asan ruby" >> release-description.md | |
- name: Append note if builds were reused | |
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.skip_slow == 'true' }} | |
run: echo "Skipped building and reused builds from ${{ needs.prepare.outputs.previous_release }} for debug and asan rubies" >> release-description.md | |
- name: Create Release | |
env: | |
GH_TOKEN: ${{ github.token }} | |
GH_REPO: ${{ github.repository }} | |
run: | | |
tag="${{ steps.tag.outputs.tag }}" | |
body="ruby/ruby@${{ needs.prepare.outputs.commit }}" | |
gh release create --draft "$tag" --title "$tag" --notes-file release-description.md | |
build: | |
needs: [prepare, release] | |
strategy: | |
fail-fast: false | |
matrix: ${{ fromJson(needs.prepare.outputs.build_matrix) }} | |
runs-on: ${{ matrix.os }} | |
steps: | |
- name: Clone ruby | |
uses: actions/checkout@v4 | |
with: | |
repository: ruby/ruby | |
ref: ${{ matrix.name != '3.4-asan' && needs.prepare.outputs.commit || needs.prepare.outputs.commit_3_4_asan }} | |
- name: Clone ruby-dev-builder | |
uses: actions/checkout@v4 | |
with: | |
path: ruby-dev-builder | |
- name: Set platform | |
id: platform | |
run: | | |
platform=${{ matrix.os }} | |
platform=${platform/macos-13/macos-latest} | |
platform=${platform/macos-14/macos-13-arm64} | |
platform=${platform/%-arm/-arm64} | |
echo "platform=$platform" >> $GITHUB_OUTPUT | |
# Build | |
- name: apt-get update on Ubuntu | |
run: sudo apt-get update | |
if: startsWith(matrix.os, 'ubuntu') | |
- run: sudo apt-get install -y --no-install-recommends ruby bison libyaml-dev libgdbm-dev libreadline-dev libncurses5-dev | |
if: startsWith(matrix.os, 'ubuntu') | |
- run: brew install autoconf automake bison | |
if: startsWith(matrix.os, 'macos') | |
- run: echo "PATH=/usr/local/opt/bison/bin:$PATH" >> $GITHUB_ENV | |
if: startsWith(matrix.os, 'macos') | |
- name: Disable Firewall # Needed for TestSocket#test_udp_server in test-all | |
if: startsWith(matrix.os, 'macos') | |
run: | | |
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off | |
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate | |
# Check | |
- name: Setup BASERUBY | |
uses: ruby/setup-ruby@master | |
with: | |
ruby-version: 3.2 | |
# ENABLE_PATH_CHECK=0: https://github.com/actions/virtual-environments/issues/267 | |
- name: Set configure flags (head) | |
run: | | |
echo "cppflags=-DENABLE_PATH_CHECK=0" >> $GITHUB_ENV | |
if: matrix.name == 'head' | |
- name: Set configure flags (debug) | |
run: | | |
echo "cppflags=-DENABLE_PATH_CHECK=0 -DRUBY_DEBUG=1" >> $GITHUB_ENV | |
echo "optflags=-O3 -fno-inline" >> $GITHUB_ENV | |
if: matrix.name == 'debug' | |
- name: Build dependencies and set configure flags (asan) | |
run: | | |
set -ex | |
# We only test ASAN with Clang. The version of Clang needs to be > 18, which the version in | |
# Ubuntu 24.04's repo is | |
sudo apt-get install -y clang | |
# ASAN builds need to compile (some of) their own dependencies with ASAN enabled, so that it can | |
# catch memory errors caused by passing invalid parameters into other libraries. | |
ASAN_LIB_PREFIX="$HOME/.rubies/ruby-${{ matrix.name }}" | |
./ruby-dev-builder/asan_libs.rb \ | |
--prefix="$ASAN_LIB_PREFIX" \ | |
--cc=clang \ | |
--cflags="-fsanitize=address -fno-omit-frame-pointer -ggdb3 -O3" \ | |
--ldflags="-Wl,-rpath=$ASAN_LIB_PREFIX/lib" \ | |
--makeopts="-j4" | |
# Set Ruby configure flags | |
# Clang > 17 does not work with M:N threading, so we disable it: https://bugs.ruby-lang.org/issues/20243 | |
echo "cppflags=-DENABLE_PATH_CHECK=0 -DRUBY_DEBUG=1 -DVM_CHECK_MODE=1 -DUSE_MN_THREADS=0" >> $GITHUB_ENV | |
echo "optflags=-O3 -fno-omit-frame-pointer" >> $GITHUB_ENV | |
echo "debugflags=-fsanitize=address -ggdb3" >> $GITHUB_ENV | |
echo "CC=clang" >> $GITHUB_ENV | |
# Make sure we link against the ASAN libs we built | |
echo "cflags=-I$ASAN_LIB_PREFIX/include" >> $GITHUB_ENV | |
echo "LDFLAGS=-L$ASAN_LIB_PREFIX/lib -Wl,-rpath=$ASAN_LIB_PREFIX/lib" >> $GITHUB_ENV | |
# Make the test timeouts more generous too (ASAN is slower) | |
echo "RUBY_TEST_TIMEOUT_SCALE=5" >> $GITHUB_ENV | |
echo "SYNTAX_SUGGEST_TIMEOUT=600" >> $GITHUB_ENV | |
if: matrix.name == 'asan' || matrix.name == '3.4-asan' | |
# Build | |
- run: mkdir -p ~/.rubies | |
- run: ./autogen.sh | |
- run: ./configure --prefix=$HOME/.rubies/ruby-${{ matrix.name }} --enable-shared --disable-install-doc --enable-yjit | |
if: startsWith(matrix.os, 'ubuntu') | |
- run: ./configure --prefix=$HOME/.rubies/ruby-${{ matrix.name }} --enable-shared --disable-install-doc --enable-yjit --with-openssl-dir=$(brew --prefix openssl@3) --with-readline-dir=$(brew --prefix readline) | |
if: startsWith(matrix.os, 'macos') | |
- run: make -j4 | |
- run: make install | |
- name: Create archive | |
run: tar czf ruby-${{ matrix.name }}-${{ steps.platform.outputs.platform }}.tar.gz -C ~/.rubies ruby-${{ matrix.name }} | |
# Test | |
- run: make test-spec MSPECOPT=-j | |
- run: make test-all TESTS="-j4" | |
- run: echo "$HOME/.rubies/ruby-${{ matrix.name }}/bin" >> $GITHUB_PATH | |
- uses: actions/checkout@v4 | |
with: | |
path: test_files | |
- name: CLI Test | |
run: ruby test_files/cli_test.rb | |
- run: mv test_files/Gemfile . | |
- run: ruby -e 'pp RbConfig::CONFIG' | |
- run: ruby --yjit -e 'exit RubyVM::YJIT.enabled?' | |
- run: ruby -ropen-uri -e 'puts URI.send(:open, "https://rubygems.org/") { |f| f.read(1024) }' | |
- run: gem install json:2.2.0 --no-document | |
- run: bundle install | |
- run: bundle exec rake --version | |
- name: Subprocess test | |
run: ruby -e 'p RbConfig::CONFIG["cppflags"]; def Warning.warn(s); raise s; end; system RbConfig.ruby, "-e", "p :OK"' | |
- name: Upload Built Ruby | |
env: | |
GH_TOKEN: ${{ github.token }} | |
GH_REPO: ${{ github.repository }} | |
run: gh release upload "${{ needs.release.outputs.tag }}" "ruby-${{ matrix.name }}-${{ steps.platform.outputs.platform }}.tar.gz" | |
reuse-slow: | |
needs: [prepare, release] | |
strategy: | |
fail-fast: false | |
matrix: ${{ fromJson(needs.prepare.outputs.reuse_matrix) }} | |
runs-on: ${{ matrix.os }} | |
env: | |
GH_TOKEN: ${{ github.token }} | |
GH_REPO: ${{ github.repository }} | |
steps: | |
- name: Set platform | |
id: platform | |
run: | | |
platform=${{ matrix.os }} | |
platform=${platform/macos-13/macos-latest} | |
platform=${platform/macos-14/macos-13-arm64} | |
platform=${platform/%-arm/-arm64} | |
echo "platform=$platform" >> $GITHUB_OUTPUT | |
- name: Download binaries from previous release | |
run: gh release download "${{ needs.prepare.outputs.previous_release }}" --pattern "ruby-${{ matrix.name }}-${{ steps.platform.outputs.platform }}.tar.gz" | |
if: matrix.name != 'noop' | |
- name: Re-upload Binaries | |
run: gh release upload "${{ needs.release.outputs.tag }}" "ruby-${{ matrix.name }}-${{ steps.platform.outputs.platform }}.tar.gz" | |
if: matrix.name != 'noop' | |
- name: (Empty step for when reuse is not applied) | |
run: echo "Not reusing binaries. This step is a no-op." # We can't skip the whole job as publish depends on it, but we skip the uploading | |
if: matrix.name == 'noop' | |
publish: | |
name: Publish Release | |
needs: [release, build, reuse-slow] | |
runs-on: ubuntu-latest | |
steps: | |
- name: Publish Release | |
env: | |
GH_TOKEN: ${{ github.token }} | |
GH_REPO: ${{ github.repository }} | |
run: gh release edit "${{ needs.release.outputs.tag }}" --draft=false | |
- uses: eregon/keep-last-n-releases@v1 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
with: | |
n: 3 | |
remove_tags_without_release: true |