diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index 80fd19d78..76c549d39 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -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 @@ -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 @@ -424,6 +680,9 @@ jobs: needs: - detect - gem-suite + - rust-host + - dep-heads + - coverage - family-suite - benchmark-dev - rbs @@ -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 }} @@ -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}" @@ -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 @@ -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 diff --git a/gems/ast-crispr/Gemfile b/gems/ast-crispr/Gemfile index 94a0c98af..b467db17b 100644 --- a/gems/ast-crispr/Gemfile +++ b/gems/ast-crispr/Gemfile @@ -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 diff --git a/gems/ast-merge-git/Gemfile b/gems/ast-merge-git/Gemfile index 44c1b6197..cdec3f5c8 100644 --- a/gems/ast-merge-git/Gemfile +++ b/gems/ast-merge-git/Gemfile @@ -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}" } diff --git a/gems/ast-merge-git/spec/real_git_integration_spec.rb b/gems/ast-merge-git/spec/real_git_integration_spec.rb index dd312d208..a1382358b 100644 --- a/gems/ast-merge-git/spec/real_git_integration_spec.rb +++ b/gems/ast-merge-git/spec/real_git_integration_spec.rb @@ -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) diff --git a/gems/ast-template/Gemfile b/gems/ast-template/Gemfile index 7009c936b..3dcb46b5a 100644 --- a/gems/ast-template/Gemfile +++ b/gems/ast-template/Gemfile @@ -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. diff --git a/gems/ast-template/spec/integration/rust_host_provider_spec.rb b/gems/ast-template/spec/integration/rust_host_provider_spec.rb index 21a32cad1..fce221b32 100644 --- a/gems/ast-template/spec/integration/rust_host_provider_spec.rb +++ b/gems/ast-template/spec/integration/rust_host_provider_spec.rb @@ -3,6 +3,12 @@ RSpec.describe Ast::Template::RustHostProvider do describe '.available?' do it 'reports the generated host bridge when loaded' do + begin + require 'structuredmerge_host_prototype' + rescue LoadError + skip 'compiled Rust host is unavailable' + end + expect(described_class).to be_available end end @@ -15,74 +21,78 @@ ) end - it 'reports missing roots through the portable options contract' do - report = described_class.new.report( - kind: 'options', - options: { - mode: 'plan', - template_root: '', - destination_root: '', - context: {}, - default_strategy: 'merge', - overrides: [], - replacements: {}, - allowed_families: nil - } - ) - - expect(report[:request_kind]).to eq('options') - expect(report[:ready]).to be(false) - expect(report[:diagnostics].map { |entry| entry['reason'] }).to eq( - %w[missing_destination_root missing_template_root] - ) - end - - it 'reports an unknown profile through the portable profile contract' do - report = described_class.new.report( - kind: 'profile', - profile_name: 'missing', - profiles: {}, - options: { - mode: 'plan', - template_root: '/templates', - destination_root: '/destination', - context: {}, - default_strategy: 'merge', - overrides: [], - replacements: {}, - allowed_families: nil - } - ) + context 'with the compiled Rust host' do + before { skip 'compiled Rust host is unavailable' unless described_class.available? } - expect(report[:request_kind]).to eq('profile') - expect(report[:ready]).to be(false) - expect(report[:diagnostics].map { |entry| entry['reason'] }).to include('missing_profile') - end + it 'reports missing roots through the portable options contract' do + report = described_class.new.report( + kind: 'options', + options: { + mode: 'plan', + template_root: '', + destination_root: '', + context: {}, + default_strategy: 'merge', + overrides: [], + replacements: {}, + allowed_families: nil + } + ) - it 'returns a read-only Rust template plan without claiming filesystem execution' do - Dir.mktmpdir('ast-template-rust-plan') do |root| - template_root = File.join(root, 'templates') - destination_root = File.join(root, 'destination') - FileUtils.mkdir_p(template_root) - FileUtils.mkdir_p(destination_root) - File.write(File.join(template_root, 'README.md'), "# {{PACKAGE_NAME}}\n") + expect(report[:request_kind]).to eq('options') + expect(report[:ready]).to be(false) + expect(report[:diagnostics].map { |entry| entry['reason'] }).to eq( + %w[missing_destination_root missing_template_root] + ) + end - report = described_class.new.plan( + it 'reports an unknown profile through the portable profile contract' do + report = described_class.new.report( + kind: 'profile', + profile_name: 'missing', + profiles: {}, options: { mode: 'plan', - template_root: template_root, - destination_root: destination_root, - context: { project_name: 'widget' }, - default_strategy: 'raw_copy', + template_root: '/templates', + destination_root: '/destination', + context: {}, + default_strategy: 'merge', overrides: [], - replacements: { 'PACKAGE_NAME' => 'widget' }, + replacements: {}, allowed_families: nil } ) - expect(report[:mode]).to eq('plan') - expect(report.dig(:runner_report, 'plan_report', 'entries')).not_to be_empty - expect(File.exist?(File.join(destination_root, 'README.md'))).to be(false) + expect(report[:request_kind]).to eq('profile') + expect(report[:ready]).to be(false) + expect(report[:diagnostics].map { |entry| entry['reason'] }).to include('missing_profile') + end + + it 'returns a read-only Rust template plan without claiming filesystem execution' do + Dir.mktmpdir('ast-template-rust-plan') do |root| + template_root = File.join(root, 'templates') + destination_root = File.join(root, 'destination') + FileUtils.mkdir_p(template_root) + FileUtils.mkdir_p(destination_root) + File.write(File.join(template_root, 'README.md'), "# {{PACKAGE_NAME}}\n") + + report = described_class.new.plan( + options: { + mode: 'plan', + template_root: template_root, + destination_root: destination_root, + context: { project_name: 'widget' }, + default_strategy: 'raw_copy', + overrides: [], + replacements: { 'PACKAGE_NAME' => 'widget' }, + allowed_families: nil + } + ) + + expect(report[:mode]).to eq('plan') + expect(report.dig(:runner_report, 'plan_report', 'entries')).not_to be_empty + expect(File.exist?(File.join(destination_root, 'README.md'))).to be(false) + end end end end diff --git a/gems/bash-merge/Gemfile b/gems/bash-merge/Gemfile index cf142c43f..26874c328 100644 --- a/gems/bash-merge/Gemfile +++ b/gems/bash-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/go-merge/Gemfile b/gems/go-merge/Gemfile index d8932e574..3ba8fe33b 100644 --- a/gems/go-merge/Gemfile +++ b/gems/go-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/json-merge/Gemfile b/gems/json-merge/Gemfile index 97a5edfbb..16d2dba24 100644 --- a/gems/json-merge/Gemfile +++ b/gems/json-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/kettle-jem/Gemfile b/gems/kettle-jem/Gemfile index 98ed8cd79..63691c8bb 100644 --- a/gems/kettle-jem/Gemfile +++ b/gems/kettle-jem/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem "kettle-family", "~> 1.2", ">= 1.2.100" # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/markdown-merge/Gemfile b/gems/markdown-merge/Gemfile index 36d88df4e..ccc2b7293 100644 --- a/gems/markdown-merge/Gemfile +++ b/gems/markdown-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/rust-merge/Gemfile b/gems/rust-merge/Gemfile index a242ffd70..2bd240f4a 100644 --- a/gems/rust-merge/Gemfile +++ b/gems/rust-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/tree_haver/Gemfile b/gems/tree_haver/Gemfile index dd418b3a6..93d226ccc 100644 --- a/gems/tree_haver/Gemfile +++ b/gems/tree_haver/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.97' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/tree_haver/spec/integration/rust_tslp_json5_contract_spec.rb b/gems/tree_haver/spec/integration/rust_tslp_json5_contract_spec.rb index 5399d920e..9d36c31fe 100644 --- a/gems/tree_haver/spec/integration/rust_tslp_json5_contract_spec.rb +++ b/gems/tree_haver/spec/integration/rust_tslp_json5_contract_spec.rb @@ -4,6 +4,8 @@ RSpec.describe TreeHaver::Backends::RustTslp do it 'normalizes JSON5 node names to the established TSLP contract' do + skip TreeHaver::Backends::RustTslp.unavailable_reason unless TreeHaver::Backends::RustTslp.available? + tree = TreeHaver.with_backend(:rust_tslp) do TreeHaver::GrammarFinder.new(:json5).register!(raise_on_missing: true) TreeHaver.parser_for(:json5).parse("{ answer: true }\n") diff --git a/gems/tree_haver/spec/integration/rust_tslp_language_matrix_spec.rb b/gems/tree_haver/spec/integration/rust_tslp_language_matrix_spec.rb index 48793a20f..1532cc349 100644 --- a/gems/tree_haver/spec/integration/rust_tslp_language_matrix_spec.rb +++ b/gems/tree_haver/spec/integration/rust_tslp_language_matrix_spec.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.describe 'Rust TSLP language matrix' do + before { skip TreeHaver::Backends::RustTslp.unavailable_reason unless TreeHaver::Backends::RustTslp.available? } + samples = { bash: "echo hi\n", go: "package main\nfunc main() {}\n", diff --git a/gems/typescript-merge/Gemfile b/gems/typescript-merge/Gemfile index 944b505ee..3da71a648 100644 --- a/gems/typescript-merge/Gemfile +++ b/gems/typescript-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides diff --git a/gems/yaml-merge/Gemfile b/gems/yaml-merge/Gemfile index 551ae90a3..5714d44b1 100644 --- a/gems/yaml-merge/Gemfile +++ b/gems/yaml-merge/Gemfile @@ -23,6 +23,12 @@ unless structuredmerge_rust_dev.empty? || %w[false 0 no off].include?(structured 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 + gem 'kettle-family', '~> 1.2', '>= 1.2.100' # Local workspace dependency wiring for *_local.gemfile overrides