diff --git a/.github/actions/create-pr-to-setup-ruby/action.yml b/.github/actions/create-pr-to-setup-ruby/action.yml new file mode 100644 index 00000000..ed5d71f1 --- /dev/null +++ b/.github/actions/create-pr-to-setup-ruby/action.yml @@ -0,0 +1,42 @@ +name: Create PR to setup-ruby +description: Create a Pull Request to ruby/setup-ruby adding the given versions +inputs: + versions: + description: 'engine-version like ruby-1.2.3, separated by comma' + required: true + token: + description: 'GitHub token to create PR' + required: true + title: + description: 'Used for the PR title and commit message' + required: true +runs: + using: "composite" + steps: + - name: Clone setup-ruby + uses: actions/checkout@v4 + with: + repository: ruby/setup-ruby + fetch-depth: 0 + token: ${{ inputs.token }} + + - run: ruby new-versions.rb ${{ inputs.versions }} + shell: bash + - run: ./pre-commit + shell: bash + + - uses: peter-evans/create-pull-request@v3 + id: pr + with: + push-to-fork: ruby-builder-bot/setup-ruby + author: ruby-builder-bot <98265520+ruby-builder-bot@users.noreply.github.com> + committer: ruby-builder-bot <98265520+ruby-builder-bot@users.noreply.github.com> + title: ${{ inputs.title }} + commit-message: ${{ inputs.title }} + body: Automated PR from ruby/ruby-builder + branch: ${{ inputs.versions }} + delete-branch: true + token: ${{ inputs.token }} + - name: PR URL + run: echo "${{ steps.pr.outputs.pull-request-url }}" + shell: bash diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..a45ebcbf --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,241 @@ +name: Build Ruby for GitHub Actions +on: + push: + paths-ignore: + - README.md +jobs: + # Build stable releases + build: + if: true + strategy: + fail-fast: false + matrix: + os: [ ubuntu-22.04, ubuntu-24.04, ubuntu-22.04-arm, ubuntu-24.04-arm, macos-13, macos-14 ] + ruby: [jruby-9.4.13.0] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set tag name + id: info + run: | + tag=toolcache + echo "tag=$tag" >> $GITHUB_OUTPUT + - 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: Set ruby + id: ruby + run: | + ruby=${{ matrix.ruby }} + if [[ "$ruby" == [0-9]* ]]; then + ruby="ruby-$ruby" + fi + echo "ruby=$ruby" >> $GITHUB_OUTPUT + echo "archive=$ruby-${{ steps.platform.outputs.platform }}.tar.gz" >> $GITHUB_OUTPUT + - name: Check if already built + run: '! curl -s -L --head --fail https://github.com/ruby/ruby-builder/releases/download/${{ steps.info.outputs.tag }}/${{ steps.ruby.outputs.archive }}' + + - name: Set NO_DOCUMENT + run: | + if [[ "${{ steps.ruby.outputs.ruby }}" == ruby-1.9* ]]; then + echo "NO_DOCUMENT=--no-ri --no-rdoc" >> $GITHUB_ENV + else + echo "NO_DOCUMENT=--no-document" >> $GITHUB_ENV + fi + + - name: Clone ruby-build + run: git clone https://github.com/rbenv/ruby-build.git + - name: Install ruby-build + run: sudo ./ruby-build/install.sh + + - name: List versions + run: ruby-build --definitions + + # Install packages + - run: sudo apt-get install -y --no-install-recommends libyaml-dev libgdbm-dev libreadline-dev libncurses5-dev + if: startsWith(matrix.os, 'ubuntu') && startsWith(steps.ruby.outputs.ruby, 'ruby-') + - run: sudo apt-get install -y --no-install-recommends libyaml-dev + if: startsWith(matrix.os, 'ubuntu') && startsWith(steps.ruby.outputs.ruby, 'truffleruby') + - run: echo "JAVA_HOME=${JAVA_HOME_21_X64:-${JAVA_HOME_21_arm64:-}}" >> "$GITHUB_ENV" + if: startsWith(steps.ruby.outputs.ruby, 'jruby-') + + - name: Install system ruby for ruby-2.5.2 + run: sudo apt-get install -y --no-install-recommends ruby + if: startsWith(matrix.os, 'ubuntu') && steps.ruby.outputs.ruby == 'ruby-2.5.2' + + - name: Set PREFIX + run: | + ruby="${{ steps.ruby.outputs.ruby }}" + if [[ $ruby == ruby-* ]]; then + # See https://github.com/ruby/setup-ruby/issues/98 + arch=$(node -e 'console.log(os.arch())') + echo "PREFIX=$RUNNER_TOOL_CACHE/Ruby/${ruby#ruby-}/$arch" >> $GITHUB_ENV + else + echo "PREFIX=$HOME/.rubies/$ruby" >> $GITHUB_ENV + fi + - run: rm -rf $PREFIX + + # macOS runners seem to default to -Werror=implicit-function-declaration, but extconf.rb expects it to be not fatal + # See https://bugs.ruby-lang.org/issues/17777 for 2.6.7 + - name: Set warnflags for Ruby <= 2.2 + run: echo "warnflags=-Wno-error=implicit-function-declaration" >> $GITHUB_ENV + if: startsWith(steps.ruby.outputs.ruby, 'ruby-1.9') || startsWith(steps.ruby.outputs.ruby, 'ruby-2.0') || startsWith(steps.ruby.outputs.ruby, 'ruby-2.1') || startsWith(steps.ruby.outputs.ruby, 'ruby-2.2') || steps.ruby.outputs.ruby == 'ruby-2.6.7' + + - name: Set RUBY_CONFIGURE_OPTS + run: echo 'RUBY_CONFIGURE_OPTS=--enable-shared --disable-install-doc' >> $GITHUB_ENV + # https://github.com/rbenv/ruby-build/discussions/1961#discussioncomment-4031745 + - name: Override RUBY_CONFIGURE_OPTS if macos-arm64 ruby-3.1 + run: echo 'RUBY_CONFIGURE_OPTS=--disable-shared --disable-install-doc' >> $GITHUB_ENV + if: matrix.os == 'macos-14' && startsWith(steps.ruby.outputs.ruby, 'ruby-3.1') + + - name: Build Ruby + run: ruby-build --verbose ${{ steps.ruby.outputs.ruby }} $PREFIX + env: + CPPFLAGS: "-DENABLE_PATH_CHECK=0" # https://github.com/actions/virtual-environments/issues/267 + - name: Create archive + run: tar czf ${{ steps.ruby.outputs.archive }} -C $(dirname $PREFIX) $(basename $PREFIX) + - name: Install Bundler if needed + run: | + if [ ! -e $PREFIX/bin/bundle ]; then + export PATH="$PREFIX/bin:$PATH" + gem install bundler -v '~> 1' $NO_DOCUMENT + fi + + - run: echo "$PREFIX/bin" >> $GITHUB_PATH + - run: ruby --version + - run: ruby -ropen-uri -e 'puts URI.send(:open, %{https://rubygems.org/}) { |f| f.read(1024) }' + - name: Install JSON gem + run: gem install json -v '2.2.0' $NO_DOCUMENT + - run: bundle --version + - run: bundle install + - run: bundle exec rake --version + - run: ruby test_subprocess.rb + + - name: Upload Built Ruby + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: gh release upload "toolcache" "${{ steps.ruby.outputs.archive }}" + + buildJRubyWindows: + if: true + strategy: + fail-fast: false + matrix: + os: [ windows-2019 ] + jruby-version: [9.4.13.0] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set tag name + id: info + run: | + tag=toolcache + echo "tag=$tag" >> $GITHUB_OUTPUT + shell: bash + - name: Set platform + id: platform + run: | + platform=${{ matrix.os }} + platform=${platform/windows-2019/windows-latest} + platform=${platform/windows-11-arm/windows-arm64} + echo "platform=$platform" >> $GITHUB_OUTPUT + shell: bash + - name: Set ruby + id: ruby + run: | + ruby=jruby-${{ matrix.jruby-version }} + echo "ruby=$ruby" >> $GITHUB_OUTPUT + echo "archive=$ruby-${{ steps.platform.outputs.platform }}.tar.gz" >> $GITHUB_OUTPUT + shell: bash + - name: Check if already built + run: '! curl -s -L --head --fail https://github.com/ruby/ruby-builder/releases/download/${{ steps.info.outputs.tag }}/${{ steps.ruby.outputs.archive }}' + shell: bash + + - run: echo "JAVA_HOME=${JAVA_HOME_21_X64:-${JAVA_HOME_21_arm64:-}}" >> "$GITHUB_ENV" + shell: bash + + - name: Set PREFIX + run: echo "PREFIX=$HOME/.rubies/${{ steps.ruby.outputs.ruby }}" >> $GITHUB_ENV + shell: bash + - run: curl --fail -L -O 'https://repo1.maven.org/maven2/org/jruby/jruby-dist/${{ matrix.jruby-version }}/jruby-dist-${{ matrix.jruby-version }}-bin.tar.gz' + shell: bash + - name: Build JRuby + shell: bash + run: | + mkdir $(dirname $PREFIX) + tar xf jruby-dist-${{ matrix.jruby-version }}-bin.tar.gz -C $(dirname $PREFIX) + cd $PREFIX/bin + # Copy bash launcher, so 'ruby' works in bash + cp jruby ruby + # Create ruby.bat, so 'ruby' works in pwsh + echo -en "@ECHO OFF\r\n@\"%~dp0jruby.exe\" %*\r\n" > ruby.bat + - name: Create archive + run: tar czf ${{ steps.ruby.outputs.archive }} -C $(dirname $PREFIX) $(basename $PREFIX) + shell: bash + - name: Install Bundler if needed + shell: bash + run: | + if [ ! -e $PREFIX/bin/bundle ]; then + export PATH="$PREFIX/bin:$PATH" + gem install bundler -v '~> 1' --no-document + fi + + - run: echo "$Env:UserProfile\.rubies\${{ steps.ruby.outputs.ruby }}\bin" >> $Env:GITHUB_PATH + - run: echo $Env:PATH + + - run: ruby --version + - 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 --version + - run: bundle install + - run: bundle exec rake --version + + - run: ruby --version + shell: bash + - run: ruby -ropen-uri -e 'puts URI.send(:open, %{https://rubygems.org/}) { |f| f.read(1024) }' + shell: bash + - run: gem install json:2.2.0 --no-document + shell: bash + - run: bundle --version + shell: bash + - run: bundle install + shell: bash + - run: bundle exec rake --version + shell: bash + + - name: Upload Built Ruby + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: gh release upload "toolcache" "${{ steps.ruby.outputs.archive }}" + + + createPullRequest: + name: Create PR to setup-ruby + needs: [build] + if: startsWith(github.event.head_commit.message, 'Build ') + runs-on: ubuntu-latest + steps: + - name: Set versions + id: versions + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + commit_message="$COMMIT_MESSAGE" + if [[ "$commit_message" =~ ^Build\ * ]]; then + versions=${commit_message#* } + echo "versions=$versions" >> $GITHUB_OUTPUT + else + exit 2 + fi + - uses: ruby/ruby-builder/.github/actions/create-pr-to-setup-ruby@master + with: + versions: ${{ steps.versions.outputs.versions }} + title: Add ${{ steps.versions.outputs.versions }} + token: ${{ secrets.CHECK_NEW_RELEASES_TOKEN }} diff --git a/.github/workflows/check-new-releases.yml b/.github/workflows/check-new-releases.yml new file mode 100644 index 00000000..3bfcdf81 --- /dev/null +++ b/.github/workflows/check-new-releases.yml @@ -0,0 +1,34 @@ +name: Check for new Ruby releases in ruby-build +on: + schedule: + - cron: '0 7,19 * * *' + workflow_dispatch: +jobs: + check_new_releases: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.CHECK_NEW_RELEASES_TOKEN }} + - name: Clone setup-ruby + uses: actions/checkout@v4 + with: + repository: ruby/setup-ruby + path: setup-ruby + - name: Clone ruby-build + uses: actions/checkout@v4 + with: + repository: rbenv/ruby-build + path: ruby-build + + - name: Install ruby-build + run: sudo ./ruby-build/install.sh + - name: List versions + run: ruby-build --definitions + + - name: Setup git user + run: | + git config user.name 'ruby-builder-bot' + git config user.email '98265520+ruby-builder-bot@users.noreply.github.com' + - run: ruby check-new-releases.rb diff --git a/.github/workflows/check-new-windows-versions.yml b/.github/workflows/check-new-windows-versions.yml new file mode 100644 index 00000000..52ba2125 --- /dev/null +++ b/.github/workflows/check-new-windows-versions.yml @@ -0,0 +1,33 @@ +name: Check for new CRuby releases on Windows +on: + schedule: + - cron: '0 7,19 * * *' + workflow_dispatch: +jobs: + check_windows_versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.CHECK_NEW_RELEASES_TOKEN }} + - name: Clone setup-ruby + uses: actions/checkout@v4 + with: + repository: ruby/setup-ruby + path: setup-ruby + + - run: ruby generate-windows-versions.rb + working-directory: setup-ruby + + - id: diff + run: git diff --exit-code + working-directory: setup-ruby + continue-on-error: true + + - if: ${{ steps.diff.outcome == 'failure' }} # changed + uses: ruby/ruby-builder/.github/actions/create-pr-to-setup-ruby@master + with: + versions: windows + title: Update CRuby releases on Windows + token: ${{ secrets.CHECK_NEW_RELEASES_TOKEN }} diff --git a/.github/workflows/create-pr.yml b/.github/workflows/create-pr.yml new file mode 100644 index 00000000..ac53ca6c --- /dev/null +++ b/.github/workflows/create-pr.yml @@ -0,0 +1,17 @@ +name: Create a PR for the given versions +on: + workflow_dispatch: + inputs: + versions: + description: 'engine-version like ruby-1.2.3, separated by comma' + required: true +jobs: + createPullRequest: + name: Create PR to setup-ruby + runs-on: ubuntu-latest + steps: + - uses: ruby/ruby-builder/.github/actions/create-pr-to-setup-ruby@master + with: + versions: ${{ github.event.inputs.versions }} + title: Add ${{ github.event.inputs.versions }} + token: ${{ secrets.CHECK_NEW_RELEASES_TOKEN }} diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..0012c312 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +# Used for testing +source 'https://rubygems.org' + +gem "rake" +gem "path" +gem "json", "2.2.0" diff --git a/README.md b/README.md new file mode 100644 index 00000000..67d764ad --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# ruby-builder + +A repository building released rubies to be used in GitHub Actions. + +The action to use these prebuilt rubies is [ruby/setup-ruby](https://github.com/ruby/setup-ruby). + +Please report issues to [ruby/setup-ruby](https://github.com/ruby/setup-ruby). + +[The latest release](https://github.com/ruby/ruby-builder/releases/latest) contains all built Rubies. + +## Building a new Ruby release + +``` +ruby build.rb [ruby|jruby|truffleruby] VERSION +``` + +## Process for new builds + +When making builds a different way, first create a new release and mark it as `prerelease`. + +Then if it might cause breaking changes, open an issue on +[actions/virtual-environments](https://github.com/actions/virtual-environments/issues) with a description of the changes. +This needs to be done 2 weeks prior to using the release. + +Once it's ready, mark the release as non-prerelease and switch to it in `ruby/setup-ruby`. + +## Naming + +Archives are named `$engine-$version-$platform.tar.gz`. + +`platform` is one of: +* `ubuntu-NN.NN`: built on the corresponding GitHub-hosted runner virtual environment +* `macos-latest`: built on `macos-13`, the oldest `macos-amd64` available on GitHub-hosted runners. +* `macos-13-arm64`: built on `macos-14`, the oldest `macos-arm64` available on GitHub-hosted runners. +* `windows-latest`: built on `windows-2019` (does not matter, it's only for repacking a JRuby archive, no actual build) + +The names contain `-latest` for compatibility, even though what `-latest` points to for runners might have changed. diff --git a/build.rb b/build.rb new file mode 100644 index 00000000..f7aa3136 --- /dev/null +++ b/build.rb @@ -0,0 +1,35 @@ +def sh(*command) + puts command.join(' ') + raise "#{command} failed" unless system(*command) +end + +versions = ARGV.dup +versions.dup.each do |engine_version| + engine, version = engine_version.split('-', 2) + if engine == 'truffleruby' + versions << "truffleruby+graalvm-#{version}" + end +end +jruby = versions.any? { |v| v.start_with?('jruby-') } + +file = ".github/workflows/build.yml" +lines = File.readlines(file) + +unix = lines.find { |line| line.include?('ruby: ') } +windows = lines.find { |line| line.include?('jruby-version: ') } +raise unless unix && windows + +unix.sub!(/ruby: .+/, "ruby: [#{versions.join(', ')}]") +if jruby + windows.sub!(/jruby-version: .+/, "jruby-version: [#{versions.map { |v| v.delete_prefix('jruby-') }.join(', ')}]") +end + +if_lines = lines.select { |line| line.match?(/^ if: (true|false)/) } +raise unless if_lines.size == 2 +if_lines[0].sub!(/if: (true|false)/, 'if: true') +if_lines[1].sub!(/if: (true|false)/, "if: #{jruby}") + +File.write(file, lines.join) + +sh 'git', 'add', file +sh 'git', 'commit', '-m', "Build #{versions.join(',')}" diff --git a/check-new-releases.rb b/check-new-releases.rb new file mode 100644 index 00000000..2022adf4 --- /dev/null +++ b/check-new-releases.rb @@ -0,0 +1,56 @@ +require 'json' + +engines_and_min_versions = { + 'ruby' => Gem::Version.new('2.6.0'), + 'jruby' => Gem::Version.new('9.2.9.0'), + 'truffleruby' => Gem::Version.new('21.0.0'), +} + +min_version_for_preview_rc = Gem::Version.new('3.0.0.a') + +def sh(*command) + puts command.join(' ') + raise "#{command} failed" unless system(*command) +end + +all_versions = `ruby-build --definitions` +abort unless $?.success? + +all_versions = all_versions.lines.map(&:chomp) +all_versions_per_engine = Hash.new { |h,k| h[k] = [] } +all_versions.each { |version| + case version + when /^\d/ + all_versions_per_engine['ruby'] << version + when /^(\w+)-(.+)$/ + all_versions_per_engine[$1] << $2 + else + nil + end +} + +all_already_built = JSON.load(File.read('setup-ruby/ruby-builder-versions.json')) + +engines_and_min_versions.each_pair { |engine, min_version| + versions = all_versions_per_engine.fetch(engine) + releases = versions.grep(/^\d+(\.\d+)+$/).select { |version| + Gem::Version.new(version) >= min_version + } + if engine == 'ruby' + releases += versions.grep(/^\d+(\.\d+)+-(preview|rc)(\d+)$/).select { |version| + Gem::Version.new(version) >= min_version_for_preview_rc + } + elsif engine == 'truffleruby' + releases += versions.grep(/^\d+(\.\d+)+-preview(\d+)$/).select { |version| + Gem::Version.new(version) >= min_version + } + end + + already_built = all_already_built.fetch(engine) + new = releases - already_built + unless new.empty? + puts "New releases for #{engine}: #{new}" + sh("ruby", "build.rb", *new.map { |v| "#{engine}-#{v}" }) + sh("git", "push") + end +} diff --git a/fix-rubygems-line.rb b/fix-rubygems-line.rb new file mode 100644 index 00000000..f4d69c42 --- /dev/null +++ b/fix-rubygems-line.rb @@ -0,0 +1,39 @@ +require 'rbconfig' + +bindir = RbConfig::CONFIG["bindir"] + +FIRST_LINE = "#!/bin/sh\n" +RUBY_SHEBANG = %r{^#!/usr/bin/env ruby$} +RUBYGEMS_LINE = /This file was generated by RubyGems/ + +Dir.glob("#{bindir}/*") do |file| + exe = "bin/#{File.basename(file)}" + + if File.binread(file, FIRST_LINE.bytesize) == FIRST_LINE + puts "\nFound load-relative prolog in #{exe}" + contents = File.binread(file) + rubygems_line = contents.lines.index { |line| RUBYGEMS_LINE =~ line } + + if !rubygems_line + puts "No RubyGems line in #{exe}, skipping it" + elsif rubygems_line == 2 + # RubyGems expects RUBYGEMS_LINE to match the 3rd line + # https://github.com/rubygems/rubygems/blob/6d7fe84753/lib/rubygems/installer.rb#L220 + # Otherwise, it will consider the executable to be conflicting and ask whether to override, + # and that results in an error when STDIN is not interactive + else + puts "The RubyGems line in #{exe} is not the 3rd line (but line #{rubygems_line+1}), fixing it" + + index = contents =~ RUBY_SHEBANG + raise "Could not find ruby shebang in:\n#{contents}" unless index + contents = contents[index..-1] + + rubygems_line = contents.lines.index { |line| RUBYGEMS_LINE =~ line } + unless rubygems_line == 2 + raise "The RubyGems line is still not 3rd in #{exe}:\n#{contents}" + end + + File.binwrite(file, contents) + end + end +end diff --git a/generate-copy-yml.rb b/generate-copy-yml.rb new file mode 100644 index 00000000..b018a923 --- /dev/null +++ b/generate-copy-yml.rb @@ -0,0 +1,54 @@ +from_tag = 'builds-bundler1' +to_tag = 'builds-newer-openssl' + +versions = { + "ruby": [ + # "2.3.0", "2.3.1", "2.3.2", "2.3.3", "2.3.4", "2.3.5", "2.3.6", "2.3.7", "2.3.8", + "2.4.0", "2.4.1", "2.4.2", "2.4.3", "2.4.4", "2.4.5", "2.4.6", "2.4.7", "2.4.9", + "2.5.0", "2.5.1", "2.5.2", "2.5.3", "2.5.4", "2.5.5", "2.5.6", "2.5.7", + "2.6.0", "2.6.1", "2.6.2", "2.6.3", "2.6.4", "2.6.5", + "2.7.0" + ], + "jruby": ["9.2.9.0"], + "truffleruby": ["19.3.0", "19.3.1"] +} + +platforms = [ "ubuntu-16.04", "ubuntu-18.04", "macos-latest" ] + +download_url_base = "https://github.com/ruby/ruby-builder/releases/download/#{from_tag}" +upload_url = `curl -s 'https://api.github.com/repos/ruby/ruby-builder/releases/tags/#{to_tag}' | jq -r .upload_url`.strip +p upload_url + +yaml = <