Skip to content

CRuby Dev Builds

CRuby Dev Builds #1959

Workflow file for this run

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