Skip to content
Merged
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
284 changes: 277 additions & 7 deletions .github/workflows/current.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ jobs:
env:
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
# Current represents what is available today: require the published
# Rust host gem, which fails until structuredmerge_host_prototype is released.
STRUCTUREDMERGE_RUST_HOST_PUBLISHED: true
run: |
bundle config set frozen false
for attempt in 1 2 3; do
Expand All @@ -168,8 +171,261 @@ jobs:
K_JEM_TEMPLATING: true
K_SOUP_COV_DO: false
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_RUST_HOST_PUBLISHED: true
run: timeout 12m bundle exec kettle-test

rust-host:
name: Rust host @ HEAD
needs: detect
if: ${{ needs.detect.outputs.count != '0' && needs.detect.outputs.release_mode == '' }}
runs-on: ubuntu-latest
steps:
- name: Clone structuredmerge-rust HEAD
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://github.com/structuredmerge/structuredmerge-rust.git "${RUNNER_TEMP}/structuredmerge-rust"
echo "structuredmerge-rust HEAD: $(git -C "${RUNNER_TEMP}/structuredmerge-rust" rev-parse HEAD)"
- name: Setup Rust
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
with:
toolchain: stable
# Build exactly as structuredmerge-rust's release-ruby-host.yml does (including
# CARGO_FEATURE_LINK_RUBY), so Deps @ HEAD tests the gem end users will install.
- name: Setup Ruby
uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314.0
with:
ruby-version: "4.0"
bundler-cache: false
- name: Install Rust host package dependencies
shell: bash
working-directory: ${{ runner.temp }}/structuredmerge-rust/packages/ruby
run: bundle install --jobs 4 --retry 8
- name: Install Alef
shell: bash
working-directory: ${{ runner.temp }}/structuredmerge-rust
run: |
cargo install --git https://github.com/structuredmerge/alef.git \
--rev "$(cat workspace-scripts/alef-source-revision)" --locked
- name: Build platform gem
shell: bash
working-directory: ${{ runner.temp }}/structuredmerge-rust
env:
CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu
CARGO_FEATURE_LINK_RUBY: "1"
PLATFORM: x86_64-linux
RUST_TARGET: x86_64-unknown-linux-gnu
TARGET: x86_64-unknown-linux-gnu
run: |
set -euo pipefail
mkdir -p dist
alef publish build --lang ruby
alef publish package --lang ruby --target "${TARGET}" --output dist
test "$(find dist -maxdepth 1 -name "*-${PLATFORM}.gem" -print | wc -l)" -eq 1
- name: Upload Rust host gem
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: structuredmerge-host-prototype-head
path: ${{ runner.temp }}/structuredmerge-rust/dist/*-x86_64-linux.gem
if-no-files-found: error
retention-days: 7

dep-heads:
name: Deps @ HEAD (${{ matrix.gem }})
needs:
- detect
- rust-host
if: ${{ needs.detect.outputs.count != '0' && needs.detect.outputs.release_mode == '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.detect.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clone sibling fixtures repo
shell: bash
run: |
set -euo pipefail
fixtures_repository="$(ruby -ryaml -e 'puts YAML.load_file(".structuredmerge/fixtures.lock").fetch("repository")')"
fixtures_tag="$(ruby -ryaml -e 'puts YAML.load_file(".structuredmerge/fixtures.lock").fetch("tag")')"
fixtures_revision="$(ruby -ryaml -e 'puts YAML.load_file(".structuredmerge/fixtures.lock").fetch("revision")')"
git clone --filter=blob:none --no-checkout "${fixtures_repository}" ../fixtures
git -C ../fixtures fetch --depth 1 origin "refs/tags/${fixtures_tag}:refs/tags/${fixtures_tag}"
git -C ../fixtures checkout --detach "${fixtures_revision}"
test "$(git -C ../fixtures rev-parse HEAD)" = "${fixtures_revision}"
test "$(git -C ../fixtures rev-parse "refs/tags/${fixtures_tag}^{commit}")" = "${fixtures_revision}"
- name: Download Rust host gem
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: structuredmerge-host-prototype-head
path: ${{ runner.temp }}/structuredmerge-host
- name: Setup Ruby
uses: appraisal-rb/setup-ruby-flash@925395edf973d2dc0a629919f407f3547a03d4b5 # v2.1
with:
ruby-version: ruby
bundler-cache: false
working-directory: ${{ matrix.gem }}
pre-bundle-gems: |
nomono --source https://gem.coop --source https://rubygems.org
- name: Unpack Rust host gem
shell: bash
run: |
set -euo pipefail
host_root="${RUNNER_TEMP}/structuredmerge-host"
gem_file="$(find "${host_root}" -maxdepth 1 -name '*-x86_64-linux.gem' -print -quit)"
test -n "${gem_file}"
gem unpack "${gem_file}" --target "${host_root}/unpacked"
host_dir="$(find "${host_root}/unpacked" -mindepth 1 -maxdepth 1 -type d -print -quit)"
gem specification "${gem_file}" --ruby > "${host_dir}/structuredmerge_host_prototype.gemspec"
echo "STRUCTUREDMERGE_RUST_HOST_DIR=${host_dir}" >> "${GITHUB_ENV}"
- name: Compose dep-heads bundle
shell: bash
working-directory: ${{ matrix.gem }}
run: |
set -euo pipefail
cat > dep_heads.gemfile <<'RUBY'
# frozen_string_literal: true

# CI-only dep-heads bundle: the Rust host built from structuredmerge-rust HEAD,
# this gem's Gemfile, and the HEAD versions of its runtime dependencies.
# Like the dep-heads appraisal, HEAD declarations replace the Gemfile's
# released declarations of the same gems instead of conflicting with them.
runtime_heads = File.expand_path('gemfiles/modular/runtime_heads.gemfile', __dir__)
head_gem_names = Bundler::Dsl.new.tap { |dsl| dsl.eval_gemfile(runtime_heads) }.dependencies.map(&:name)

gem 'structuredmerge_host_prototype', path: ENV.fetch('STRUCTUREDMERGE_RUST_HOST_DIR')
eval_gemfile File.expand_path('Gemfile', __dir__)
instance_variable_get(:@dependencies).reject! { |dependency| head_gem_names.include?(dependency.name) }
eval_gemfile runtime_heads
RUBY
- name: Install dependencies
working-directory: ${{ matrix.gem }}
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gem }}/dep_heads.gemfile
K_JEM_TEMPLATING: true
# Bundle for spec subprocesses (e.g. ast-merge-git Git drivers) that reset BUNDLE_GEMFILE.
KETTLE_FAMILY_BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gem }}/dep_heads.gemfile
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
run: |
bundle config set frozen false
for attempt in 1 2 3; do
if bundle install --jobs 4 --retry 8; then
exit 0
fi

if [[ "${attempt}" -lt 3 ]]; then
sleep "$((attempt * 5))"
fi
done

exit 1
- name: Verify Rust host loads
working-directory: ${{ matrix.gem }}
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gem }}/dep_heads.gemfile
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
shell: bash
run: |
set -euo pipefail
# Gems that declare the host must be able to load the release-built host on this
# standard rv Ruby; failing here keeps host specs from silently skipping.
if ! grep -q 'structuredmerge_host_prototype' Gemfile; then
echo "This gem does not declare structuredmerge_host_prototype; nothing to verify."
exit 0
fi
bundle exec ruby -e 'require "structuredmerge_host_prototype"; puts "structuredmerge_host_prototype #{StructuredmergeHostPrototype::VERSION} loaded"'
- name: Run checks
working-directory: ${{ matrix.gem }}
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gem }}/dep_heads.gemfile
K_JEM_TEMPLATING: true
K_SOUP_COV_DO: false
KETTLE_FAMILY_BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gem }}/dep_heads.gemfile
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
run: timeout 12m bundle exec kettle-test

coverage:
name: Coverage (${{ matrix.gem }})
needs: detect
if: ${{ needs.detect.outputs.count != '0' && needs.detect.outputs.release_mode == '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.detect.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clone sibling fixtures repo
shell: bash
run: |
set -euo pipefail
fixtures_repository="$(ruby -ryaml -e 'puts YAML.load_file(".structuredmerge/fixtures.lock").fetch("repository")')"
fixtures_tag="$(ruby -ryaml -e 'puts YAML.load_file(".structuredmerge/fixtures.lock").fetch("tag")')"
fixtures_revision="$(ruby -ryaml -e 'puts YAML.load_file(".structuredmerge/fixtures.lock").fetch("revision")')"
git clone --filter=blob:none --no-checkout "${fixtures_repository}" ../fixtures
git -C ../fixtures fetch --depth 1 origin "refs/tags/${fixtures_tag}:refs/tags/${fixtures_tag}"
git -C ../fixtures checkout --detach "${fixtures_revision}"
test "$(git -C ../fixtures rev-parse HEAD)" = "${fixtures_revision}"
test "$(git -C ../fixtures rev-parse "refs/tags/${fixtures_tag}^{commit}")" = "${fixtures_revision}"
- name: Setup Ruby
uses: appraisal-rb/setup-ruby-flash@925395edf973d2dc0a629919f407f3547a03d4b5 # v2.1
with:
ruby-version: ruby
bundler-cache: false
working-directory: ${{ matrix.gem }}
pre-bundle-gems: |
nomono --source https://gem.coop --source https://rubygems.org
- name: Install dependencies
working-directory: ${{ matrix.gem }}
env:
K_JEM_TEMPLATING: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
# Coverage runs against the published Rust host gem, for maximum coverage once it
# is released; until then this fails like current.
STRUCTUREDMERGE_RUST_HOST_PUBLISHED: true
run: |
bundle config set frozen false
for attempt in 1 2 3; do
if bundle install --jobs 4 --retry 8; then
exit 0
fi

if [[ "${attempt}" -lt 3 ]]; then
sleep "$((attempt * 5))"
fi
done

exit 1
- name: Load coverage thresholds
shell: bash
working-directory: ${{ matrix.gem }}
run: |
set -euo pipefail
for key in K_SOUP_COV_MIN_BRANCH K_SOUP_COV_MIN_LINE K_SOUP_COV_MIN_HARD; do
value="$(sed -nE "s/^${key}[[:space:]]*=[[:space:]]*\"([^\"]*)\".*/\\1/p" mise.toml 2>/dev/null | head -1)"
if [[ -n "${value}" ]]; then
echo "${key}=${value}" >> "${GITHUB_ENV}"
fi
done
- name: Run checks with coverage
working-directory: ${{ matrix.gem }}
env:
K_JEM_TEMPLATING: true
K_SOUP_COV_COMMAND_NAME: Test Coverage
K_SOUP_COV_DO: true
K_SOUP_COV_FORMATTERS: xml,rcov,lcov,tty
K_SOUP_COV_MULTI_FORMATTERS: true
STRUCTUREDMERGE_DEV: ${{ github.workspace }}/gems
STRUCTUREDMERGE_RUST_HOST_PUBLISHED: true
run: timeout 20m bundle exec kettle-test
- name: Verify coverage reports
working-directory: ${{ matrix.gem }}
run: |
test -s coverage/lcov.info
test -s coverage/coverage.xml

family-suite:
name: StructuredMerge family suite
needs: detect
Expand Down Expand Up @@ -424,6 +680,9 @@ jobs:
needs:
- detect
- gem-suite
- rust-host
- dep-heads
- coverage
- family-suite
- benchmark-dev
- rbs
Expand All @@ -438,6 +697,9 @@ jobs:
RELEASE_MODE: ${{ needs.detect.outputs.release_mode }}
DETECT_RESULT: ${{ needs.detect.result }}
GEM_SUITE_RESULT: ${{ needs.gem-suite.result }}
RUST_HOST_RESULT: ${{ needs.rust-host.result }}
DEP_HEADS_RESULT: ${{ needs.dep-heads.result }}
COVERAGE_RESULT: ${{ needs.coverage.result }}
FAMILY_SUITE_RESULT: ${{ needs.family-suite.result }}
BENCHMARK_DEV_RESULT: ${{ needs.benchmark-dev.result }}
RBS_RESULT: ${{ needs.rbs.result }}
Expand All @@ -447,6 +709,9 @@ jobs:
echo "Selected gem suites: ${SELECTED_GEMS}"
echo "Detect result: ${DETECT_RESULT}"
echo "Gem suite result: ${GEM_SUITE_RESULT}"
echo "Rust host @ HEAD result: ${RUST_HOST_RESULT}"
echo "Deps @ HEAD result: ${DEP_HEADS_RESULT}"
echo "Coverage result: ${COVERAGE_RESULT}"
echo "Family suite result: ${FAMILY_SUITE_RESULT}"
echo "Dev benchmark result: ${BENCHMARK_DEV_RESULT}"
echo "RBS result: ${RBS_RESULT}"
Expand All @@ -462,6 +727,9 @@ jobs:

if [[ "${FAMILY_SUITE_RESULT}" == "success" ]]; then
[[ "${GEM_SUITE_RESULT}" == "skipped" ]] || exit 1
[[ "${RUST_HOST_RESULT}" == "skipped" ]] || exit 1
[[ "${DEP_HEADS_RESULT}" == "skipped" ]] || exit 1
[[ "${COVERAGE_RESULT}" == "skipped" ]] || exit 1
[[ "${RBS_RESULT}" == "skipped" ]] || exit 1
[[ "${BENCHMARK_DEV_RESULT}" == "skipped" ]] || exit 1
[[ "${TSLP_COLD_CACHE_RESULT}" == "skipped" ]] || exit 1
Expand All @@ -472,10 +740,12 @@ jobs:
[[ "${result}" == "success" ]] || exit 1
done

case "${GEM_SUITE_RESULT}" in
success|skipped)
;;
*)
exit 1
;;
esac
for result in "${GEM_SUITE_RESULT}" "${RUST_HOST_RESULT}" "${DEP_HEADS_RESULT}" "${COVERAGE_RESULT}"; do
case "${result}" in
success|skipped)
;;
*)
exit 1
;;
esac
done
6 changes: 6 additions & 0 deletions gems/ast-crispr/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ if !structuredmerge_rust_dev.empty? && !%w[false 0 no off].include?(structuredme
gem 'structuredmerge_host_prototype', path: File.join(structuredmerge_rust_dev, 'packages/ruby')
end

# CI current and coverage jobs require the published Rust host gem; this fails until it is released.
if ENV.fetch('STRUCTUREDMERGE_RUST_HOST_PUBLISHED', 'false').casecmp('true').zero? &&
instance_variable_get(:@dependencies).none? { |dependency| dependency.name == 'structuredmerge_host_prototype' }
gem 'structuredmerge_host_prototype', '~> 0.2'
end

# Local workspace dependency wiring for *_local.gemfile overrides
gem 'nomono', '~> 1.1', '>= 1.1.5', require: false # ruby >= 3.2.0

Expand Down
6 changes: 6 additions & 0 deletions gems/ast-merge-git/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ if !structuredmerge_rust_dev.empty? && !%w[false 0 no off].include?(structuredme
gem 'structuredmerge_host_prototype', path: File.join(structuredmerge_rust_dev, 'packages/ruby')
end

# CI current and coverage jobs require the published Rust host gem; this fails until it is released.
if ENV.fetch('STRUCTUREDMERGE_RUST_HOST_PUBLISHED', 'false').casecmp('true').zero? &&
instance_variable_get(:@dependencies).none? { |dependency| dependency.name == 'structuredmerge_host_prototype' }
gem 'structuredmerge_host_prototype', '~> 0.2'
end

git_source(:codeberg) { |repo_name| "https://codeberg.org/#{repo_name}" }
git_source(:gitlab) { |repo_name| "https://gitlab.com/#{repo_name}" }

Expand Down
2 changes: 2 additions & 0 deletions gems/ast-merge-git/spec/real_git_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@ def text_git_baseline(base:, ours:, theirs:)
}
].each do |provider|
it "runs the explicit #{provider.fetch(:label)} selector through the installed Git-driver path" do
skip 'compiled Rust host is unavailable' unless Ast::Merge::Git::RustHostProvider.available?

base = provider.fetch(:source)
ours = provider.fetch(:ours)
theirs = provider.fetch(:theirs)
Expand Down
6 changes: 6 additions & 0 deletions gems/ast-template/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ if !structuredmerge_rust_dev.empty? && !%w[false 0 no off].include?(structuredme
gem 'structuredmerge_host_prototype', path: File.join(structuredmerge_rust_dev, 'packages/ruby')
end

# CI current and coverage jobs require the published Rust host gem; this fails until it is released.
if ENV.fetch('STRUCTUREDMERGE_RUST_HOST_PUBLISHED', 'false').casecmp('true').zero? &&
instance_variable_get(:@dependencies).none? { |dependency| dependency.name == 'structuredmerge_host_prototype' }
gem 'structuredmerge_host_prototype', '~> 0.2'
end

# The default adapter registry is exercised by the release suite with local
# sibling wiring disabled, so its released adapter dependencies must be present
# in the ordinary development bundle as well as in the templating bundle.
Expand Down
Loading
Loading