mirror of
https://github.com/ruby/ruby.git
synced 2026-10-29 00:46:26 +08:00
Several things are fixed: * Don't re-install a plugin referenced from the gemfile with every call to `bundle install` * If the version of a plugin referenced in the gemfile conflicts with what's in the plugin index, _do_ re-install it * If plugins aren't installed yet, don't throw cryptic errors from commands that don't implicitly install gems, such as `bundle check` and `bundle info`. This also applies if the plugin index references a system gem, and that gem is removed. This is all accomplished by actuallying including plugins as regular dependencies in the Gemfile, so that they end up in the lockfile, and then just using the regular lockfile with the plugins-only pass of the gemfile in the Plugin infrastructure. This also means that non-specific version constraints can be used for plugins, and you can update them with `bundle update <plugin>` just like any other gem. https://github.com/ruby/rubygems/commit/5bb065ddab Co-authored-by: Diogo Fernandes <diogofernandesop@gmail.com>
463 lines
12 KiB
Ruby
463 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe "real source plugins" do
|
|
context "with a minimal source plugin" do
|
|
before do
|
|
build_repo2 do
|
|
build_plugin "bundler-source-mpath" do |s|
|
|
s.write "plugins.rb", <<-RUBY
|
|
require "bundler-source-mpath"
|
|
|
|
class MPath < Bundler::Plugin::API
|
|
source "mpath"
|
|
|
|
attr_reader :path
|
|
|
|
def initialize(opts)
|
|
super
|
|
|
|
@path = Pathname.new options["uri"]
|
|
end
|
|
|
|
def fetch_gemspec_files
|
|
@spec_files ||= begin
|
|
glob = "{,*,*/*}.gemspec"
|
|
if installed?
|
|
search_path = install_path
|
|
else
|
|
search_path = path
|
|
end
|
|
Dir["\#{search_path.to_s}/\#{glob}"]
|
|
end
|
|
end
|
|
|
|
def install(spec, opts)
|
|
mkdir_p(install_path.parent)
|
|
require 'fileutils'
|
|
FileUtils.cp_r(path, install_path)
|
|
|
|
spec_path = install_path.join("\#{spec.full_name}.gemspec")
|
|
spec_path.open("wb") {|f| f.write spec.to_ruby }
|
|
spec.loaded_from = spec_path.to_s
|
|
|
|
post_install(spec)
|
|
|
|
nil
|
|
end
|
|
end
|
|
RUBY
|
|
end # build_plugin
|
|
end
|
|
|
|
build_lib "a-path-gem"
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo2" # plugin source
|
|
source "#{lib_path("a-path-gem-1.0")}", :type => :mpath do
|
|
gem "a-path-gem"
|
|
end
|
|
G
|
|
end
|
|
|
|
it "installs" do
|
|
bundle "install"
|
|
|
|
expect(out).to include("Bundle complete!")
|
|
|
|
expect(the_bundle).to include_gems("a-path-gem 1.0")
|
|
end
|
|
|
|
it "writes to lockfile" do
|
|
bundle "install"
|
|
|
|
checksums = checksums_section_when_enabled do |c|
|
|
c.no_checksum "a-path-gem", "1.0"
|
|
c.checksum gem_repo2, "bundler-source-mpath", "1.0"
|
|
end
|
|
|
|
expect(lockfile).to eq <<~G
|
|
PLUGIN SOURCE
|
|
remote: #{lib_path("a-path-gem-1.0")}
|
|
type: mpath
|
|
specs:
|
|
a-path-gem (1.0)
|
|
|
|
GEM
|
|
remote: https://gem.repo2/
|
|
specs:
|
|
bundler-source-mpath (1.0)
|
|
|
|
PLATFORMS
|
|
#{lockfile_platforms}
|
|
|
|
DEPENDENCIES
|
|
a-path-gem!
|
|
bundler-source-mpath
|
|
#{checksums}
|
|
BUNDLED WITH
|
|
#{Bundler::VERSION}
|
|
G
|
|
end
|
|
|
|
it "provides correct #full_gem_path" do
|
|
bundle "install"
|
|
run <<-RUBY
|
|
puts Bundler.rubygems.find_name('a-path-gem').first.full_gem_path
|
|
RUBY
|
|
expect(out).to eq(bundle("info a-path-gem --path"))
|
|
end
|
|
|
|
it "installs the gem executables" do
|
|
build_lib "gem_with_bin" do |s|
|
|
s.executables = ["foo"]
|
|
end
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo2" # plugin source
|
|
source "#{lib_path("gem_with_bin-1.0")}", :type => :mpath do
|
|
gem "gem_with_bin"
|
|
end
|
|
G
|
|
|
|
bundle "exec foo"
|
|
expect(out).to eq("1.0")
|
|
end
|
|
|
|
describe "bundle cache/package" do
|
|
let(:uri_hash) { Digest(:SHA1).hexdigest(lib_path("a-path-gem-1.0").to_s) }
|
|
it "copies repository to vendor cache and uses it" do
|
|
bundle "install"
|
|
bundle :cache
|
|
|
|
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
|
|
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.git")).not_to exist
|
|
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.bundlecache")).to be_file
|
|
|
|
FileUtils.rm_r lib_path("a-path-gem-1.0")
|
|
expect(the_bundle).to include_gems("a-path-gem 1.0")
|
|
end
|
|
|
|
it "copies repository to vendor cache and uses it even when installed with `path` configured" do
|
|
bundle_config "path vendor/bundle"
|
|
bundle :install
|
|
bundle :cache
|
|
|
|
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
|
|
|
|
FileUtils.rm_r lib_path("a-path-gem-1.0")
|
|
expect(the_bundle).to include_gems("a-path-gem 1.0")
|
|
end
|
|
|
|
it "bundler package copies repository to vendor cache" do
|
|
bundle_config "path vendor/bundle"
|
|
bundle :install
|
|
bundle :cache
|
|
|
|
expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist
|
|
|
|
FileUtils.rm_r lib_path("a-path-gem-1.0")
|
|
expect(the_bundle).to include_gems("a-path-gem 1.0")
|
|
end
|
|
end
|
|
|
|
context "with lockfile" do
|
|
before do
|
|
lockfile <<-G
|
|
PLUGIN SOURCE
|
|
remote: #{lib_path("a-path-gem-1.0")}
|
|
type: mpath
|
|
specs:
|
|
a-path-gem (1.0)
|
|
|
|
GEM
|
|
remote: https://gem.repo2/
|
|
specs:
|
|
|
|
PLATFORMS
|
|
#{generic_local_platform}
|
|
|
|
DEPENDENCIES
|
|
a-path-gem!
|
|
|
|
BUNDLED WITH
|
|
#{Bundler::VERSION}
|
|
G
|
|
end
|
|
|
|
it "installs" do
|
|
bundle "install"
|
|
|
|
expect(the_bundle).to include_gems("a-path-gem 1.0")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a more elaborate source plugin" do
|
|
before do
|
|
build_repo2 do
|
|
build_plugin "bundler-source-gitp" do |s|
|
|
s.write "plugins.rb", <<-RUBY
|
|
require "open3"
|
|
|
|
class SPlugin < Bundler::Plugin::API
|
|
source "gitp"
|
|
|
|
attr_reader :ref
|
|
|
|
def initialize(opts)
|
|
super
|
|
|
|
@ref = options["ref"] || options["branch"] || options["tag"] || "main"
|
|
@unlocked = false
|
|
end
|
|
|
|
def eql?(other)
|
|
other.is_a?(self.class) && uri == other.uri && ref == other.ref
|
|
end
|
|
|
|
alias_method :==, :eql?
|
|
|
|
def fetch_gemspec_files
|
|
@spec_files ||= begin
|
|
glob = "{,*,*/*}.gemspec"
|
|
if !cached?
|
|
cache_repo
|
|
end
|
|
|
|
if installed? && !@unlocked
|
|
path = install_path
|
|
else
|
|
path = cache_path
|
|
end
|
|
|
|
Dir["\#{path}/\#{glob}"]
|
|
end
|
|
end
|
|
|
|
def install(spec, opts)
|
|
mkdir_p(install_path.dirname)
|
|
rm_rf(install_path)
|
|
`git clone --no-checkout --quiet "\#{cache_path}" "\#{install_path}"`
|
|
Open3.capture2e("git reset --hard \#{revision}", :chdir => install_path)
|
|
|
|
spec_path = install_path.join("\#{spec.full_name}.gemspec")
|
|
spec_path.open("wb") {|f| f.write spec.to_ruby }
|
|
spec.loaded_from = spec_path.to_s
|
|
|
|
post_install(spec)
|
|
|
|
nil
|
|
end
|
|
|
|
def options_to_lock
|
|
opts = {"revision" => revision}
|
|
opts["ref"] = ref if ref != "main"
|
|
opts
|
|
end
|
|
|
|
def unlock!
|
|
@unlocked = true
|
|
@revision = latest_revision
|
|
end
|
|
|
|
def app_cache_dirname
|
|
"\#{base_name}-\#{shortref_for_path(revision)}"
|
|
end
|
|
|
|
private
|
|
|
|
def cache_path
|
|
@cache_path ||= cache_dir.join("gitp", base_name)
|
|
end
|
|
|
|
def cache_repo
|
|
`git clone --quiet \#{@options["uri"]} \#{cache_path}`
|
|
end
|
|
|
|
def cached?
|
|
File.directory?(cache_path)
|
|
end
|
|
|
|
def locked_revision
|
|
options["revision"]
|
|
end
|
|
|
|
def revision
|
|
@revision ||= locked_revision || latest_revision
|
|
end
|
|
|
|
def latest_revision
|
|
if !cached? || @unlocked
|
|
rm_rf(cache_path)
|
|
cache_repo
|
|
end
|
|
|
|
output, _status = Open3.capture2e("git rev-parse --verify \#{@ref}", :chdir => cache_path)
|
|
output.strip
|
|
end
|
|
|
|
def base_name
|
|
File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*}, ""), ".git")
|
|
end
|
|
|
|
def shortref_for_path(ref)
|
|
ref[0..11]
|
|
end
|
|
|
|
def install_path
|
|
@install_path ||= begin
|
|
git_scope = "\#{base_name}-\#{shortref_for_path(revision)}"
|
|
|
|
gem_install_dir.join(git_scope)
|
|
end
|
|
end
|
|
|
|
def installed?
|
|
File.directory?(install_path)
|
|
end
|
|
end
|
|
RUBY
|
|
end
|
|
end
|
|
|
|
build_git "ma-gitp-gem"
|
|
|
|
gemfile <<-G
|
|
source "https://gem.repo2" # plugin source
|
|
source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
|
|
gem "ma-gitp-gem"
|
|
end
|
|
G
|
|
end
|
|
|
|
it "handles the source option" do
|
|
bundle "install"
|
|
expect(out).to include("Bundle complete!")
|
|
expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
|
|
end
|
|
|
|
it "writes to lockfile" do
|
|
revision = revision_for(lib_path("ma-gitp-gem-1.0"))
|
|
bundle "install"
|
|
|
|
checksums = checksums_section_when_enabled do |c|
|
|
c.checksum gem_repo2, "bundler-source-gitp", "1.0"
|
|
c.no_checksum "ma-gitp-gem", "1.0"
|
|
end
|
|
|
|
expect(lockfile).to eq <<~G
|
|
PLUGIN SOURCE
|
|
remote: #{lib_path("ma-gitp-gem-1.0")}
|
|
type: gitp
|
|
revision: #{revision}
|
|
specs:
|
|
ma-gitp-gem (1.0)
|
|
|
|
GEM
|
|
remote: https://gem.repo2/
|
|
specs:
|
|
bundler-source-gitp (1.0)
|
|
|
|
PLATFORMS
|
|
#{lockfile_platforms}
|
|
|
|
DEPENDENCIES
|
|
bundler-source-gitp
|
|
ma-gitp-gem!
|
|
#{checksums}
|
|
BUNDLED WITH
|
|
#{Bundler::VERSION}
|
|
G
|
|
end
|
|
|
|
context "with lockfile" do
|
|
before do
|
|
revision = revision_for(lib_path("ma-gitp-gem-1.0"))
|
|
lockfile <<-G
|
|
PLUGIN SOURCE
|
|
remote: #{lib_path("ma-gitp-gem-1.0")}
|
|
type: gitp
|
|
revision: #{revision}
|
|
specs:
|
|
ma-gitp-gem (1.0)
|
|
|
|
GEM
|
|
remote: https://gem.repo2/
|
|
specs:
|
|
|
|
PLATFORMS
|
|
#{generic_local_platform}
|
|
|
|
DEPENDENCIES
|
|
ma-gitp-gem!
|
|
|
|
BUNDLED WITH
|
|
#{Bundler::VERSION}
|
|
G
|
|
end
|
|
|
|
it "installs" do
|
|
bundle "install"
|
|
expect(the_bundle).to include_gems("ma-gitp-gem 1.0")
|
|
end
|
|
|
|
it "uses the locked ref" do
|
|
update_git "ma-gitp-gem"
|
|
bundle "install"
|
|
|
|
run <<-RUBY
|
|
require 'ma/gitp/gem'
|
|
puts "WIN" unless defined?(MAGITPGEM_PREV_REF)
|
|
RUBY
|
|
expect(out).to eq("WIN")
|
|
end
|
|
|
|
it "updates the deps on bundler update" do
|
|
update_git "ma-gitp-gem"
|
|
bundle "update ma-gitp-gem"
|
|
|
|
run <<-RUBY
|
|
require 'ma/gitp/gem'
|
|
puts "WIN" if defined?(MAGITPGEM_PREV_REF)
|
|
RUBY
|
|
expect(out).to eq("WIN")
|
|
end
|
|
|
|
it "updates the deps on change in gemfile" do
|
|
update_git "ma-gitp-gem", "1.1", path: lib_path("ma-gitp-gem-1.0"), gemspec: true
|
|
gemfile <<-G
|
|
source "https://gem.repo2" # plugin source
|
|
source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do
|
|
gem "ma-gitp-gem", "1.1"
|
|
end
|
|
G
|
|
bundle "install"
|
|
|
|
expect(the_bundle).to include_gems("ma-gitp-gem 1.1")
|
|
end
|
|
end
|
|
|
|
describe "bundle cache with gitp" do
|
|
it "copies repository to vendor cache and uses it" do
|
|
git = build_git "foo"
|
|
ref = git.ref_for("main", 11)
|
|
|
|
install_gemfile <<-G
|
|
source "https://gem.repo2" # plugin source
|
|
source '#{lib_path("foo-1.0")}', :type => :gitp do
|
|
gem "foo"
|
|
end
|
|
G
|
|
|
|
bundle :cache
|
|
expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist
|
|
expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist
|
|
expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file
|
|
|
|
FileUtils.rm_r lib_path("foo-1.0")
|
|
expect(the_bundle).to include_gems "foo 1.0"
|
|
end
|
|
end
|
|
end
|
|
end
|