Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/scripts/deps_gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
#
# Dependency gate decision (see .github/workflows/_deps_gate.yml).
#
# Runs only when dependency files changed. Probes every base gemfile against its
# lockfile and emits proceed=true|false to "$GITHUB_OUTPUT":
# true - every lockfile is consistent with its gemfile; dependent (expensive)
# jobs may run.
# false - at least one lockfile is stale; dependent jobs should skip and wait
# for the lock-dependency bot to regenerate and commit the lockfiles,
# which re-triggers CI on a fresh commit.
#
# All base gemfiles are probed (not just one) so that a change isolated to a
# single Ruby's gemfile, or a broad change (gemspec, appraisal) that affects
# every lockfile, is caught.
set -uo pipefail

export BUNDLE_FROZEN=true
proceed=true

for gemfile in gemfiles/ruby-*.gemfile; do
rc=0
BUNDLE_GEMFILE="$gemfile" ruby .github/scripts/deps_gate_probe.rb || rc=$?
case "$rc" in
0)
;;
2)
echo "::notice::Stale lockfile for ${gemfile}; waiting for the lock-dependency bot."
proceed=false
;;
*)
echo "::error::deps-gate probe errored for ${gemfile} (exit ${rc})."
exit 1
;;
esac
done

echo "Dependency files changed; all lockfiles consistent: ${proceed}."
echo "proceed=${proceed}" >> "$GITHUB_OUTPUT"
28 changes: 28 additions & 0 deletions .github/scripts/deps_gate_probe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

# Dependency gate probe (see .github/workflows/_deps_gate.yml).
#
# Checks whether the gemfile in BUNDLE_GEMFILE is consistent with its lockfile,
# WITHOUT installing gems or touching the network: it only compares the gemfile's
# declared dependencies against the lockfile. Must run under BUNDLE_FROZEN=true,
# which is what makes Bundler raise on a divergence instead of silently updating
# the lockfile.
#
# Exit codes:
# 0 - consistent: the lockfile matches the gemfile.
# 2 - stale: the gemfile changed but the lockfile has not been regenerated yet.
# 1 - probe error (e.g. a future Bundler renamed the API): surfaced loudly so it
# gets fixed, rather than silently gating out every dependency PR.
require "bundler"

Bundler.ui.level = "error"

begin
Bundler.definition.ensure_equivalent_gemfile_and_lockfile
exit 0
rescue Bundler::ProductionError, Bundler::GemfileError
exit 2
rescue => e
warn "deps-gate: unexpected probe failure for #{ENV["BUNDLE_GEMFILE"]}: #{e.class}: #{e.message}"
exit 1
end
57 changes: 57 additions & 0 deletions .github/workflows/_deps_gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Dependency Gate

# Single source of truth for "may dependent (expensive) workflows run on this
# commit yet?". Adopted by workflows whose jobs would otherwise run, and fail, on
# a dependency PR before the lock-dependency bot has regenerated the lockfiles.
#
# A dependency PR triggers CI twice: once on the contributor's commit (gemfiles
# changed, lockfiles still stale) and again on the bot's lock commit. This gate
# lets expensive jobs skip the first, stale run and only run once the lockfiles
# are consistent. It cannot reduce the two runs to one — the bot commit always
# re-triggers CI — but it keeps the first run cheap.

on: # yamllint disable-line rule:truthy
workflow_call:
outputs:
proceed:
description: >-
'true' when dependent jobs may run: either no dependency files changed,
or every lockfile is consistent with its gemfile. 'false' when a
dependency changed but at least one lockfile is still stale (the
lock-dependency bot has not regenerated and committed it yet).
value: ${{ jobs.changes.outputs.dependencies != 'true' || jobs.gate.outputs.proceed == 'true' }}

# Default permissions for all jobs
permissions: {}

jobs:
changes:
name: detect dependency changes
runs-on: ubuntu-24.04
outputs:
dependencies: ${{ steps.changes.outputs.dependencies }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: .github/dependency_filters.yml

gate:
name: probe lockfile consistency
needs: changes
if: ${{ needs.changes.outputs.dependencies == 'true' }}
runs-on: ubuntu-24.04
container:
image: ghcr.io/datadog/images-rb/engines/ruby:4.0-gnu-gcc
outputs:
proceed: ${{ steps.decide.outputs.proceed }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Decide whether dependent jobs may proceed
id: decide
run: bash .github/scripts/deps_gate.sh
4 changes: 4 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on: # yamllint disable-line rule:truthy
# The branches below must be a subset of the branches above
branches: [master]

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}"

# Default permissions for all jobs
permissions: {}

Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ on: # yamllint disable-line rule:truthy
branches:
- master

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}"

# Default permissions for all jobs
permissions: {}

jobs:
gate:
uses: ./.github/workflows/_deps_gate.yml

test:
needs: gate
if: ${{ needs.gate.outputs.proceed == 'true' }}
strategy:
fail-fast: false
matrix:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/system-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ on: # yamllint disable-line rule:truthy
schedule:
- cron: "00 04 * * 0-6"

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}"

# Default permissions for all jobs
permissions: {}

Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/test-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ on: # yamllint disable-line rule:truthy
branches:
- master

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}"

# Default permissions for all jobs
permissions: {}

jobs:
gate:
uses: ./.github/workflows/_deps_gate.yml

test-macos:
needs: gate
if: ${{ needs.gate.outputs.proceed == 'true' }}
strategy:
fail-fast: false
matrix:
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test-memory-leaks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on: # yamllint disable-line rule:truthy
branches:
- master

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}"

# Default permissions for all jobs
permissions: {}

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test-yjit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on: # yamllint disable-line rule:truthy
branches:
- master

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: "${{ github.ref != 'refs/heads/master' }}"

# Default permissions for all jobs
permissions: {}

Expand Down
Loading