Skip to content
This repository was archived by the owner on Apr 14, 2021. It is now read-only.
Closed
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
4 changes: 4 additions & 0 deletions lib/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ def app_cache(custom_path = nil)
path.join(self.settings.app_cache_path)
end

def global_cache
Pathname.new(File.expand_path(self.settings.global_cache_path))
end

def tmp(name = Process.pid.to_s)
Pathname.new(Dir.mktmpdir(["bundler", name]))
end
Expand Down
4 changes: 4 additions & 0 deletions lib/bundler/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ def app_cache_path
end
end

def global_cache_path
self["path.global_cache"] || File.join(Bundler.rubygems.user_home, ".bundle/cache")
end

private

def key_for(key)
Expand Down
39 changes: 32 additions & 7 deletions lib/bundler/source/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ def specs
# sources, and large_idx.use small_idx is way faster than
# small_idx.use large_idx.
idx = @allow_remote ? remote_specs.dup : Index.new
idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
idx.use(cached_specs(:local), :override_dupes) if @allow_cached || @allow_remote
idx.use(cached_specs(:global), :override_dupes)
idx.use(installed_specs, :override_dupes)
idx
end
Expand All @@ -88,6 +89,7 @@ def specs
def install(spec, opts = {})
force = opts[:force]
ensure_builtin_gems_cached = opts[:ensure_builtin_gems_cached]
cache_globally(cached_path(spec), spec) if spec && cached_path(spec)

if ensure_builtin_gems_cached && builtin_gem?(spec)
if !cached_path(spec)
Expand Down Expand Up @@ -306,12 +308,32 @@ def installed_specs
end
end

def cached_specs
def cache_globally(gemfile, spec = nil)
unless File.exist?("#{Bundler.global_cache}/#{File.basename(gemfile)}")
if spec
uri = spec.source.remotes.first
source_dir = [uri.hostname, uri.port, Digest::MD5.hexdigest(uri.path)].compact.join(".")
cache_dir = Bundler.global_cache.join("gems", source_dir)
else
cache_dir = Bundler.global_cache.join("gems")
end
FileUtils.mkdir_p(cache_dir)
FileUtils.cp(gemfile, cache_dir)
end
end

def cached_specs(scope)
@cached_specs ||= begin
idx = installed_specs.dup

path = Bundler.app_cache
Dir["#{path}/*.gem"].each do |gemfile|
path =
case scope
when :local then Bundler.app_cache
when :global then Bundler.global_cache
else
raise "scope must be :local or :global"
end

Dir["#{path}/**/*.gem"].each do |gemfile|
next if gemfile =~ /^bundler\-[\d\.]+?\.gem/
s ||= Bundler.rubygems.spec_from_gem(gemfile)
s.source = self
Expand Down Expand Up @@ -397,13 +419,16 @@ def fetch_gem(spec)
spec.fetch_platform

download_path = Bundler.requires_sudo? ? Bundler.tmp(spec.full_name) : Bundler.rubygems.gem_dir
gem_path = "#{Bundler.rubygems.gem_dir}/cache/#{spec.full_name}.gem"
local_gem_path = "#{Bundler.rubygems.gem_dir}/cache"
gem_path = "#{local_gem_path}/#{spec.full_name}.gem"

FileUtils.mkdir_p("#{download_path}/cache")
Bundler.rubygems.download_gem(spec, uri, download_path)
cache_globally(gem_path, spec)
# TODO: Maybe do something in this method: Check when download_gem is called?

if Bundler.requires_sudo?
Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/cache"
Bundler.mkdir_p local_gem_path
Bundler.sudo "mv #{download_path}/cache/#{spec.full_name}.gem #{gem_path}"
end

Expand Down
158 changes: 158 additions & 0 deletions spec/commands/install_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -404,4 +404,162 @@
expect(err).to include("Please use `bundle config path")
end
end

describe "using the global cache" do
let(:source_hostname) { "localgemserver.test" }
let(:source_uri) { "http://#{source_hostname}" }

it "creates the global cache directory" do
gemfile <<-G
source "file://#{gem_repo1}"
gem "rack", "1.0"
G

bundle :install
expect(bundle_cache).to exist
end

it "copies .gem files to the global cache" do
gemfile <<-G
source "file://#{gem_repo1}"
gem "rack", "1.0"
G

bundle :install
expect(bundle_cached_gem("rack-1.0.0", gem_repo1)).to exist
end

it "does not remove .gem files from the global cache" do
gemfile <<-G
source "file://#{gem_repo1}"
gem "rack", "1.0"
G

bundle :install
expect(bundle_cached_gem("rack-1.0.0", gem_repo1)).to exist

gemfile <<-G
source "file://#{gem_repo1}"
G

bundle :install
expect(bundle_cached_gem("rack-1.0.0", gem_repo1)).to exist
end

# FIXME: check what behavior is being tested
it "does not download gems to the global cache when caching globally" do
gemfile <<-G
source "#{source_uri}"
gem "rack", "1.0"
G

bundle :install, :artifice => "endpoint"
expect(out).to include("Fetching gem metadata from #{source_uri}")
expect(bundle_cached_gem("rack-1.0.0", source_uri)).to exist
FileUtils.rm_r(bundle_cache)
expect(bundle_cache).not_to exist

bundle :install, :artifice => "endpoint"
expect(out).not_to include("Fetching gem metadata from #{source_uri}")
expect(bundle_cached_gem("rack-1.0.0", source_uri)).to exist
end

it "uses the global cache as a source when installing gems" do
install_gemfile <<-G
source "file://#{gem_repo1}"
gem "rack", "1.0"
G

FileUtils.rm_r(default_bundle_path)
# build_gem "rack", :path => bundle_cache_source_dir("https://rubygems.org")

install_gemfile <<-G, :artifice => "endpoint_no_gem"
source "https://rubygems.org"
gem "rack"
G
$stderr.puts out

should_be_installed "rack 1.0.0"
end

it "uses the global cache as a source when installing local gems from a different directory" do
build_gem "omg", :path => bundle_cache_source_dir(gem_repo1)
build_gem "foo", :path => bundle_cache_source_dir(gem_repo1)

install_gemfile <<-G
source "file://#{gem_repo1}"
gem "omg"
G

should_be_installed "omg 1.0.0"
should_not_be_installed "foo 1.0.0"

Dir.chdir bundled_app2 do
create_file "gems.rb", Pathname.new(bundled_app2("gems.rb")), <<-G
source "file://#{gem_repo1}"
gem "foo"
G

should_not_be_installed "omg 1.0.0"
should_not_be_installed "foo 1.0.0"

bundle :install

should_be_installed "foo 1.0.0"
should_not_be_installed "omg 1.0.0"
end
end

it "uses the global cache as a source when installing remote gems from a different directory" do
install_gemfile <<-G
source "file://#{gem_repo1}"
gem "rack"
G

should_be_installed "rack 1.0.0"

Dir.chdir bundled_app2 do
create_file "gems.rb", Pathname.new(bundled_app2("gems.rb")), <<-G
source "#{source_uri}"
gem "rack"
G

should_not_be_installed "rack 1.0.0"

bundle :install, :artifice => "endpoint_no_gem"
expect(out).not_to include("Fetching gem metadata from #{source_uri}")
should_be_installed "rack 1.0.0"
end
end

it "allows the global cache path to be configured" do
bundle "config path.global_cache #{home}/machine_cache"
build_gem "omg", :path => "#{home}/machine_cache/gems/#{source_dir(gem_repo1)}"

install_gemfile <<-G
source "file://#{gem_repo1}"
gem "omg"
G

should_be_installed "omg 1.0.0"
end

it "copies gems from the local cache to the global cache" do
gemfile <<-G
source "file://#{gem_repo1}"
gem "rack", "1.0"
G

bundle :install
bundle :cache
FileUtils.rm_r(default_bundle_path)
FileUtils.rm_r(bundle_cache)
expect(default_bundle_path).not_to exist
expect(bundle_cache).not_to exist
expect(cached_gem("rack-1.0.0")).to exist

bundle :install
expect(bundle_cached_gem("rack-1.0.0", gem_repo1)).to exist
end
end
end
11 changes: 11 additions & 0 deletions spec/support/artifice/endpoint_no_gem.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require File.expand_path("../endpoint", __FILE__)

Artifice.deactivate

class EndpointNoGem < Endpoint
get "/gems/:id" do
halt 500
end
end

Artifice.activate_with(EndpointNoGem)
22 changes: 22 additions & 0 deletions spec/support/path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ def cached_gem(path)
bundled_app("vendor/cache/#{path}.gem")
end

def bundle_cache(*path)
home(".bundle/cache", *path)
end

def source_dir(source)
prefix = %r(https?:\/\/) =~ source.to_s ? "" : "file:"
uri = Bundler::Source::Rubygems::Remote.new(URI("#{prefix}#{source}/")).uri
[uri.hostname, uri.port, Digest::MD5.hexdigest(uri.path)].compact.join(".")
end

def bundle_cache_source_dir(source)
bundle_cache("gems", source_dir(source))
end

def bundle_cached_gem(gem, source = nil)
if source
bundle_cache_source_dir(source).join("#{gem}.gem")
else
bundle_cache("gems", "#{gem}.gem")
end
end

def base_system_gems
tmp.join("gems/base")
end
Expand Down