diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c61c7939..fa0546c2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,5 +1,5 @@ # This workflow publishes releases to upstream registries (crates.io -# for Rust crates, rubygems.org for Ruby gems). +# for Rust crates). # # The release process is somewhat convoluted due to interdependencies # between packages (most notably, Ruby gem requires eppo_core to be @@ -12,7 +12,6 @@ # The following names are supported: # - eppo_core@*.*.* to publish eppo_core to crates.io. # - rust-sdk@*.*.* to publish Rust SDK. -# - ruby-sdk@*.*.* to publish Ruby SDK. name: Publish Release on: @@ -74,201 +73,3 @@ jobs: title: "chore: update lockfiles" branch: create-pull-request/ruby-lockfile base: main - - cross_gems: - runs-on: ubuntu-latest - if: ${{ startsWith(github.ref_name, 'ruby-sdk@') }} - defaults: - run: - working-directory: ruby-sdk - strategy: - fail-fast: false - matrix: - _: - - platform: x86_64-linux - - platform: x86_64-linux-musl - - platform: aarch64-linux - - platform: aarch64-linux-musl - - platform: arm-linux - - platform: arm64-darwin - # - platform: x64-mingw32 - # - platform: x86-mingw-ucrt - steps: - - uses: actions/checkout@v6 - - - uses: oxidize-rb/actions/setup-ruby-and-rust@v1 - with: - ruby-version: "3.4" - bundler-cache: true - cargo-cache: true - cargo-vendor: true - cache-version: v3-${{ matrix._.platform }} - - - name: Set vars - id: vars - run: | - echo "rb-sys-version=$(bundle exec ruby -rrb_sys -e 'puts RbSys::VERSION')" >> $GITHUB_OUTPUT - - - uses: "ruby/setup-ruby@v1" - with: - ruby-version: "3.4" - bundler-cache: true - - - name: Configure environment - shell: bash - id: configure - run: | - : Configure environment - echo "RB_SYS_DOCK_UID=$(id -u)" >> $GITHUB_ENV - echo "RB_SYS_DOCK_GID=$(id -g)" >> $GITHUB_ENV - rb_sys_version="$((grep rb_sys Gemfile.lock | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') || (gem info rb_sys --remote | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') || echo "latest")" - rb_sys_dock_cache_dir="$HOME/.cache/rb-sys-$rb_sys_version" - mkdir -p "$rb_sys_dock_cache_dir" - echo "RB_SYS_DOCK_CACHE_DIR=$rb_sys_dock_cache_dir" >> $GITHUB_ENV - echo "rb_sys_version=$rb_sys_version" >> $GITHUB_OUTPUT - - - name: Setup caching - uses: actions/cache@v5 - with: - path: | - ${{ env.RB_SYS_DOCK_CACHE_DIR }} - ruby-sdk/tmp/rb-sys-dock/${{ matrix._.platform }}/target - key: rb-sys-dock-v0-${{ matrix._.platform }}-${{ hashFiles('**/Gemfile.lock', '**/Cargo.lock') }} - save-always: true - restore-keys: | - rb-sys-dock-v0-${{ matrix._.platform }}- - - - name: Install cargo-cache - uses: oxidize-rb/actions/cargo-binstall@v1 - id: install-cargo-cache - with: - crate: cargo-cache - version: 0.8.3 - strategies: quick-install - - - name: Clean the cargo cache - uses: oxidize-rb/actions/post-run@v1 - with: - run: cargo-cache --autoclean - cwd: ruby-sdk - always: true - - - name: Setup rb-sys - shell: bash - working-directory: ruby-sdk - run: | - version="${{ steps.configure.outputs.rb_sys_version }}" - echo "Installing rb_sys@$version" - - if [ "$version" = "latest" ]; then - gem install rb_sys - else - gem install rb_sys -v $version - fi - - - name: Build gem - shell: bash - run: | - : Compile gem - echo "Docker Working Directory: $(pwd)" - set -x - - # We can't parallelize the Ruby versions because they get - # bundled into the same gem. - # - # However, not parallelizing versions is actually helpful - # because Cargo is able to reuse most of compile work - # between versions. - rb-sys-dock \ - --platform ${{ matrix._.platform }} \ - --mount-toolchains \ - --ruby-versions 4.0,3.4,3.3,3.2,3.1,3.0 \ - --build \ - -- ${{ matrix._.rb-sys-dock-setup }} - - - name: Smoke gem install - if: matrix._.platform == 'x86_64-linux' # GitHub actions architecture - run: | - gem install pkg/eppo-server-sdk-*.gem --verbose - script="EppoClient::init(EppoClient::Config.new('placeholder')); EppoClient::Client.instance.shutdown" - ruby -reppo_client -e "$script" 2>&1 || { echo "❌ Failed to run smoke test"; exit 1; } - echo "✅ Successfully installed gem" - env: - EPPO_LOG: "eppo=debug" - - - name: Set outputs - id: set-outputs - shell: bash - run: | - : Set output - echo "gem-path=ruby-sdk/$(find pkg -name '*-${{ matrix._.platform }}.gem')" >> $GITHUB_OUTPUT - - - name: Upload the cross-compiled gems - uses: actions/upload-artifact@v6 - with: - name: cross-gem-${{ matrix._.platform }} - path: ${{ steps.set-outputs.outputs.gem-path }} - - publish_ruby: - name: Publish to RubyGems - runs-on: ubuntu-latest - if: ${{ startsWith(github.ref_name, 'ruby-sdk@') }} - needs: cross_gems - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - uses: actions/setup-node@v6 - with: - node-version: "20" - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: "4.0" - - - name: Download the cross-compiled gems - uses: actions/download-artifact@v7 - with: - pattern: cross-gem-* - merge-multiple: true - - - name: Check Cargo.lock - # Ensure that Cargo.lock matches Cargo.toml - run: cargo update --workspace --locked --verbose - working-directory: ruby-sdk - - - name: Install dependencies - run: bundle install - working-directory: ruby-sdk - - - name: Build - run: bundle exec rake build - working-directory: ruby-sdk - - - name: Move the downloaded artifacts - run: | - mv *.gem ruby-sdk/pkg - - - name: Publish to RubyGems - working-directory: ruby-sdk/pkg/ - env: - RUBYGEMS_API_KEY: "${{ secrets.RUBYGEMS_API_KEY }}" - run: | - mkdir -p $HOME/.gem - touch $HOME/.gem/credentials - chmod 0600 $HOME/.gem/credentials - printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials - ls -l - for i in *.gem; do - if [ -f "$i" ]; then - if ! gem push "$i" >push.out; then - gemerr=$? - sed 's/^/::error:: /' push.out - if ! grep -q "Repushing of gem" push.out; then - exit $gemerr - fi - fi - fi - done diff --git a/.github/workflows/ruby-release.yml b/.github/workflows/ruby-release.yml new file mode 100644 index 00000000..fbb29eaf --- /dev/null +++ b/.github/workflows/ruby-release.yml @@ -0,0 +1,256 @@ +# This workflow handles cross-platform gem building and publishing. +# +# Runs on: +# - Push to main: Release readiness check (builds gems, doesn't publish) +# - Release events: Builds and publishes gems to RubyGems.org +# +# To publish a new Ruby SDK version: +# 1. Ensure eppo_core is published first if ruby-sdk depends on a new version. +# 2. Create a GitHub release with tag format: ruby-sdk@x.x.x (e.g., ruby-sdk@3.7.5) +# 3. The workflow will cross-compile gems for all platforms +# 4. If successful, gems are automatically published to RubyGems.org +name: Ruby SDK - Release + +on: + push: + branches: + - main + paths: + - 'ruby-sdk/**' + - '.github/workflows/ruby-release.yml' + release: + types: [published] + +env: + CARGO_TERM_COLOR: always + +jobs: + # Cross-compile gems for multiple platforms using rb-sys-dock. + # + # This job runs on pushes to main and releases to catch issues + # before we attempt a release (e.g., rb-sys updates, Docker image + # changes, new Ruby versions, platform toolchain issues). + # + # Important: This job always builds against the PUBLISHED eppo_core + # from crates.io, not the local version. This is intentional for + # releases (we want published gems to use published dependencies), + # and a technical limitation for main pushes (rb-sys-dock runs builds + # inside Docker and only mounts the ruby-sdk directory, so we cannot + # use the cargo config override like ruby_test does). + # + # Implications: + # - If ruby-sdk needs an unpublished eppo_core change, this job will + # fail until eppo_core is published. + # - We use continue-on-error for main pushes so these expected failures + # don't block merging. The failure is still visible as a warning. + # - On releases, failures are hard blockers to prevent broken publishes. + # - We skip PRs to avoid noise (ruby_test already validates functionality). + cross_gems: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' || (github.event_name == 'release' && startsWith(github.ref_name, 'ruby-sdk@')) }} + continue-on-error: ${{ github.event_name != 'release' }} + defaults: + run: + working-directory: ruby-sdk + strategy: + fail-fast: false + matrix: + _: + - platform: x86_64-linux + - platform: x86_64-linux-musl + - platform: aarch64-linux + - platform: aarch64-linux-musl + - platform: arm-linux + - platform: arm64-darwin + # - platform: x64-mingw32 + # - platform: x86-mingw-ucrt + steps: + - uses: actions/checkout@v6 + + - uses: oxidize-rb/actions/setup-ruby-and-rust@v1 + with: + ruby-version: "3.4" + bundler-cache: true + cargo-cache: true + cargo-vendor: true + cache-version: v3-${{ matrix._.platform }} + + - name: Set vars + id: vars + run: | + echo "rb-sys-version=$(bundle exec ruby -rrb_sys -e 'puts RbSys::VERSION')" >> $GITHUB_OUTPUT + + - uses: "ruby/setup-ruby@v1" + with: + ruby-version: "3.4" + bundler-cache: true + + - name: Configure environment + shell: bash + id: configure + run: | + : Configure environment + echo "RB_SYS_DOCK_UID=$(id -u)" >> $GITHUB_ENV + echo "RB_SYS_DOCK_GID=$(id -g)" >> $GITHUB_ENV + rb_sys_version="$((grep rb_sys Gemfile.lock | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') || (gem info rb_sys --remote | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') || echo "latest")" + rb_sys_dock_cache_dir="$HOME/.cache/rb-sys-$rb_sys_version" + mkdir -p "$rb_sys_dock_cache_dir" + echo "RB_SYS_DOCK_CACHE_DIR=$rb_sys_dock_cache_dir" >> $GITHUB_ENV + echo "rb_sys_version=$rb_sys_version" >> $GITHUB_OUTPUT + + - name: Setup caching + uses: actions/cache@v5 + with: + path: | + ${{ env.RB_SYS_DOCK_CACHE_DIR }} + ruby-sdk/tmp/rb-sys-dock/${{ matrix._.platform }}/target + key: rb-sys-dock-v0-${{ matrix._.platform }}-${{ hashFiles('**/Gemfile.lock', '**/Cargo.lock') }} + save-always: true + restore-keys: | + rb-sys-dock-v0-${{ matrix._.platform }}- + + - name: Install cargo-cache + uses: oxidize-rb/actions/cargo-binstall@v1 + id: install-cargo-cache + with: + crate: cargo-cache + version: 0.8.3 + strategies: quick-install + + - name: Clean the cargo cache + uses: oxidize-rb/actions/post-run@v1 + with: + run: cargo-cache --autoclean + cwd: ruby-sdk + always: true + + - name: Setup rb-sys + shell: bash + working-directory: ruby-sdk + run: | + version="${{ steps.configure.outputs.rb_sys_version }}" + echo "Installing rb_sys@$version" + + if [ "$version" = "latest" ]; then + gem install rb_sys + else + gem install rb_sys -v $version + fi + + - name: Build gem + shell: bash + run: | + : Compile gem + echo "Docker Working Directory: $(pwd)" + set -x + + # We can't parallelize the Ruby versions because they get + # bundled into the same gem. + # + # However, not parallelizing versions is actually helpful + # because Cargo is able to reuse most of compile work + # between versions. + rb-sys-dock \ + --platform ${{ matrix._.platform }} \ + --mount-toolchains \ + --ruby-versions 4.0,3.4,3.3,3.2,3.1,3.0 \ + --build \ + -- ${{ matrix._.rb-sys-dock-setup }} + + - name: Smoke gem install + if: matrix._.platform == 'x86_64-linux' # GitHub actions architecture + run: | + gem install pkg/eppo-server-sdk-*.gem --verbose + script="EppoClient::init(EppoClient::Config.new('placeholder')); EppoClient::Client.instance.shutdown" + ruby -reppo_client -e "$script" 2>&1 || { echo "❌ Failed to run smoke test"; exit 1; } + echo "✅ Successfully installed gem" + env: + EPPO_LOG: "eppo=debug" + + - name: Set outputs + id: set-outputs + shell: bash + run: | + : Set output + echo "gem-path=ruby-sdk/$(find pkg -name '*-${{ matrix._.platform }}.gem')" >> $GITHUB_OUTPUT + + - name: Upload the cross-compiled gems + uses: actions/upload-artifact@v6 + with: + name: cross-gem-${{ matrix._.platform }} + path: ${{ steps.set-outputs.outputs.gem-path }} + + # Publish gems to RubyGems.org. + # + # This job is protected by TWO conditions: + # 1. github.event_name == 'release' - must be a GitHub release event + # 2. startsWith(github.ref_name, 'ruby-sdk@') - tag must match pattern ruby-sdk@x.x.x + # + # This prevents accidental publishing from: + # - Pushes to main (builds gems but doesn't publish) + # - Manual workflow dispatch + # - Releases for other packages (eppo_core@x.x.x, rust-sdk@x.x.x) + publish_ruby: + name: Publish to RubyGems + runs-on: ubuntu-latest + if: ${{ github.event_name == 'release' && startsWith(github.ref_name, 'ruby-sdk@') }} + needs: cross_gems + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - uses: actions/setup-node@v6 + with: + node-version: "20" + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "4.0" + + - name: Download the cross-compiled gems + uses: actions/download-artifact@v7 + with: + pattern: cross-gem-* + merge-multiple: true + + - name: Check Cargo.lock + # Ensure that Cargo.lock matches Cargo.toml + run: cargo update --workspace --locked --verbose + working-directory: ruby-sdk + + - name: Install dependencies + run: bundle install + working-directory: ruby-sdk + + - name: Build + run: bundle exec rake build + working-directory: ruby-sdk + + - name: Move the downloaded artifacts + run: | + mv *.gem ruby-sdk/pkg + + - name: Publish to RubyGems + working-directory: ruby-sdk/pkg/ + env: + RUBYGEMS_API_KEY: "${{ secrets.RUBYGEMS_API_KEY }}" + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials + ls -l + for i in *.gem; do + if [ -f "$i" ]; then + if ! gem push "$i" >push.out; then + gemerr=$? + sed 's/^/::error:: /' push.out + if ! grep -q "Repushing of gem" push.out; then + exit $gemerr + fi + fi + fi + done + diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 79df9a67..389c7b4d 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -34,7 +34,7 @@ jobs: matrix: os: [ubuntu, macos] # NOTE: these versions are only affecting CI check for - # PRs. For prebuilt libraries in release, see publish.yml. + # PRs. For prebuilt libraries in release, see ruby-release.yml. ruby: - "3.0" - "3.1"