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
3 changes: 3 additions & 0 deletions gems/kettle-jem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ Please file a bug if you notice a violation of semantic versioning.
- The packaged `Gemfile` no longer overrides `git_source(:github)` with an SSH
URL. The override was recorded in `Gemfile.lock` as `git@github.com` remotes,
which CI runners cannot clone; Bundler's built-in HTTPS source is used again.
Templating now also removes existing overrides of Bundler's built-in git
sources (`git_source(:github)`, `:gist`, `:bitbucket`) from destination
Gemfiles, including the managed monorepo root Gemfile.

- Generated Ruby 4 extracted-stdlib Gemfiles now include `ostruct` so suites
that require it can run on Rubies where it is no longer bundled by default.
Expand Down
22 changes: 22 additions & 0 deletions gems/kettle-jem/lib/kettle/jem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ class Error < StandardError; end
# turbo_tests were replaced by appraisal2 and turbo_tests2.
PROHIBITED_GEMSPEC_DEPENDENCIES = %w[gem-release appraisal turbo_tests].freeze
PROHIBITED_GEMFILE_DEPENDENCIES = PROHIBITED_GEMSPEC_DEPENDENCIES.freeze
# Git sources Bundler already defines. A Gemfile override (for example an
# SSH `git_source(:github)`) replaces Bundler's own HTTPS source and is
# recorded in Gemfile.lock, where CI cannot clone it: unconditional removal.
BUNDLER_BUILTIN_GIT_SOURCES = %w[github gist bitbucket].freeze
# Canonical resolutions for development-dependency gems whose handling is
# already known ahead of any specific destination project. Two
# categories today:
Expand Down Expand Up @@ -5530,6 +5534,7 @@ def ensure_monorepo_root_gemfile_dependencies(content)
ensure_trailing_newline(content.to_s.empty? ? %(source "https://gem.coop"\n) : content.to_s).dup
)
updated = remove_gemfile_dependency_blocks(updated, PROHIBITED_GEMFILE_DEPENDENCIES)
updated = remove_gemfile_builtin_git_source_overrides(updated).dup
monorepo_root_gemfile_dependency_lines.each do |line|
next if gemfile_declares_gem?(updated, line.fetch(:name))

Expand Down Expand Up @@ -8123,6 +8128,7 @@ def finalize_gemfile_template_source(recipe, content, destination_content, facts
preserve_self_word_entries: local_gemfile_template_recipe?(recipe)
)
end
output = remove_gemfile_builtin_git_source_overrides(output)
if recipe.fetch(:target_path).to_s == "Gemfile"
# A merged Gemfile retains its project-specific source; an accepted
# template owns the complete source declaration.
Expand Down Expand Up @@ -9700,6 +9706,22 @@ def remove_gemfile_dependency_blocks(content, gem_names)
ensure_trailing_newline(lines.each_with_index.reject { |_line, index| remove_indexes.include?(index) }.map(&:first).join.gsub(/\n{3,}/, "\n\n"))
end

# Bundler already provides these git sources; see BUNDLER_BUILTIN_GIT_SOURCES.
def remove_gemfile_builtin_git_source_overrides(content)
remove_indexes = Set.new
ruby_call_records(content, :git_source).each do |call|
argument = call.arguments&.arguments&.first
next unless argument.is_a?(::Prism::SymbolNode)
next unless BUNDLER_BUILTIN_GIT_SOURCES.include?(argument.unescaped.to_s)

(call.location.start_line..ruby_node_source_end_line(call)).each { |line_number| remove_indexes << (line_number - 1) }
end
return content if remove_indexes.empty?

lines = content.to_s.lines.each_with_index.reject { |_line, index| remove_indexes.include?(index) }.map(&:first)
ensure_trailing_newline(lines.join.sub(/\A\n+/, "").gsub(/\n{3,}/, "\n\n"))
end

def gemfile_gem_call_records(content)
ruby_call_records(content, :gem).filter_map do |call|
name = ruby_string_argument(call)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,86 @@
expect(updated).not_to include('gem "turbo_tests"')
end

it "removes overrides of Bundler's built-in git sources and keeps other git sources" do
updated = described_class.remove_gemfile_builtin_git_source_overrides(<<~RUBY)
source "https://gem.coop"

git_source(:github) { |repo_name| "git@github.com:\#{repo_name}.git" }
git_source(:gist) { |repo_name| "https://gist.github.com/\#{repo_name}.git" }
git_source(:bitbucket) do |repo_name|
"https://bitbucket.org/\#{repo_name}.git"
end
git_source(:codeberg) { |repo_name| "https://codeberg.org/\#{repo_name}" }

gem "rubocop-packaging", github: "pboling/rubocop-packaging"
RUBY

expect(updated).not_to include("git_source(:github)")
expect(updated).not_to include("git_source(:gist)")
expect(updated).not_to include("git_source(:bitbucket)")
expect(updated).not_to include("bitbucket.org")
expect(updated).to include("git_source(:codeberg)")
expect(updated).to include('gem "rubocop-packaging", github: "pboling/rubocop-packaging"')
end

it "removes a git_source(:github) override from the managed root Gemfile" do
updated = described_class.ensure_monorepo_root_gemfile_dependencies(
"source \"https://gem.coop\"\ngit_source(:github) { |repo_name| \"https://github.com/\#{repo_name}\" }\n"
)

expect(updated).not_to include("git_source(:github)")
end

it "removes a destination git_source(:github) override when templating the main Gemfile" do
tmp_root = File.expand_path("../tmp", __dir__)
FileUtils.mkdir_p(tmp_root)
Dir.mktmpdir("kettle-jem-main-gemfile-builtin-git-source", tmp_root) do |root|
write_tree(root, {
"example-gem.gemspec" => <<~RUBY,
Gem::Specification.new do |spec|
spec.name = "example-gem"
spec.summary = "Example Gem"
end
RUBY
".kettle-jem.yml" => <<~YAML,
templates:
root: template
apply: true
entries:
- Gemfile
YAML
"Gemfile" => <<~RUBY,
# frozen_string_literal: true

source "https://gem.coop"

git_source(:github) { |repo_name| "git@github.com:\#{repo_name}.git" }
git_source(:codeberg) { |repo_name| "https://codeberg.org/\#{repo_name}" }

gem "destination-only"
RUBY
"template/Gemfile.example" => <<~RUBY
# frozen_string_literal: true

source "https://gem.coop"

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

gem "shared-tool"
RUBY
})

apply = described_class.apply_project(root, env: {})
report = apply.fetch(:recipe_reports).find { |candidate| candidate.fetch(:relative_path) == "Gemfile" }
content = report.fetch(:final_content)

expect(content).not_to include("git_source(:github)")
expect(content).to include("git_source(:codeberg)")
expect(content).to include('gem "shared-tool"')
expect(File.read(File.join(root, "Gemfile"))).to eq(content)
end
end

it "replaces declared kettle-dev dependencies with local paths without duplicate declarations" do
template = File.read(File.expand_path("../../lib/kettle/jem/templates/gemfiles/modular/templating_local.gemfile.example", __dir__))
coverage_template = File.read(File.expand_path("../../lib/kettle/jem/templates/gemfiles/modular/coverage_local.gemfile.example", __dir__))
Expand Down
Loading