summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml40
-rw-r--r--CHANGELOG.md9
-rw-r--r--DEVELOPMENT.md4
-rw-r--r--ISSUES.md17
-rw-r--r--README.md2
-rw-r--r--Rakefile9
-rwxr-xr-xexe/bundle10
-rw-r--r--lib/bundler.rb60
-rw-r--r--lib/bundler/cli.rb28
-rw-r--r--lib/bundler/cli/binstubs.rb2
-rw-r--r--lib/bundler/cli/cache.rb2
-rw-r--r--lib/bundler/cli/check.rb2
-rw-r--r--lib/bundler/cli/common.rb9
-rw-r--r--lib/bundler/cli/doctor.rb36
-rw-r--r--lib/bundler/cli/gem.rb3
-rw-r--r--lib/bundler/cli/install.rb7
-rw-r--r--lib/bundler/cli/lock.rb13
-rw-r--r--lib/bundler/cli/outdated.rb101
-rw-r--r--lib/bundler/cli/platform.rb2
-rw-r--r--lib/bundler/cli/show.rb2
-rw-r--r--lib/bundler/cli/update.rb17
-rw-r--r--lib/bundler/compact_index_client.rb102
-rw-r--r--lib/bundler/compact_index_client/cache.rb119
-rw-r--r--lib/bundler/compact_index_client/updater.rb88
-rw-r--r--lib/bundler/current_ruby.rb6
-rw-r--r--lib/bundler/definition.rb111
-rw-r--r--lib/bundler/dependency.rb2
-rw-r--r--lib/bundler/dsl.rb5
-rw-r--r--lib/bundler/env.rb4
-rw-r--r--lib/bundler/errors.rb1
-rw-r--r--lib/bundler/feature_flag.rb32
-rw-r--r--lib/bundler/fetcher/compact_index.rb6
-rw-r--r--lib/bundler/fetcher/dependency.rb2
-rw-r--r--lib/bundler/fetcher/downloader.rb11
-rw-r--r--lib/bundler/friendly_errors.rb5
-rw-r--r--lib/bundler/gem_helper.rb2
-rw-r--r--lib/bundler/gem_helpers.rb70
-rw-r--r--lib/bundler/inline.rb4
-rw-r--r--lib/bundler/installer.rb3
-rw-r--r--lib/bundler/lazy_specification.rb17
-rw-r--r--lib/bundler/lockfile_parser.rb2
-rw-r--r--lib/bundler/match_platform.rb3
-rw-r--r--lib/bundler/plugin.rb6
-rw-r--r--lib/bundler/postit_trampoline.rb19
-rw-r--r--lib/bundler/remote_specification.rb5
-rw-r--r--lib/bundler/resolver.rb101
-rw-r--r--lib/bundler/ruby_version.rb5
-rw-r--r--lib/bundler/rubygems_ext.rb5
-rw-r--r--lib/bundler/rubygems_gem_installer.rb48
-rw-r--r--lib/bundler/rubygems_integration.rb5
-rw-r--r--lib/bundler/runtime.rb3
-rw-r--r--lib/bundler/settings.rb23
-rw-r--r--lib/bundler/setup.rb2
-rw-r--r--lib/bundler/shared_helpers.rb16
-rw-r--r--lib/bundler/source.rb1
-rw-r--r--lib/bundler/source/git.rb2
-rw-r--r--lib/bundler/source/git/git_proxy.rb5
-rw-r--r--lib/bundler/source/path.rb7
-rw-r--r--lib/bundler/source/rubygems.rb3
-rw-r--r--lib/bundler/spec_set.rb41
-rw-r--r--lib/bundler/templates/newgem/README.md.tt2
-rw-r--r--lib/bundler/templates/newgem/bin/console.tt2
-rw-r--r--lib/bundler/ui/shell.rb4
-rw-r--r--lib/bundler/ui/silent.rb9
-rw-r--r--lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb79
-rw-r--r--lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb112
-rw-r--r--lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb80
-rw-r--r--lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb4
-rw-r--r--lib/bundler/yaml_serializer.rb2
-rw-r--r--man/bundle-config.ronn2
-rw-r--r--spec/bundler/env_spec.rb9
-rw-r--r--spec/bundler/fetcher/compact_index_spec.rb2
-rw-r--r--spec/bundler/fetcher/dependency_spec.rb4
-rw-r--r--spec/bundler/mirror_spec.rb10
-rw-r--r--spec/bundler/remote_specification_spec.rb22
-rw-r--r--spec/bundler/settings_spec.rb12
-rw-r--r--spec/bundler/shared_helpers_spec.rb5
-rw-r--r--spec/bundler/source_spec.rb2
-rw-r--r--spec/bundler/yaml_serializer_spec.rb23
-rw-r--r--spec/cache/gems_spec.rb2
-rw-r--r--spec/commands/doctor_spec.rb7
-rw-r--r--spec/commands/exec_spec.rb36
-rw-r--r--spec/commands/install_spec.rb2
-rw-r--r--spec/commands/lock_spec.rb171
-rw-r--r--spec/commands/newgem_spec.rb29
-rw-r--r--spec/commands/outdated_spec.rb114
-rw-r--r--spec/install/gemfile/eval_gemfile_spec.rb18
-rw-r--r--spec/install/gemfile/platform_spec.rb19
-rw-r--r--spec/install/gemfile/specific_platform_spec.rb95
-rw-r--r--spec/install/gems/compact_index_spec.rb43
-rw-r--r--spec/install/gems/resolving_spec.rb52
-rw-r--r--spec/install/gemspecs_spec.rb2
-rw-r--r--spec/install/path_spec.rb8
-rw-r--r--spec/other/major_deprecation_spec.rb16
-rw-r--r--spec/other/trampoline_spec.rb18
-rw-r--r--spec/resolver/basic_spec.rb5
-rw-r--r--spec/resolver/platform_spec.rb2
-rw-r--r--spec/runtime/inline_spec.rb20
-rw-r--r--spec/runtime/platform_spec.rb16
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/artifice/compact_index.rb7
-rw-r--r--spec/support/artifice/compact_index_wrong_gem_checksum.rb19
-rw-r--r--spec/support/builders.rb9
-rw-r--r--spec/support/helpers.rb6
-rw-r--r--spec/support/indexes.rb2
-rw-r--r--spec/support/matchers.rb6
-rw-r--r--spec/support/platforms.rb4
-rw-r--r--spec/support/the_bundle.rb5
108 files changed, 1832 insertions, 557 deletions
diff --git a/.travis.yml b/.travis.yml
index f8dd2dd7ca..ba3035a4d7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,11 +13,6 @@ branches:
- /.+-stable$/
notifications:
- email:
- # andre
- - secure: "bCcvqJT7YrBawtkXXwHhT+jOFth7r2Qv/30PkkbhQxk6Jb3xambjCOJ3U6vJ\ngYmiL50exi5lUp3oc3SEbHN5t2CrZqOZDQ6o7P8EAmB5c0oH2RrYaFOkI5Gt\nul/jGH/96A9sj0aMwG7JfdMSfhqj1DUKAm2PnnbXPL853VfmT24="
- # terence
- - secure: "MQ8eA5Jb8YzEpAo58DRGfVJklAPcEbAulpBZnTxp0am6ldneDtJHbQk21w6R\nj5GsDHlzr/lMp/GHIimtUZ7rLohfND8fj/W7fs1Dkd4eN02/ERt98x3pHlqv\nvZgSnZ39uVYv+OcphraE24QaRaGWLhWZAMYQTVe/Yz50NyG8g1U="
slack:
on_success: change
on_failure: always
@@ -43,12 +38,16 @@ env:
# We need to know if changes to rubygems will break bundler on release
- RGV=master
# Test the latest rubygems release with all of our supported rubies
- - RGV=v2.6.4
- - RGV=v2.4.8
+ - RGV=v2.6.6
matrix:
include:
- # Ruby 2.2, Rubygems 2.4.5 and up (included by RGV above)
+ # Ruby 2.3, Rubygems 2.5.1 and up
+ - rvm: 2.2
+ env: RGV=v2.5.2
+ # Ruby 2.2, Rubygems 2.4.5 and up
+ - rvm: 2.2
+ env: RGV=v2.4.8
# Ruby 2.1, Rubygems 2.2.2 and up
- rvm: 2.1
env: RGV=v2.2.5
@@ -74,9 +73,12 @@ matrix:
env: RGV=v1.6.2
- rvm: 1.9.3
env: RGV=v1.5.3
+
# Ruby 1.8.7, Rubygems 1.3.6 and up
- rvm: 1.8.7
env: RGV=v2.2.5
+ # ALLOWED FAILURES
+ # since the great Travis image outage, frequent random segfaults :'(
- rvm: 1.8.7
env: RGV=v2.0.14
- rvm: 1.8.7
@@ -93,16 +95,26 @@ matrix:
env: RGV=v1.3.7
- rvm: 1.8.7
env: RGV=v1.3.6
-
- # ALLOWED FAILURES
- # For no apparent reason, this often goes over the Travis limit
- - rvm: 1.8.7
- env: RGV=v2.1.11
# Ruby-head (we want to know how we're doing, but not fail the build)
- rvm: ruby-head
env: RGV=master
allow_failures:
- rvm: 1.8.7
- env: RGV=v2.1.11
+ env: RGV=v2.0.14
+ - rvm: 1.8.7
+ env: RGV=v1.8.29
+ - rvm: 1.8.7
+ env: RGV=v1.7.2
+ - rvm: 1.8.7
+ env: RGV=v1.6.2
+ - rvm: 1.8.7
+ env: RGV=v1.5.3
+ - rvm: 1.8.7
+ env: RGV=v1.4.2
+ - rvm: 1.8.7
+ env: RGV=v1.3.7
+ - rvm: 1.8.7
+ env: RGV=v1.3.6
- rvm: ruby-head
+ env: RGV=master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d3f6ae5e23..5499bc828e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,7 +23,7 @@ Bugfixes:
- ensure redefined methods have the same visibility as the one they're replacing, fixing `Kernel.require` failing on JRuby (#4975, @segiddins)
- ensure that Bundler won't complain about a corrupt lockfile when no lockfile exists when using `gemspec` in the Gemfile (#5006, @segiddins)
- fail gracefully when parsing the metadata for a gemspec from the compact index fails (@segiddins)
- - fix invoking bundler with binstubs generated by RubyGems 2.6.2+ (#4974, @chrismo)
+ - fix system gems not being copied to --path on bundle install (e.g. --deployment) (#4974, @chrismo)
Performance:
@@ -53,7 +53,7 @@ Features:
- print gem installation errors after other install output (#4834, @segiddins)
- add `lock --remove-platform` flag to remove platforms from the lock (#4877, @segiddins)
- add `only_update_to_newer_versions` setting to prevent downgrades during `update` (@segiddins)
- - expanded expirimental plugin support to include hooks and sources (@asutoshpalai)
+ - expanded experimental plugin support to include hooks and sources (@asutoshpalai)
Bugfixes:
@@ -131,6 +131,11 @@ Bugfixes:
- allow running `bundle install --deployment` after `bundle package --all` with path gems (#2175, @allenzhao)
- add support for patchlevels in ruby versions in the gemfile and gemspecs (#4593, @chalkos)
+## 1.12.6 (2016-10-10)
+
+Bugfixes:
+ - add support for weak etags to the new index (@segiddins)
+
## 1.12.5 (2016-05-25)
Bugfixes:
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 8964b75c3d..67e1527677 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -12,7 +12,7 @@ If you have any questions after reading this page, please feel free to contact e
## How you can help
-We track [small bugs and features](https://github.com/bundler/bundler/issues?labels=small) so that anyone who wants to help can start with something that's not too overwhelming. We also keep a [list of things anyone can help with, any time](https://github.com/bundler/bundler/blob/master/CONTRIBUTING.md#contributing). If nothing on those lists looks good, talk to us, and we'll figure out what you can help with. We can absolutely use your help, no matter what level of programming skill you have at the moment.
+We track [small bugs and features](https://github.com/bundler/bundler/labels/contribution%3A%20small) so that anyone who wants to help can start with something that's not too overwhelming. We also keep a [list of things anyone can help with, any time](https://github.com/bundler/bundler/blob/master/CONTRIBUTING.md#contributing). If nothing on those lists looks good, talk to us, and we'll figure out what you can help with. We can absolutely use your help, no matter what level of programming skill you have at the moment.
# Development setup
@@ -40,6 +40,8 @@ Bundler doesn't use a Gemfile to list development dependencies, because when we
The `BUNDLE_DISABLE_POSTIT` environment variable ensures that the version of Bundler in `/path/to/bundler/lib` will be used. Without that environment setting, Bundler will automatically download, install, and run the version of Bundler listed in `Gemfile.lock`. With that set up, you can test changes you've made to Bundler by running `dbundle`, without interfering with the regular `bundle` command.
+To dive into the code with Pry: `RUBYOPT=-rpry dbundle` to require pry and then run commands.
+
# Submitting Pull Requests
Before you submit a pull request, please remember to do the following:
diff --git a/ISSUES.md b/ISSUES.md
index e0bc56cafc..b7d18ea314 100644
--- a/ISSUES.md
+++ b/ISSUES.md
@@ -10,6 +10,23 @@ Detailed information about each Bundler command, including help with common prob
## Troubleshooting
+### Permission denied when installing bundler
+
+Certain operating systems such as MacOS and Ubuntu have versions of Ruby that require evelated privileges to install gems.
+
+ ERROR: While executing gem ... (Gem::FilePermissionError)
+ You don't have write permissions for the /Library/Ruby/Gems/2.0.0 directory.
+
+There are multiple ways to solve this issue. You can install bundler with elevated privilges using `sudo` or `su`.
+
+ sudo gem install bundler
+
+If you cannot elevated your privileges or do not want to globally install Bundler, you can use the `--user-install` option.
+
+ gem install bundler --user-install
+
+This will install Bundler into your home directory. Note that you will need to append `~/.gem/ruby/<ruby version>/bin` to your `$PATH` variable to use `bundle`.
+
### Heroku errors
Please open a ticket with [Heroku](https://www.heroku.com) if you're having trouble deploying. They have a professional support team who can help you resolve Heroku issues far better than the Bundler team can. If the problem that you are having turns out to be a bug in Bundler itself, [Heroku support](https://www.heroku.com/support) can get the exact details to us.
diff --git a/README.md b/README.md
index c467673067..a3a9acbf48 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,8 @@ bundle install
bundle exec rspec
```
+For help with installation issues, see [ISSUES](https://github.com/bundler/bundler/blob/master/ISSUES.md)
+
See [bundler.io](http://bundler.io) for the full documentation.
### Troubleshooting
diff --git a/Rakefile b/Rakefile
index 7c4a2eb0a4..178d481655 100644
--- a/Rakefile
+++ b/Rakefile
@@ -127,7 +127,7 @@ begin
rubyopt = ENV["RUBYOPT"]
# When editing this list, also edit .travis.yml!
branches = %w(master)
- releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.6.4)
+ releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.5.2 v2.6.6)
(branches + releases).each do |rg|
desc "Run specs with Rubygems #{rg}"
RSpec::Core::RakeTask.new(rg) do |t|
@@ -285,13 +285,6 @@ end
begin
require "automatiek"
- Automatiek::RakeTask.new("compact_index_client") do |lib|
- lib.download = { :github => "https://github.com/bundler/compact_index_client" }
- lib.namespace = "CompactIndexClient"
- lib.prefix = "Bundler"
- lib.vendor_lib = "lib/bundler/vendor/compact_index_client"
- end
-
Automatiek::RakeTask.new("molinillo") do |lib|
lib.download = { :github => "https://github.com/CocoaPods/Molinillo" }
lib.namespace = "Molinillo"
diff --git a/exe/bundle b/exe/bundle
index 51a9035d97..ec88ea7552 100755
--- a/exe/bundle
+++ b/exe/bundle
@@ -4,12 +4,10 @@
# Exit cleanly from an early interrupt
Signal.trap("INT") { exit 1 }
-unless ENV["BUNDLE_DISABLE_POSTIT"]
- update = "update".start_with?(ARGV.first || " ") && ARGV.find {|a| a.start_with?("--bundler") }
- update &&= update =~ /--bundler(?:=(.+))?/ && $1 || "> 0.a"
- ENV["BUNDLER_VERSION"] = update if update
- require "bundler/postit_trampoline"
-end
+update = "update".start_with?(ARGV.first || " ") && ARGV.find {|a| a.start_with?("--bundler") }
+update &&= update =~ /--bundler(?:=(.+))?/ && $1 || "> 0.a"
+ENV["BUNDLER_VERSION"] = update if update
+require "bundler/postit_trampoline"
require "bundler"
# Check if an older version of bundler is installed
diff --git a/lib/bundler.rb b/lib/bundler.rb
index a152db323c..0dcaa03965 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -3,6 +3,8 @@ require "fileutils"
require "pathname"
require "rbconfig"
require "thread"
+require "tmpdir"
+
require "bundler/errors"
require "bundler/environment_preserver"
require "bundler/gem_remote_fetcher"
@@ -27,6 +29,7 @@ module Bundler
autoload :EndpointSpecification, "bundler/endpoint_specification"
autoload :Env, "bundler/env"
autoload :Fetcher, "bundler/fetcher"
+ autoload :FeatureFlag, "bundler/feature_flag"
autoload :GemHelper, "bundler/gem_helper"
autoload :GemHelpers, "bundler/gem_helpers"
autoload :GemVersionPromoter, "bundler/gem_version_promoter"
@@ -90,7 +93,7 @@ module Bundler
# Return if all groups are already loaded
return @setup if defined?(@setup) && @setup
- definition.validate_ruby!
+ definition.validate_runtime!
SharedHelpers.print_major_deprecations!
@@ -142,8 +145,41 @@ module Bundler
"#{Bundler.rubygems.ruby_engine}/#{Bundler.rubygems.config_map[:ruby_version]}"
end
+ def user_home
+ @user_home ||= begin
+ home = Bundler.rubygems.user_home
+ warning = "Your home directory is not set properly:"
+ if home.nil?
+ warning += "\n * It is not set at all"
+ elsif !File.directory?(home)
+ warning += "\n * `#{home}` is not a directory"
+ elsif !File.writable?(home)
+ warning += "\n * `#{home}` is not writable"
+ else
+ return @user_home = Pathname.new(home)
+ end
+
+ login = Etc.getlogin || "unknown"
+
+ tmp_home = Pathname.new(Dir.tmpdir).join("bundler", "home", login)
+ begin
+ SharedHelpers.filesystem_access(tmp_home, :write) do |p|
+ FileUtils.mkdir_p(p)
+ end
+ rescue => e
+ warning += "\n\nBundler also failed to create a temporary home directory at `#{tmp_home}`:\n#{e}"
+ raise warning
+ end
+
+ warning += "\n\nBundler will use `#{tmp_home}` as your home directory temporarily"
+
+ Bundler.ui.warn(warning)
+ tmp_home
+ end
+ end
+
def user_bundle_path
- Pathname.new(Bundler.rubygems.user_home).join(".bundle")
+ Pathname.new(user_home).join(".bundle")
end
def home
@@ -257,6 +293,11 @@ EOF
with_clean_env { Kernel.exec(*args) }
end
+ def local_platform
+ return Gem::Platform::RUBY if settings[:force_ruby_platform]
+ Gem::Platform.local
+ end
+
def default_gemfile
SharedHelpers.default_gemfile
end
@@ -328,17 +369,23 @@ EOF
def sudo(str)
SUDO_MUTEX.synchronize do
prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " "
- Your user account isn't allowed to install to the system Rubygems.
+ Your user account isn't allowed to install to the system RubyGems.
You can cancel this installation and run:
bundle install --path vendor/bundle
to install the gems into ./vendor/bundle/, or you can enter your password
- and install the bundled gems to Rubygems using sudo.
+ and install the bundled gems to RubyGems using sudo.
Password:
PROMPT
+ unless @prompted_for_sudo ||= system(%(sudo -k -p "#{prompt}" true))
+ raise SudoNotPermittedError,
+ "Bundler requires sudo access to install at the moment. " \
+ "Try installing again, granting Bundler sudo access when prompted, or installing into a different path."
+ end
+
`sudo -p "#{prompt}" #{str}`
end
end
@@ -389,6 +436,10 @@ EOF
@git_present = Bundler.which("git") || Bundler.which("git.exe")
end
+ def feature_flag
+ @feature_flag ||= FeatureFlag.new(VERSION)
+ end
+
def reset!
@root = nil
@settings = nil
@@ -398,6 +449,7 @@ EOF
@locked_gems = nil
@bundle_path = nil
@bin_path = nil
+ @user_home = nil
Plugin.reset!
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 0453480401..f92ecb1c33 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -36,8 +36,10 @@ module Bundler
ensure
self.options ||= {}
Bundler.settings.cli_flags_given = !options.empty?
+ unprinted_warnings = Bundler.ui.unprinted_warnings
Bundler.ui = UI::Shell.new(options)
Bundler.ui.level = "debug" if options["verbose"]
+ unprinted_warnings.each {|w| Bundler.ui.warn(w) }
if ENV["RUBYGEMS_GEMDEPS"] && !ENV["RUBYGEMS_GEMDEPS"].empty?
Bundler.ui.warn(
@@ -92,7 +94,7 @@ module Bundler
end
def self.handle_no_command_error(command, has_namespace = $thor_runner)
- if Bundler.settings[:plugins] && Bundler::Plugin.command?(command)
+ if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command)
return Bundler::Plugin.exec_command(command, ARGV[1..-1])
end
@@ -184,11 +186,9 @@ module Bundler
map "i" => "install"
def install
require "bundler/cli/install"
- no_install = Bundler.settings[:no_install]
- Bundler.settings[:no_install] = false if no_install == true
- Install.new(options.dup).run
- ensure
- Bundler.settings[:no_install] = no_install unless no_install.nil?
+ Bundler.settings.temporary(:no_install => false) do
+ Install.new(options.dup).run
+ end
end
desc "update [OPTIONS]", "update the current environment"
@@ -255,7 +255,7 @@ module Bundler
"Overwrite existing binstubs if they exist"
method_option "path", :type => :string, :lazy_default => "bin", :banner =>
"Binstub destination directory (default bin)"
- method_option "standalone", :type => :array, :lazy_default => [], :banner =>
+ method_option "standalone", :type => :boolean, :banner =>
"Make binstubs that can work without the Bundler runtime"
def binstubs(*gems)
require "bundler/cli/binstubs"
@@ -269,6 +269,8 @@ module Bundler
versions of the given gems. Prerelease gems are ignored by default. If your gems
are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1.
D
+ method_option "group", :aliases => "--group", :type => :string, :banner => "List gems from a specific group"
+ method_option "groups", :aliases => "--groups", :type => :boolean, :banner => "List gems organized by groups"
method_option "local", :type => :boolean, :banner =>
"Do not attempt to fetch gems remotely and use the gem cache instead"
method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems"
@@ -443,7 +445,7 @@ module Bundler
end
desc "lock", "Creates a lockfile without installing"
- method_option "update", :type => :array, :lazy_default => [], :banner =>
+ method_option "update", :type => :array, :lazy_default => true, :banner =>
"ignore the existing lockfile, update all gems by default, or update list of given gems"
method_option "local", :type => :boolean, :default => false, :banner =>
"do not attempt to fetch remote gemspecs and use the local gem cache only"
@@ -457,6 +459,14 @@ module Bundler
"add a new platform to the lockfile"
method_option "remove-platform", :type => :array, :default => [], :banner =>
"remove a platform from the lockfile"
+ method_option "patch", :type => :boolean, :hide => true, :banner =>
+ "Prefer updating only to next patch version"
+ method_option "minor", :type => :boolean, :hide => true, :banner =>
+ "Prefer updating only to next minor version"
+ method_option "major", :type => :boolean, :hide => true, :banner =>
+ "Prefer updating to next major version (default)"
+ method_option "strict", :type => :boolean, :hide => true, :banner =>
+ "Do not allow any gem to be updated past latest --patch/--minor/--major"
def lock
require "bundler/cli/lock"
Lock.new(options).run
@@ -482,7 +492,7 @@ module Bundler
Doctor.new(options).run
end
- if Bundler.settings[:plugins]
+ if Bundler.feature_flag.plugins?
require "bundler/cli/plugin"
desc "plugin SUBCOMMAND ...ARGS", "manage the bundler plugins"
subcommand "plugin", Plugin
diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb
index f7a27b01bb..95103b7dd8 100644
--- a/lib/bundler/cli/binstubs.rb
+++ b/lib/bundler/cli/binstubs.rb
@@ -10,7 +10,7 @@ module Bundler
end
def run
- Bundler.definition.validate_ruby!
+ Bundler.definition.validate_runtime!
Bundler.settings[:bin] = options["path"] if options["path"]
Bundler.settings[:bin] = nil if options["path"] && options["path"].empty?
installer = Installer.new(Bundler.root, Bundler.definition)
diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb
index c8c63e926c..5ba105a31d 100644
--- a/lib/bundler/cli/cache.rb
+++ b/lib/bundler/cli/cache.rb
@@ -7,7 +7,7 @@ module Bundler
end
def run
- Bundler.definition.validate_ruby!
+ Bundler.definition.validate_runtime!
Bundler.definition.resolve_with_cache!
setup_cache_all
Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms")
diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb
index 738d40b622..3f504ff621 100644
--- a/lib/bundler/cli/check.rb
+++ b/lib/bundler/cli/check.rb
@@ -15,7 +15,7 @@ module Bundler
begin
definition = Bundler.definition
- definition.validate_ruby!
+ definition.validate_runtime!
not_installed = definition.missing_specs
rescue GemNotFound, VersionConflict
Bundler.ui.error "Bundler can't satisfy your Gemfile's dependencies."
diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb
index 491ea04a11..6f45322db8 100644
--- a/lib/bundler/cli/common.rb
+++ b/lib/bundler/cli/common.rb
@@ -52,5 +52,14 @@ module Bundler
message += "\nDid you mean #{suggestions}?" if suggestions
message
end
+
+ def self.config_gem_version_promoter(definition, opts)
+ patch_level = [:major, :minor, :patch].select {|v| opts.keys.include?(v.to_s) }
+ raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1
+ definition.gem_version_promoter.tap do |gvp|
+ gvp.level = patch_level.first || :major
+ gvp.strict = opts[:strict]
+ end
+ end
end
end
diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb
index 8fd862a1c2..728662024b 100644
--- a/lib/bundler/cli/doctor.rb
+++ b/lib/bundler/cli/doctor.rb
@@ -14,11 +14,11 @@ module Bundler
end
def otool_available?
- system("otool --version 2>&1 >#{Bundler::NULL}")
+ system("otool --version 2>#{Bundler::NULL} >#{Bundler::NULL}")
end
def ldd_available?
- !system("ldd --help 2>&1 >#{Bundler::NULL}").nil?
+ !system("ldd --help 2>#{Bundler::NULL} >#{Bundler::NULL}").nil?
end
def dylibs_darwin(path)
@@ -55,22 +55,18 @@ module Bundler
Dir.glob("#{spec.full_gem_path}/**/*.bundle")
end
+ def check!
+ require "bundler/cli/check"
+ Bundler::CLI::Check.new({}).run
+ end
+
def run
Bundler.ui.level = "error" if options[:quiet]
+ check!
+ definition = Bundler.definition
broken_links = {}
- begin
- definition = Bundler.definition
- definition.validate_ruby!
- not_installed = definition.missing_specs
- raise GemNotFound if not_installed.any?
- rescue GemNotFound
- Bundler.ui.warn "This bundle's gems must be installed to run this command."
- Bundler.ui.warn "Install missing gems with `bundle install`."
- exit 0
- end
-
definition.specs.each do |spec|
bundles_for_gem(spec).each do |bundle|
bad_paths = dylibs(bundle).select {|f| !File.exist?(f) }
@@ -82,13 +78,15 @@ module Bundler
end
if broken_links.any?
- Bundler.ui.error "The following gems are missing OS dependencies"
- broken_links.each do |spec, paths|
- paths.uniq.each do |path|
- Bundler.ui.error " * #{spec.name}: #{path}"
+ message = "The following gems are missing OS dependencies:"
+ broken_links.map do |spec, paths|
+ paths.uniq.map do |path|
+ "\n * #{spec.name}: #{path}"
end
- end
- exit 1
+ end.flatten.sort.each {|m| message += m }
+ raise ProductionError, message
+ else
+ Bundler.ui.info "No issues found with the installed bundle"
end
end
end
diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb
index 27f4262e30..4dc0dbdb6b 100644
--- a/lib/bundler/cli/gem.rb
+++ b/lib/bundler/cli/gem.rb
@@ -44,7 +44,8 @@ module Bundler
:test => options[:test],
:ext => options[:ext],
:exe => options[:exe],
- :bundler_version => bundler_dependency_version
+ :bundler_version => bundler_dependency_version,
+ :git_user_name => git_user_name.empty? ? "[USERNAME]" : git_user_name
}
ensure_safe_gem_name(name, constant_array)
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
index 5c7b8c5b0b..f66716b39d 100644
--- a/lib/bundler/cli/install.rb
+++ b/lib/bundler/cli/install.rb
@@ -60,13 +60,10 @@ module Bundler
"the --binstubs option will be removed in favor of `bundle binstubs`"
end
- # rubygems plugins sometimes hook into the gem install process
- Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins)
-
- Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
definition = Bundler.definition
- definition.validate_ruby!
+ definition.validate_runtime!
installer = Installer.install(Bundler.root, definition, options)
Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.settings[:frozen]
diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb
index a6a95f895c..eb47c9efb0 100644
--- a/lib/bundler/cli/lock.rb
+++ b/lib/bundler/cli/lock.rb
@@ -1,4 +1,6 @@
# frozen_string_literal: true
+require "bundler/cli/common"
+
module Bundler
class CLI::Lock
attr_reader :options
@@ -17,14 +19,13 @@ module Bundler
ui = Bundler.ui
Bundler.ui = UI::Silent.new if print
- gems = options[:update]
Bundler::Fetcher.disable_endpoint = options["full-index"]
- if gems && !gems.empty?
- definition = Bundler.definition(:gems => gems)
- else
- definition = Bundler.definition(true)
- end
+ update = options[:update]
+ update = { :gems => update } if update.is_a?(Array)
+ definition = Bundler.definition(update)
+
+ Bundler::CLI::Common.config_gem_version_promoter(Bundler.definition, options) if options[:update]
options["remove-platform"].each do |platform|
definition.remove_platform(platform)
diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb
index 09b2d71453..5729c07ffe 100644
--- a/lib/bundler/cli/outdated.rb
+++ b/lib/bundler/cli/outdated.rb
@@ -4,6 +4,7 @@ require "bundler/cli/common"
module Bundler
class CLI::Outdated
attr_reader :options, :gems
+
def initialize(options, gems)
@options = options
@gems = gems
@@ -18,7 +19,7 @@ module Bundler
Bundler::CLI::Common.select_spec(gem_name)
end
- Bundler.definition.validate_ruby!
+ Bundler.definition.validate_runtime!
current_specs = Bundler.ui.silence { Bundler.load.specs }
current_dependencies = {}
Bundler.ui.silence { Bundler.load.dependencies.each {|dep| current_dependencies[dep.name] = dep } }
@@ -30,7 +31,7 @@ module Bundler
Bundler.definition(:gems => gems, :sources => sources)
end
- definition_resolution = proc { options["local"] ? definition.resolve_with_cache! : definition.resolve_remotely! }
+ definition_resolution = proc { options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely! }
if options[:parseable]
Bundler.ui.silence(&definition_resolution)
else
@@ -38,8 +39,9 @@ module Bundler
end
Bundler.ui.info ""
+ outdated_gems_by_groups = {}
+ outdated_gems_list = []
- out_count = 0
# Loop through the current specs
gemfile_specs, dependency_specs = current_specs.partition {|spec| current_dependencies.key? spec.name }
[gemfile_specs.sort_by(&:name), dependency_specs.sort_by(&:name)].flatten.each do |current_spec|
@@ -47,7 +49,7 @@ module Bundler
dependency = current_dependencies[current_spec.name]
- if options["strict"]
+ if options[:strict]
active_spec = definition.specs.detect {|spec| spec.name == current_spec.name && spec.platform == current_spec.platform }
else
active_specs = definition.index[current_spec.name].select {|spec| spec.platform == current_spec.platform }.sort_by(&:version)
@@ -67,47 +69,89 @@ module Bundler
gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
git_outdated = current_spec.git_version != active_spec.git_version
if gem_outdated || git_outdated
- unless options[:parseable]
- if out_count == 0
- if options["pre"]
- Bundler.ui.info "Outdated gems included in the bundle (including pre-releases):"
- else
- Bundler.ui.info "Outdated gems included in the bundle:"
- end
- end
- end
-
- spec_version = "#{active_spec.version}#{active_spec.git_version}"
- current_version = "#{current_spec.version}#{current_spec.git_version}"
- dependency_version = %(, requested #{dependency.requirement}) if dependency && dependency.specific?
-
+ groups = nil
if dependency && !options[:parseable]
groups = dependency.groups.join(", ")
- pl = (dependency.groups.length > 1) ? "s" : ""
- groups = " in group#{pl} \"#{groups}\""
end
- spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, installed #{current_version}#{dependency_version})"
- if options[:parseable]
- Bundler.ui.info spec_outdated_info.to_s.rstrip
- else
- Bundler.ui.info " * #{spec_outdated_info}#{groups}".rstrip
- end
+ outdated_gems_list << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
- out_count += 1
+ outdated_gems_by_groups[groups] ||= []
+ outdated_gems_by_groups[groups] << { :active_spec => active_spec,
+ :current_spec => current_spec,
+ :dependency => dependency,
+ :groups => groups }
end
+
Bundler.ui.debug "from #{active_spec.loaded_from}"
end
- if out_count.zero?
+ if outdated_gems_list.empty?
Bundler.ui.info "Bundle up to date!\n" unless options[:parseable]
else
+ unless options[:parseable]
+ if options[:pre]
+ Bundler.ui.info "Outdated gems included in the bundle (including pre-releases):"
+ else
+ Bundler.ui.info "Outdated gems included in the bundle:"
+ end
+ end
+
+ options_include_groups = [:group, :groups].select {|v| options.keys.include?(v.to_s) }
+ if options_include_groups.any?
+ ordered_groups = outdated_gems_by_groups.keys.compact.sort
+ [nil, ordered_groups].flatten.each do |groups|
+ gems = outdated_gems_by_groups[groups]
+ contains_group = if groups
+ groups.split(",").include?(options[:group])
+ else
+ options[:group] == "group"
+ end
+
+ next if (!options[:groups] && !contains_group) || gems.nil?
+
+ unless options[:parseable]
+ if groups
+ Bundler.ui.info "===== Group #{groups} ====="
+ else
+ Bundler.ui.info "===== Without group ====="
+ end
+ end
+
+ gems.each do |gem|
+ print_gem(gem[:current_spec], gem[:active_spec], gem[:dependency], groups, options_include_groups.any?)
+ end
+ end
+ else
+ outdated_gems_list.each do |gem|
+ print_gem(gem[:current_spec], gem[:active_spec], gem[:dependency], gem[:groups], options_include_groups.any?)
+ end
+ end
+
exit 1
end
end
private
+ def print_gem(current_spec, active_spec, dependency, groups, options_include_groups)
+ spec_version = "#{active_spec.version}#{active_spec.git_version}"
+ current_version = "#{current_spec.version}#{current_spec.git_version}"
+ dependency_version = %(, requested #{dependency.requirement}) if dependency && dependency.specific?
+
+ spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, installed #{current_version}#{dependency_version})"
+ if options[:parseable]
+ Bundler.ui.info spec_outdated_info.to_s.rstrip
+ elsif options_include_groups || !groups
+ Bundler.ui.info " * #{spec_outdated_info}".rstrip
+ else
+ Bundler.ui.info " * #{spec_outdated_info} in groups \"#{groups}\"".rstrip
+ end
+ end
+
def check_for_deployment_mode
if Bundler.settings[:frozen]
error_message = "You are trying to check outdated gems in deployment mode. " \
@@ -123,7 +167,6 @@ module Bundler
active_major = active_spec.version.segments.first
update_present = false
-
update_present = active_major > current_major if options[:major]
if !update_present && (options[:minor] || options[:patch]) && current_major == active_major
diff --git a/lib/bundler/cli/platform.rb b/lib/bundler/cli/platform.rb
index b5f906bfd9..9fdab0a53c 100644
--- a/lib/bundler/cli/platform.rb
+++ b/lib/bundler/cli/platform.rb
@@ -29,7 +29,7 @@ module Bundler
output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}"
begin
- Bundler.definition.validate_ruby!
+ Bundler.definition.validate_runtime!
output << "Your current platform satisfies the Ruby version requirement."
rescue RubyVersionMismatch => e
output << e.message
diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb
index d67b086dd1..77e845a603 100644
--- a/lib/bundler/cli/show.rb
+++ b/lib/bundler/cli/show.rb
@@ -13,7 +13,7 @@ module Bundler
def run
Bundler.ui.silence do
- Bundler.definition.validate_ruby!
+ Bundler.definition.validate_runtime!
Bundler.load.lock
end
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
index 1c35659e0b..51de98bf34 100644
--- a/lib/bundler/cli/update.rb
+++ b/lib/bundler/cli/update.rb
@@ -1,4 +1,6 @@
# frozen_string_literal: true
+require "bundler/cli/common"
+
module Bundler
class CLI::Update
attr_reader :options, :gems
@@ -10,7 +12,7 @@ module Bundler
def run
Bundler.ui.level = "error" if options[:quiet]
- Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins]
+ Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
sources = Array(options[:source])
groups = Array(options[:group]).map(&:to_sym)
@@ -27,7 +29,6 @@ module Bundler
names = Bundler.locked_gems.specs.map(&:name)
gems.each do |g|
next if names.include?(g)
- require "bundler/cli/common"
raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(g, names)
end
@@ -39,12 +40,7 @@ module Bundler
Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby])
end
- patch_level = [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) }
- raise ProductionError, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1
- Bundler.definition.gem_version_promoter.tap do |gvp|
- gvp.level = patch_level.first || :major
- gvp.strict = options[:strict]
- end
+ Bundler::CLI::Common.config_gem_version_promoter(Bundler.definition, options)
Bundler::Fetcher.disable_endpoint = options["full-index"]
@@ -54,10 +50,7 @@ module Bundler
Bundler.settings[:jobs] = opts["jobs"] if opts["jobs"]
- # rubygems plugins sometimes hook into the gem install process
- Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins)
-
- Bundler.definition.validate_ruby!
+ Bundler.definition.validate_runtime!
Installer.install Bundler.root, Bundler.definition, opts
Bundler.load.cache if Bundler.app_cache.exist?
diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb
new file mode 100644
index 0000000000..1ff3deda01
--- /dev/null
+++ b/lib/bundler/compact_index_client.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+require "pathname"
+require "set"
+
+module Bundler
+ class CompactIndexClient
+ DEBUG_MUTEX = Mutex.new
+ def self.debug
+ return unless ENV["DEBUG_COMPACT_INDEX"]
+ DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
+ end
+
+ class Error < StandardError; end
+
+ require "bundler/compact_index_client/cache"
+ require "bundler/compact_index_client/updater"
+
+ attr_reader :directory
+
+ # @return [Lambda] A lambda that takes an array of inputs and a block, and
+ # maps the inputs with the block in parallel.
+ #
+ attr_accessor :in_parallel
+
+ def initialize(directory, fetcher)
+ @directory = Pathname.new(directory)
+ @updater = Updater.new(fetcher)
+ @cache = Cache.new(@directory)
+ @endpoints = Set.new
+ @info_checksums_by_name = {}
+ @in_parallel = lambda do |inputs, &blk|
+ inputs.map(&blk)
+ end
+ end
+
+ def names
+ Bundler::CompactIndexClient.debug { "/names" }
+ update(@cache.names_path, "names")
+ @cache.names
+ end
+
+ def versions
+ Bundler::CompactIndexClient.debug { "/versions" }
+ update(@cache.versions_path, "versions")
+ versions, @info_checksums_by_name = @cache.versions
+ versions
+ end
+
+ def dependencies(names)
+ Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
+ in_parallel.call(names) do |name|
+ update_info(name)
+ @cache.dependencies(name).map {|d| d.unshift(name) }
+ end.flatten(1)
+ end
+
+ def spec(name, version, platform = nil)
+ Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" }
+ update_info(name)
+ @cache.specific_dependency(name, version, platform)
+ end
+
+ def update_and_parse_checksums!
+ Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" }
+ return @info_checksums_by_name if @parsed_checksums
+ update(@cache.versions_path, "versions")
+ @info_checksums_by_name = @cache.checksums
+ @parsed_checksums = true
+ end
+
+ private
+
+ def update(local_path, remote_path)
+ Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" }
+ unless @endpoints.add?(remote_path)
+ Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" }
+ return
+ end
+ @updater.update(local_path, url(remote_path))
+ end
+
+ def update_info(name)
+ Bundler::CompactIndexClient.debug { "update_info(#{name})" }
+ path = @cache.info_path(name)
+ checksum = @updater.checksum_for_file(path)
+ unless existing = @info_checksums_by_name[name]
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" }
+ return
+ end
+ if checksum == existing
+ Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" }
+ return
+ end
+ Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" }
+ update(path, "info/#{name}")
+ end
+
+ def url(path)
+ path
+ end
+ end
+end
diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb
new file mode 100644
index 0000000000..e44f05dc7e
--- /dev/null
+++ b/lib/bundler/compact_index_client/cache.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+require "digest/md5"
+
+module Bundler
+ class CompactIndexClient
+ class Cache
+ attr_reader :directory
+
+ def initialize(directory)
+ @directory = Pathname.new(directory).expand_path
+ info_roots.each do |dir|
+ SharedHelpers.filesystem_access(dir) do
+ FileUtils.mkdir_p(dir)
+ end
+ end
+ end
+
+ def names
+ lines(names_path)
+ end
+
+ def names_path
+ directory.join("names")
+ end
+
+ def versions
+ versions_by_name = Hash.new {|hash, key| hash[key] = [] }
+ info_checksums_by_name = {}
+
+ lines(versions_path).each do |line|
+ name, versions_string, info_checksum = line.split(" ", 3)
+ info_checksums_by_name[name] = info_checksum || ""
+ versions_string.split(",").each do |version|
+ if version.start_with?("-")
+ version = version[1..-1].split("-", 2).unshift(name)
+ versions_by_name[name].delete(version)
+ else
+ version = version.split("-", 2).unshift(name)
+ versions_by_name[name] << version
+ end
+ end
+ end
+
+ [versions_by_name, info_checksums_by_name]
+ end
+
+ def versions_path
+ directory.join("versions")
+ end
+
+ def checksums
+ checksums = {}
+
+ lines(versions_path).each do |line|
+ name, _, checksum = line.split(" ", 3)
+ checksums[name] = checksum
+ end
+
+ checksums
+ end
+
+ def dependencies(name)
+ lines(info_path(name)).map do |line|
+ parse_gem(line)
+ end
+ end
+
+ def info_path(name)
+ name = name.to_s
+ if name =~ /[^a-z0-9_-]/
+ name += "-#{Digest::MD5.hexdigest(name).downcase}"
+ info_roots.last.join(name)
+ else
+ info_roots.first.join(name)
+ end
+ end
+
+ def specific_dependency(name, version, platform)
+ pattern = [version, platform].compact.join("-")
+ return nil if pattern.empty?
+
+ gem_lines = info_path(name).read
+ gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0]
+ gem_line ? parse_gem(gem_line) : nil
+ end
+
+ private
+
+ def lines(path)
+ return [] unless path.file?
+ lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n")
+ header = lines.index("---")
+ header ? lines[header + 1..-1] : lines
+ end
+
+ def parse_gem(string)
+ version_and_platform, rest = string.split(" ", 2)
+ version, platform = version_and_platform.split("-", 2)
+ dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
+ dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
+ requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
+ [version, platform, dependencies, requirements]
+ end
+
+ def parse_dependency(string)
+ dependency = string.split(":")
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
+ dependency
+ end
+
+ def info_roots
+ [
+ directory.join("info"),
+ directory.join("info-special-characters"),
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb
new file mode 100644
index 0000000000..b407c64039
--- /dev/null
+++ b/lib/bundler/compact_index_client/updater.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+require "fileutils"
+require "stringio"
+require "tmpdir"
+require "zlib"
+
+module Bundler
+ class CompactIndexClient
+ class Updater
+ class MisMatchedChecksumError < Error
+ def initialize(path, server_checksum, local_checksum)
+ @path = path
+ @server_checksum = server_checksum
+ @local_checksum = local_checksum
+ end
+
+ def message
+ "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \
+ "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})."
+ end
+ end
+
+ def initialize(fetcher)
+ @fetcher = fetcher
+ end
+
+ def update(local_path, remote_path, retrying = nil)
+ headers = {}
+
+ Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir|
+ local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename)
+
+ # first try to fetch any new bytes on the existing file
+ if retrying.nil? && local_path.file?
+ FileUtils.cp local_path, local_temp_path
+ headers["If-None-Match"] = etag_for(local_temp_path)
+ headers["Range"] = "bytes=#{local_temp_path.size}-"
+ else
+ # Fastly ignores Range when Accept-Encoding: gzip is set
+ headers["Accept-Encoding"] = "gzip"
+ end
+
+ response = @fetcher.call(remote_path, headers)
+ return nil if response.is_a?(Net::HTTPNotModified)
+
+ content = response.body
+ if response["Content-Encoding"] == "gzip"
+ content = Zlib::GzipReader.new(StringIO.new(content)).read
+ end
+
+ mode = response.is_a?(Net::HTTPPartialContent) ? "a" : "w"
+ SharedHelpers.filesystem_access(local_temp_path) do
+ local_temp_path.open(mode) {|f| f << content }
+ end
+
+ response_etag = response["ETag"].gsub(%r{\AW/}, "")
+ if etag_for(local_temp_path) == response_etag
+ SharedHelpers.filesystem_access(local_path) do
+ FileUtils.mv(local_temp_path, local_path)
+ end
+ return nil
+ end
+
+ if retrying
+ raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path))
+ end
+
+ update(local_path, remote_path, :retrying)
+ end
+ end
+
+ def etag_for(path)
+ sum = checksum_for_file(path)
+ sum ? %("#{sum}") : nil
+ end
+
+ def checksum_for_file(path)
+ return nil unless path.file?
+ # This must use IO.read instead of Digest.file().hexdigest
+ # because we need to preserve \n line endings on windows when calculating
+ # the checksum
+ SharedHelpers.filesystem_access(path, :read) do
+ Digest::MD5.hexdigest(IO.read(path))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb
index 6180285942..7b3d87e320 100644
--- a/lib/bundler/current_ruby.rb
+++ b/lib/bundler/current_ruby.rb
@@ -57,15 +57,15 @@ module Bundler
end
def mswin64?
- Bundler::WINDOWS && Gem::Platform.local.os == "mswin64" && Gem::Platform.local.cpu == "x64"
+ Bundler::WINDOWS && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64"
end
def mingw?
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu != "x64"
+ Bundler::WINDOWS && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64"
end
def x64_mingw?
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu == "x64"
+ Bundler::WINDOWS && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64"
end
(KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version|
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index f14085f60a..8a6bf0d17c 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -62,6 +62,7 @@ module Bundler
@specs = nil
@ruby_version = ruby_version
+ @lockfile = lockfile
@lockfile_contents = String.new
@locked_bundler_version = nil
@locked_ruby_version = nil
@@ -69,7 +70,8 @@ module Bundler
if lockfile && File.exist?(lockfile)
@lockfile_contents = Bundler.read_file(lockfile)
@locked_gems = LockfileParser.new(@lockfile_contents)
- @platforms = @locked_gems.platforms
+ @locked_platforms = @locked_gems.platforms
+ @platforms = @locked_platforms.dup
@locked_bundler_version = @locked_gems.bundler_version
@locked_ruby_version = @locked_gems.ruby_version
@@ -90,21 +92,17 @@ module Bundler
@locked_deps = []
@locked_specs = SpecSet.new([])
@locked_sources = []
+ @locked_platforms = []
end
@unlock[:gems] ||= []
@unlock[:sources] ||= []
- @unlock[:ruby] ||= if @ruby_version && @locked_ruby_version
- unless locked_ruby_version_object = RubyVersion.from_string(@locked_ruby_version)
- raise LockfileError, "Failed to create a `RubyVersion` object from " \
- "`#{@locked_ruby_version}` found in #{lockfile} -- try running `bundle update --ruby`."
- end
+ @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
@ruby_version.diff(locked_ruby_version_object)
end
@unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version)
- current_platform = Bundler.rubygems.platforms.map {|p| generic(p) }.compact.last
- add_platform(current_platform)
+ add_current_platform unless Bundler.settings[:frozen]
@path_changes = converge_paths
eager_unlock = expand_dependencies(@unlock[:gems])
@@ -247,7 +245,7 @@ module Bundler
else
# Run a resolve against the locally available gems
Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
- last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, ruby_version, gem_version_promoter, additional_base_requirements_for_resolve)
+ last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve)
end
end
end
@@ -262,6 +260,8 @@ module Bundler
dependency_names -= pinned_spec_names(source.specs)
dependency_names.concat(source.unmet_deps).uniq!
end
+ idx << Gem::Specification.new("ruby\0", RubyVersion.system.to_gem_version_with_patchlevel)
+ idx << Gem::Specification.new("rubygems\0", Gem::VERSION)
end
end
@@ -338,6 +338,18 @@ module Bundler
end
end
+ def locked_ruby_version_object
+ return unless @locked_ruby_version
+ @locked_ruby_version_object ||= begin
+ unless version = RubyVersion.from_string(@locked_ruby_version)
+ raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \
+ "#{@lockfile} could not be parsed. " \
+ "Try running bundle update --ruby to resolve this."
+ end
+ version
+ end
+ end
+
def to_lock
out = String.new
@@ -401,6 +413,11 @@ module Bundler
deleted = []
changed = []
+ new_platforms = @platforms - @locked_platforms
+ deleted_platforms = @locked_platforms - @platforms
+ added.concat new_platforms.map {|p| "* platform: #{p}" }
+ deleted.concat deleted_platforms.map {|p| "* platform: #{p}" }
+
gemfile_sources = sources.lock_sources
new_sources = gemfile_sources - @locked_sources
@@ -449,6 +466,11 @@ module Bundler
raise ProductionError, msg if added.any? || deleted.any? || changed.any?
end
+ def validate_runtime!
+ validate_ruby!
+ validate_platforms!
+ end
+
def validate_ruby!
return unless ruby_version
@@ -474,6 +496,22 @@ module Bundler
end
end
+ # TODO: refactor this so that `match_platform` can be called with two platforms
+ DummyPlatform = Struct.new(:platform)
+ class DummyPlatform; include MatchPlatform; end
+ def validate_platforms!
+ return if @platforms.any? do |bundle_platform|
+ bundle_platform = DummyPlatform.new(bundle_platform)
+ Bundler.rubygems.platforms.any? do |local_platform|
+ bundle_platform.match_platform(local_platform)
+ end
+ end
+
+ raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \
+ "but your local platforms are #{Bundler.rubygems.platforms.map(&:to_s)}, and " \
+ "there's no compatible match between those two lists."
+ end
+
def add_platform(platform)
@new_platform ||= !@platforms.include?(platform)
@platforms |= [platform]
@@ -484,6 +522,12 @@ module Bundler
raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}"
end
+ def add_current_platform
+ current_platform = Bundler.local_platform
+ add_platform(current_platform) if Bundler.settings[:specific_platform]
+ add_platform(generic(current_platform))
+ end
+
attr_reader :sources
private :sources
@@ -726,16 +770,54 @@ module Bundler
@locked_specs.any? {|s| s.satisfies?(dep) && (!dep.source || s.source.include?(dep.source)) }
end
+ # This list of dependencies is only used in #resolve, so it's OK to add
+ # the metadata dependencies here
def expanded_dependencies
- @expanded_dependencies ||= expand_dependencies(dependencies, @remote)
+ @expanded_dependencies ||= begin
+ ruby_versions = concat_ruby_version_requirements(@ruby_version)
+ if ruby_versions.empty? || !@ruby_version.exact?
+ concat_ruby_version_requirements(RubyVersion.system)
+ concat_ruby_version_requirements(locked_ruby_version_object) unless @unlock[:ruby]
+ end
+
+ metadata_dependencies = [
+ Dependency.new("ruby\0", ruby_versions),
+ Dependency.new("rubygems\0", Gem::VERSION),
+ ]
+ expand_dependencies(dependencies + metadata_dependencies, @remote)
+ end
+ end
+
+ def concat_ruby_version_requirements(ruby_version, ruby_versions = [])
+ return ruby_versions unless ruby_version
+ if ruby_version.patchlevel
+ ruby_versions << ruby_version.to_gem_version_with_patchlevel
+ else
+ ruby_versions.concat(ruby_version.versions.map do |version|
+ requirement = Gem::Requirement.new(version)
+ if requirement.exact?
+ "~> #{version}.0"
+ else
+ requirement
+ end
+ end)
+ end
end
def expand_dependencies(dependencies, remote = false)
deps = []
dependencies.each do |dep|
dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name)
- next unless remote || dep.current_platform?
- dep.gem_platforms(@platforms).each do |p|
+ next if !remote && !dep.current_platform?
+ platforms = dep.gem_platforms(@platforms)
+ if platforms.empty?
+ Bundler.ui.warn \
+ "The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \
+ "Bundler is installing for #{@platforms.join ", "} but the dependency " \
+ "is only for #{dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] }.join ", "}. " \
+ "To add those platforms to the bundle, run `bundle lock --add-platform #{dep.platforms.join ", "}`."
+ end
+ platforms.each do |p|
deps << DepProxy.new(dep, p) if remote || p == generic_local_platform
end
end
@@ -812,9 +894,10 @@ module Bundler
end
def additional_base_requirements_for_resolve
- return [] unless @locked_gems && Bundler.settings[:only_update_to_newer_versions]
+ return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions?
@locked_gems.specs.reduce({}) do |requirements, locked_spec|
- requirements[locked_spec.name] = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
+ dep = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}")
+ requirements[locked_spec.name] = DepProxy.new(dep, locked_spec.platform)
requirements
end.values
end
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
index 1b1808b40a..66162d741a 100644
--- a/lib/bundler/dependency.rb
+++ b/lib/bundler/dependency.rb
@@ -52,7 +52,7 @@ module Bundler
:x64_mingw_20 => Gem::Platform::X64_MINGW,
:x64_mingw_21 => Gem::Platform::X64_MINGW,
:x64_mingw_22 => Gem::Platform::X64_MINGW,
- :x64_mingw_23 => Gem::Platform::X64_MINGW
+ :x64_mingw_23 => Gem::Platform::X64_MINGW,
}.freeze
REVERSE_PLATFORM_MAP = {}.tap do |reverse_platform_map|
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index b064c80d4c..cdbae076f0 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -65,7 +65,7 @@ module Bundler
case specs_by_name_and_version.size
when 1
specs = specs_by_name_and_version.values.first
- spec = specs.find {|s| s.match_platform(Gem::Platform.local) } || specs.first
+ spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first
@gemspecs << spec
@@ -393,7 +393,8 @@ module Bundler
"as an option for #{command}, but it is invalid."
end
- message << " Valid options are: #{valid_keys.join(", ")}"
+ message << " Valid options are: #{valid_keys.join(", ")}."
+ message << " You may be able to resolve this by upgrading Bundler to the newest version."
raise InvalidOption, message
end
end
diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb
index 75edbf2b88..f7c6f7e83d 100644
--- a/lib/bundler/env.rb
+++ b/lib/bundler/env.rb
@@ -20,6 +20,8 @@ module Bundler
out << " GEM_PATH #{ENV["GEM_PATH"]}\n" unless ENV["GEM_PATH"] == ENV["GEM_HOME"]
out << " RVM #{ENV["rvm_version"]}\n" if ENV["rvm_version"]
out << " Git #{git_version}\n"
+ out << " Platform #{Gem::Platform.local}\n"
+ out << " OpenSSL #{OpenSSL::OPENSSL_VERSION}\n" if defined?(OpenSSL::OPENSSL_VERSION)
%w(rubygems-bundler open_gem).each do |name|
specs = Bundler.rubygems.find_name(name)
out << " #{name} (#{specs.map(&:version).join(",")})\n" unless specs.empty?
@@ -33,6 +35,8 @@ module Bundler
end
end
+ return out unless SharedHelpers.in_bundle?
+
if print_gemfile
out << "\n#{Bundler.default_gemfile.relative_path_from(SharedHelpers.pwd)}\n\n"
out << " " << read_file(Bundler.default_gemfile).gsub(/\n/, "\n ") << "\n"
diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb
index 7681ea73ae..dd5782fb3d 100644
--- a/lib/bundler/errors.rb
+++ b/lib/bundler/errors.rb
@@ -52,6 +52,7 @@ module Bundler
class CyclicDependencyError < BundlerError; status_code(21); end
class GemfileLockNotFound < BundlerError; status_code(22); end
class PluginError < BundlerError; status_code(29); end
+ class SudoNotPermittedError < BundlerError; status_code(30); end
class GemfileEvalError < GemfileError; end
class MarshalError < StandardError; end
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
new file mode 100644
index 0000000000..150cac1e67
--- /dev/null
+++ b/lib/bundler/feature_flag.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+module Bundler
+ class FeatureFlag
+ def self.settings_flag(flag, &default)
+ unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s)
+ raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key"
+ end
+ define_method("#{flag}?") do
+ value = Bundler.settings[flag]
+ value = instance_eval(&default) if value.nil? && !default.nil?
+ value
+ end
+ end
+
+ (1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } }
+
+ settings_flag(:allow_offline_install) { bundler_2_mode? }
+ settings_flag(:only_update_to_newer_versions) { bundler_2_mode? }
+ settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
+
+ def initialize(bundler_version)
+ @bundler_version = Gem::Version.create(bundler_version)
+ end
+
+ def major_version
+ @bundler_version.segments.first
+ end
+ private :major_version
+
+ class << self; private :settings_flag; end
+ end
+end
diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb
index 34391bc260..38105a3f8f 100644
--- a/lib/bundler/fetcher/compact_index.rb
+++ b/lib/bundler/fetcher/compact_index.rb
@@ -5,7 +5,7 @@ require "bundler/worker"
module Bundler
class Fetcher
class CompactIndex < Base
- require "bundler/vendor/compact_index_client/lib/compact_index_client"
+ require "bundler/compact_index_client"
def self.compact_index_request(method_name)
method = instance_method(method_name)
@@ -61,7 +61,7 @@ module Bundler
compact_index_request :fetch_spec
def available?
- user_home = Pathname.new(Bundler.rubygems.user_home)
+ user_home = Bundler.user_home
return nil unless user_home.directory? && user_home.writable?
# Read info file checksums out of /versions, so we can know if gems are up to date
fetch_uri.scheme != "file" && compact_index_client.update_and_parse_checksums!
@@ -114,7 +114,7 @@ module Bundler
def call(path, headers)
fetcher.downloader.fetch(fetcher.fetch_uri + path, headers)
rescue NetworkDownError => e
- raise unless Bundler.settings[:allow_offline_install] && headers["If-None-Match"]
+ raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"]
ui.warn "Using the cached data for the new index because of a network error: #{e}"
Net::HTTPNotModified.new(nil, nil, nil)
end
diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb
index 1cd5f9a213..445b0f2332 100644
--- a/lib/bundler/fetcher/dependency.rb
+++ b/lib/bundler/fetcher/dependency.rb
@@ -73,7 +73,7 @@ module Bundler
def dependency_api_uri(gem_names = [])
uri = fetch_uri + "api/v1/dependencies"
- uri.query = "gems=#{CGI.escape(gem_names.join(","))}" if gem_names.any?
+ uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any?
uri
end
end
diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb
index c8d714c05a..ee1aa1a972 100644
--- a/lib/bundler/fetcher/downloader.rb
+++ b/lib/bundler/fetcher/downloader.rb
@@ -38,6 +38,8 @@ module Bundler
end
def request(uri, options)
+ validate_uri_scheme!(uri)
+
Bundler.ui.debug "HTTP GET #{uri}"
req = Net::HTTP::Get.new uri.request_uri, options
if uri.user
@@ -61,6 +63,15 @@ module Bundler
raise HTTPError, "Network error while fetching #{URICredentialsFilter.credential_filtered_uri(uri)}"
end
end
+
+ private
+
+ def validate_uri_scheme!(uri)
+ return if uri.scheme =~ /\Ahttps?\z/
+ raise InvalidOption,
+ "The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \
+ "Did you mean `http` or `https`?"
+ end
end
end
end
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
index df45dd6946..c2853294a7 100644
--- a/lib/bundler/friendly_errors.rb
+++ b/lib/bundler/friendly_errors.rb
@@ -37,6 +37,11 @@ module Bundler
when Gem::InvalidSpecificationException
Bundler.ui.error error.message, :wrap => true
when SystemExit
+ when *[defined?(Java::JavaLang::OutOfMemoryError) && Java::JavaLang::OutOfMemoryError].compact
+ Bundler.ui.error "\nYour JVM has run out of memory, and Bundler cannot continue. " \
+ "You can decrease the amount of memory Bundler needs by removing gems from your Gemfile, " \
+ "especially large gems. (Gems can be as large as hundreds of megabytes, and Bundler has to read those files!). " \
+ "Alternatively, you can increase the amount of memory the JVM is able to use by running Bundler with jruby -J-Xmx1024m -S bundle (JRuby defaults to 500MB)."
else request_issue_report_for(error)
end
end
diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb
index fdb2db7dbf..73cbf9e0d1 100644
--- a/lib/bundler/gem_helper.rb
+++ b/lib/bundler/gem_helper.rb
@@ -98,7 +98,7 @@ module Bundler
allowed_push_host = @gemspec.metadata["allowed_push_host"]
gem_command += " --host #{allowed_push_host}" if allowed_push_host
end
- unless allowed_push_host || Pathname.new("~/.gem/credentials").expand_path.file?
+ unless allowed_push_host || Bundler.user_home.join(".gem/credentials").file?
raise "Your rubygems.org credentials aren't set. Run `gem push` to set them."
end
sh(gem_command)
diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb
index 5c824ffefc..955834ff01 100644
--- a/lib/bundler/gem_helpers.rb
+++ b/lib/bundler/gem_helpers.rb
@@ -25,8 +25,76 @@ module Bundler
module_function :generic
def generic_local_platform
- generic(Gem::Platform.local)
+ generic(Bundler.local_platform)
end
module_function :generic_local_platform
+
+ def platform_specificity_match(spec_platform, user_platform)
+ spec_platform = Gem::Platform.new(spec_platform)
+ return PlatformMatch::EXACT_MATCH if spec_platform == user_platform
+ return PlatformMatch::WORST_MATCH if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY
+
+ PlatformMatch.new(
+ PlatformMatch.os_match(spec_platform, user_platform),
+ PlatformMatch.cpu_match(spec_platform, user_platform),
+ PlatformMatch.platform_version_match(spec_platform, user_platform)
+ )
+ end
+ module_function :platform_specificity_match
+
+ def select_best_platform_match(specs, platform)
+ specs.select {|spec| spec.match_platform(platform) }.
+ min_by {|spec| platform_specificity_match(spec.platform, platform) }
+ end
+ module_function :select_best_platform_match
+
+ PlatformMatch = Struct.new(:os_match, :cpu_match, :platform_version_match)
+ class PlatformMatch
+ def <=>(other)
+ return nil unless other.is_a?(PlatformMatch)
+
+ m = os_match <=> other.os_match
+ return m unless m.zero?
+
+ m = cpu_match <=> other.cpu_match
+ return m unless m.zero?
+
+ m = platform_version_match <=> other.platform_version_match
+ m
+ end
+
+ EXACT_MATCH = new(-1, -1, -1).freeze
+ WORST_MATCH = new(1_000_000, 1_000_000, 1_000_000).freeze
+
+ def self.os_match(spec_platform, user_platform)
+ if spec_platform.os == user_platform.os
+ 0
+ else
+ 1
+ end
+ end
+
+ def self.cpu_match(spec_platform, user_platform)
+ if spec_platform.cpu == user_platform.cpu
+ 0
+ elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm")
+ 0
+ elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal"
+ 1
+ else
+ 2
+ end
+ end
+
+ def self.platform_version_match(spec_platform, user_platform)
+ if spec_platform.version == user_platform.version
+ 0
+ elsif spec_platform.version.nil?
+ 1
+ else
+ 2
+ end
+ end
+ end
end
end
diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb
index dcaf22944c..dec3be3e98 100644
--- a/lib/bundler/inline.rb
+++ b/lib/bundler/inline.rb
@@ -41,13 +41,13 @@ def gemfile(install = false, options = {}, &gemfile)
end
ENV["BUNDLE_GEMFILE"] ||= "Gemfile"
- Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins]
+ Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
builder = Bundler::Dsl.new
builder.instance_eval(&gemfile)
definition = builder.to_definition(nil, true)
def definition.lock(*); end
- definition.validate_ruby!
+ definition.validate_runtime!
missing_specs = proc do
begin
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
index 528dee177e..c4892ff186 100644
--- a/lib/bundler/installer.rb
+++ b/lib/bundler/installer.rb
@@ -159,6 +159,7 @@ module Bundler
# that said, it's a rare situation (other than rake), and parallel
# installation is SO MUCH FASTER. so we let people opt in.
def install(options)
+ Bundler.rubygems.load_plugins
force = options["force"]
jobs = 1
jobs = [Bundler.settings[:jobs].to_i - 1, 1].max if can_install_in_parallel?
@@ -207,7 +208,7 @@ module Bundler
end unless Bundler.bundle_path.exist?
rescue Errno::EEXIST
raise PathError, "Could not install to path `#{Bundler.settings[:path]}` " \
- "because of an invalid symlink. Remove the symlink so the directory can be created."
+ "because a file already exists at that path. Either remove or rename the file so the directory can be created."
end
def resolve_if_need(options)
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index 83ab9ef967..7508347c32 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -6,6 +6,20 @@ require "bundler/match_platform"
module Bundler
class LazySpecification
Identifier = Struct.new(:name, :version, :source, :platform, :dependencies)
+ class Identifier
+ include Comparable
+ def <=>(other)
+ return unless other.is_a?(Identifier)
+ [name, version, platform_string] <=> [other.name, other.version, other.platform_string]
+ end
+
+ protected
+
+ def platform_string
+ platform_string = platform.to_s
+ platform_string == Index::RUBY ? Index::NULL : platform_string
+ end
+ end
include MatchPlatform
@@ -55,10 +69,11 @@ module Bundler
end
def __materialize__
+ search_object = Bundler.settings[:specific_platform] || Bundler.settings[:force_ruby_platform] ? self : Dependency.new(name, version)
@specification = if source.is_a?(Source::Gemspec) && source.gemspec.name == name
source.gemspec.tap {|s| s.source = source }
else
- source.specs.search(Gem::Dependency.new(name, version)).last
+ source.specs.search(search_object).last
end
end
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index 063a1887fa..51148ab614 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -92,7 +92,7 @@ module Bundler
end
end
@sources << @rubygems_aggregate
- @specs = @specs.values
+ @specs = @specs.values.sort_by(&:identifier)
warn_for_outdated_bundler_version
rescue ArgumentError => e
Bundler.ui.debug(e)
diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb
index fed418b593..0a4e4c7e3a 100644
--- a/lib/bundler/match_platform.rb
+++ b/lib/bundler/match_platform.rb
@@ -8,7 +8,8 @@ module Bundler
def match_platform(p)
Gem::Platform::RUBY == platform ||
platform.nil? || p == platform ||
- generic(Gem::Platform.new(platform)) === p
+ generic(Gem::Platform.new(platform)) === p ||
+ Gem::Platform.new(platform) === p
end
end
end
diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb
index 06f0317786..e700ae71f1 100644
--- a/lib/bundler/plugin.rb
+++ b/lib/bundler/plugin.rb
@@ -62,7 +62,9 @@ module Bundler
save_plugins plugins, installed_specs, builder.inferred_plugins
rescue => e
- Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
+ unless e.is_a?(GemfileError)
+ Bundler.ui.error "Failed to install plugin: #{e.message}\n #{e.backtrace[0]}"
+ end
raise
end
@@ -158,7 +160,7 @@ module Bundler
#
# @param [String] event
def hook(event, *args, &arg_blk)
- return unless Bundler.settings[:plugins]
+ return unless Bundler.feature_flag.plugins?
plugins = index.hook_plugins(event)
return unless plugins.any?
diff --git a/lib/bundler/postit_trampoline.rb b/lib/bundler/postit_trampoline.rb
index dbb23aa4d9..2a22489954 100644
--- a/lib/bundler/postit_trampoline.rb
+++ b/lib/bundler/postit_trampoline.rb
@@ -1,13 +1,18 @@
# frozen_string_literal: true
-if ENV["BUNDLE_ENABLE_TRAMPOLINE"]
- module BundlerVendoredPostIt; end
- require "bundler/vendor/postit/lib/postit"
- require "rubygems"
+module BundlerVendoredPostIt; end
+require "bundler/vendor/postit/lib/postit"
+require "rubygems"
- environment = BundlerVendoredPostIt::PostIt::Environment.new([])
- version = Gem::Requirement.new(environment.bundler_version)
+environment = BundlerVendoredPostIt::PostIt::Environment.new([])
+version = Gem::Requirement.new(environment.bundler_version)
+if version.requirements.size == 1 && version.requirements.first.first == "=" # version.exact?
+ if version.requirements.first.last.segments.first >= 2
+ ENV["BUNDLE_ENABLE_TRAMPOLINE"] = "true"
+ end
+end
+if ENV["BUNDLE_ENABLE_TRAMPOLINE"] && !ENV["BUNDLE_DISABLE_POSTIT"]
installed_version =
if defined?(Bundler::VERSION)
Bundler::VERSION
@@ -65,4 +70,4 @@ You're running Bundler #{installed_version} but this project uses #{running_vers
abort "The running bundler (#{running_version}) does not match the required `#{version}`"
end
-end # unless ENV["BUNDLE_ENABLE_TRAMPOLINE"]
+end # if ENV["BUNDLE_ENABLE_TRAMPOLINE"] && !ENV["BUNDLE_DISABLE_POSTIT"]
diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb
index 6a02897c63..112c7f97fe 100644
--- a/lib/bundler/remote_specification.rb
+++ b/lib/bundler/remote_specification.rb
@@ -81,5 +81,10 @@ module Bundler
def method_missing(method, *args, &blk)
_remote_specification.send(method, *args, &blk)
end
+
+ def respond_to?(method, include_all = false)
+ super || _remote_specification.respond_to?(method, include_all)
+ end
+ public :respond_to?
end
end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index 10d5404028..b8016b37a9 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -66,48 +66,39 @@ module Bundler
end
end
- ALL = Bundler::Dependency::PLATFORM_MAP.values.uniq.freeze
-
class SpecGroup < Array
include GemHelpers
- attr_reader :activated, :required_by
+ attr_reader :activated
def initialize(a)
super
- @required_by = []
- @activated = []
+ @required_by = []
+ @activated_platforms = []
@dependencies = nil
- @specs = {}
-
- ALL.each do |p|
- @specs[p] = reverse.find {|s| s.match_platform(p) }
+ @specs = Hash.new do |specs, platform|
+ specs[platform] = select_best_platform_match(self, platform)
end
end
def initialize_copy(o)
super
- @required_by = o.required_by.dup
- @activated = o.activated.dup
+ @activated_platforms = o.activated.dup
end
def to_specs
- specs = {}
-
- @activated.each do |p|
+ @activated_platforms.map do |p|
next unless s = @specs[p]
- platform = generic(Gem::Platform.new(s.platform))
- next if specs[platform]
-
- lazy_spec = LazySpecification.new(name, version, platform, source)
+ lazy_spec = LazySpecification.new(name, version, s.platform, source)
lazy_spec.dependencies.replace s.dependencies
- specs[platform] = lazy_spec
- end
- specs.values
+ lazy_spec
+ end.compact
end
def activate_platform!(platform)
- @activated << platform if !@activated.include?(platform) && for?(platform, nil)
+ return unless for?(platform)
+ return if @activated_platforms.include?(platform)
+ @activated_platforms << platform
end
def name
@@ -122,17 +113,9 @@ module Bundler
@source ||= first.source
end
- def for?(platform, ruby_version)
+ def for?(platform)
spec = @specs[platform]
- return false unless spec
-
- return true if ruby_version.nil?
- # Only allow endpoint specifications since they won't hit the network to
- # fetch the full gemspec when calling required_ruby_version
- return true if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
- return true if spec.required_ruby_version.nil?
-
- spec.required_ruby_version.satisfied_by?(ruby_version.to_gem_version_with_patchlevel)
+ !spec.nil?
end
def to_s
@@ -140,7 +123,11 @@ module Bundler
end
def dependencies_for_activated_platforms
- @activated.map {|p| __dependencies[p] }.flatten
+ dependencies = @activated_platforms.map {|p| __dependencies[p] }
+ metadata_dependencies = @activated_platforms.map do |platform|
+ metadata_dependencies(@specs[platform], platform)
+ end
+ dependencies.concat(metadata_dependencies).flatten
end
def platforms_for_dependency_named(dependency)
@@ -150,18 +137,31 @@ module Bundler
private
def __dependencies
- @dependencies ||= begin
- dependencies = {}
- ALL.each do |p|
- next unless spec = @specs[p]
- dependencies[p] = []
+ @dependencies = Hash.new do |dependencies, platform|
+ dependencies[platform] = []
+ if spec = @specs[platform]
spec.dependencies.each do |dep|
next if dep.type == :development
- dependencies[p] << DepProxy.new(dep, p)
+ dependencies[platform] << DepProxy.new(dep, platform)
end
end
- dependencies
+ dependencies[platform]
+ end
+ end
+
+ def metadata_dependencies(spec, platform)
+ return [] unless spec
+ # Only allow endpoint specifications since they won't hit the network to
+ # fetch the full gemspec when calling required_ruby_version
+ return [] if !spec.is_a?(EndpointSpecification) && !spec.is_a?(Gem::Specification)
+ dependencies = []
+ if !spec.required_ruby_version.nil? && !spec.required_ruby_version.none?
+ dependencies << DepProxy.new(Gem::Dependency.new("ruby\0", spec.required_ruby_version), platform)
end
+ if !spec.required_rubygems_version.nil? && !spec.required_rubygems_version.none?
+ dependencies << DepProxy.new(Gem::Dependency.new("rubygems\0", spec.required_rubygems_version), platform)
+ end
+ dependencies
end
end
@@ -175,30 +175,34 @@ module Bundler
# ==== Returns
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
# collection of gemspecs is returned. Otherwise, nil is returned.
- def self.resolve(requirements, index, source_requirements = {}, base = [], ruby_version = nil, gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [])
+ def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [])
base = SpecSet.new(base) unless base.is_a?(SpecSet)
- resolver = new(index, source_requirements, base, ruby_version, gem_version_promoter, additional_base_requirements)
+ resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements)
result = resolver.start(requirements)
SpecSet.new(result)
end
- def initialize(index, source_requirements, base, ruby_version, gem_version_promoter, additional_base_requirements)
+ def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements)
@index = index
@source_requirements = source_requirements
@base = base
@resolver = Molinillo::Resolver.new(self, self)
@search_for = {}
@base_dg = Molinillo::DependencyGraph.new
- @base.each {|ls| @base_dg.add_vertex(ls.name, Dependency.new(ls.name, ls.version), true) }
+ @base.each do |ls|
+ dep = Dependency.new(ls.name, ls.version)
+ @base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true)
+ end
additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
- @ruby_version = ruby_version
@gem_version_promoter = gem_version_promoter
end
def start(requirements)
verify_gemfile_dependencies_are_found!(requirements)
dg = @resolver.resolve(requirements, @base_dg)
- dg.map(&:payload).map(&:to_specs).flatten
+ dg.map(&:payload).
+ reject {|sg| sg.name.end_with?("\0") }.
+ map(&:to_specs).flatten
rescue Molinillo::VersionConflict => e
raise VersionConflict.new(e.conflicts.keys.uniq, e.message)
rescue Molinillo::CircularDependencyError => e
@@ -279,7 +283,7 @@ module Bundler
@gem_version_promoter.sort_versions(dependency, spec_groups)
end
end
- search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform!(platform) }
+ search.select {|sg| sg.for?(platform) }.each {|sg| sg.activate_platform!(platform) }
end
def index_for(dependency)
@@ -303,7 +307,8 @@ module Bundler
end
def requirement_satisfied_by?(requirement, activated, spec)
- requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
+ return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec)
+ spec.activate_platform!(requirement.__platform) || spec.for?(requirement.__platform)
end
def sort_dependencies(dependencies, activated, conflicts)
diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb
index 4ffc572bf0..ebdefe63fc 100644
--- a/lib/bundler/ruby_version.rb
+++ b/lib/bundler/ruby_version.rb
@@ -128,6 +128,11 @@ module Bundler
end
end
+ def exact?
+ return @exact if defined?(@exact)
+ @exact = versions.all? {|v| Gem::Requirement.create(v).exact? }
+ end
+
private
def matches?(requirements, version)
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index e9a7267f86..53a153e560 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -169,6 +169,11 @@ module Gem
def none?
@none ||= (to_s == ">= 0")
end unless allocate.respond_to?(:none?)
+
+ def exact?
+ return false unless @requirements.size == 1
+ @requirements[0][0] == "="
+ end unless allocate.respond_to?(:exact?)
end
class Platform
diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb
index e18f46268b..28ad988b94 100644
--- a/lib/bundler/rubygems_gem_installer.rb
+++ b/lib/bundler/rubygems_gem_installer.rb
@@ -12,5 +12,53 @@ module Bundler
def check_executable_overwrite(filename)
# Bundler needs to install gems regardless of binstub overwriting
end
+
+ def pre_install_checks
+ super && validate_bundler_checksum(options[:bundler_expected_checksum])
+ end
+
+ private
+
+ def validate_bundler_checksum(checksum)
+ return true if Bundler.settings[:disable_checksum_validation]
+ return true unless checksum
+ return true unless source = @package.instance_variable_get(:@gem)
+ return true unless source.respond_to?(:with_read_io)
+ digest = source.with_read_io do |io|
+ digest = Digest::SHA256.new
+ digest << io.read(16_384) until io.eof?
+ io.rewind
+ send(checksum_type(checksum), digest)
+ end
+ unless digest == checksum
+ raise SecurityError,
+ "The checksum for the downloaded `#{spec.full_name}.gem` did not match " \
+ "the checksum given by the API. This means that the contents of the " \
+ "gem appear to be different from what was uploaded, and could be an indicator of a security issue.\n" \
+ "(The expected SHA256 checksum was #{checksum.inspect}, but the checksum for the downloaded gem was #{digest.inspect}.)\n" \
+ "Bundler cannot continue installing #{spec.name} (#{spec.version})."
+ end
+ true
+ end
+
+ def checksum_type(checksum)
+ case checksum.length
+ when 64 then :hexdigest!
+ when 44 then :base64digest!
+ else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest"
+ end
+ end
+
+ def hexdigest!(digest)
+ digest.hexdigest!
+ end
+
+ def base64digest!(digest)
+ if digest.respond_to?(:base64digest!)
+ digest.base64digest!
+ else
+ [digest.digest!].pack("m0")
+ end
+ end
end
end
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index 1c44ef3ddd..90da0b4645 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -80,6 +80,7 @@ module Bundler
end
def platforms
+ return [Gem::Platform::RUBY] if Bundler.settings[:force_ruby_platform]
Gem.platforms
end
@@ -194,6 +195,10 @@ module Bundler
end
end
+ def load_plugins
+ Gem.load_plugins
+ end
+
def ui=(obj)
Gem::DefaultUserInteraction.ui = obj
end
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
index fda499cf5a..45f445aec1 100644
--- a/lib/bundler/runtime.rb
+++ b/lib/bundler/runtime.rb
@@ -140,7 +140,8 @@ module Bundler
Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}"
- specs.each do |spec|
+ specs_to_cache = Bundler.settings[:cache_all_platforms] ? @definition.resolve.materialized_for_all_platforms : specs
+ specs_to_cache.each do |spec|
next if spec.name == "bundler"
next if spec.source.is_a?(Source::Gemspec)
spec.source.send(:fetch_gem, spec) if Bundler.settings[:cache_all_platforms] && spec.source.respond_to?(:fetch_gem, true)
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index 01594066c3..2f532c832f 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -7,6 +7,7 @@ module Bundler
allow_offline_install
auto_install
cache_all
+ disable_checksum_validation
disable_exec_load
disable_local_branch_check
disable_shared_gems
@@ -17,6 +18,7 @@ module Bundler
major_deprecations
no_install
no_prune
+ force_ruby_platform
only_update_to_newer_versions
plugins
silence_root_warning
@@ -42,11 +44,18 @@ module Bundler
@local_config = load_config(local_config_file)
@global_config = load_config(global_config_file)
@cli_flags_given = false
+ @temporary = {}
end
def [](name)
key = key_for(name)
- value = (@local_config[key] || ENV[key] || @global_config[key] || DEFAULT_CONFIG[name])
+ value = @temporary.fetch(name) do
+ @local_config.fetch(key) do
+ ENV.fetch(key) do
+ @global_config.fetch(key) do
+ DEFAULT_CONFIG.fetch(name) do
+ nil
+ end end end end end
if value.nil?
nil
@@ -76,9 +85,19 @@ module Bundler
local_config_file || raise(GemfileNotFound, "Could not locate Gemfile")
set_key(key, value, @local_config, local_config_file)
end
-
alias_method :set_local, :[]=
+ def temporary(update)
+ existing = Hash[update.map {|k, _| [k, @temporary[k]] }]
+ @temporary.update(update)
+ return unless block_given?
+ begin
+ yield
+ ensure
+ existing.each {|k, v| v.nil? ? @temporary.delete(k) : @temporary[k] = v }
+ end
+ end
+
def delete(key)
@local_config.delete(key_for(key))
end
diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb
index 8b4b479778..4e213beed3 100644
--- a/lib/bundler/setup.rb
+++ b/lib/bundler/setup.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require "bundler/postit_trampoline" unless ENV["BUNDLE_DISABLE_POSTIT"]
+require "bundler/postit_trampoline"
require "bundler/shared_helpers"
if Bundler::SharedHelpers.in_bundle?
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index 69543356a2..0ddcea1ca5 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -39,10 +39,12 @@ module Bundler
bundle_dir = find_directory(".bundle")
return nil unless bundle_dir
- global_bundle_dir = File.join(Bundler.rubygems.user_home, ".bundle")
+ bundle_dir = Pathname.new(bundle_dir)
+
+ global_bundle_dir = Bundler.user_home.join(".bundle")
return nil if bundle_dir == global_bundle_dir
- Pathname.new(bundle_dir)
+ bundle_dir
end
def in_bundle?
@@ -202,10 +204,15 @@ module Bundler
def set_rubylib
rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
- rubylib.unshift File.expand_path("../..", __FILE__)
+ rubylib.unshift bundler_ruby_lib
ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR)
end
+ def bundler_ruby_lib
+ File.expand_path("../..", __FILE__)
+ end
+ private :bundler_ruby_lib
+
def clean_load_path
# handle 1.9 where system gems are always on the load path
if defined?(::Gem)
@@ -224,7 +231,8 @@ module Bundler
def prints_major_deprecations?
require "bundler"
- return false unless Bundler.settings[:major_deprecations]
+ deprecation_release = Bundler::VERSION.split(".").drop(1).include?("99")
+ return false if !deprecation_release && !Bundler.settings[:major_deprecations]
require "bundler/deprecate"
return false if Bundler::Deprecate.skip
true
diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb
index 08c1e6f79d..9d65d4613b 100644
--- a/lib/bundler/source.rb
+++ b/lib/bundler/source.rb
@@ -14,6 +14,7 @@ module Bundler
def version_message(spec)
message = "#{spec.name} #{spec.version}"
+ message += " (#{spec.platform})" if spec.platform != Gem::Platform::RUBY && !spec.platform.nil?
if Bundler.locked_gems
locked_spec = Bundler.locked_gems.specs.find {|s| s.name == spec.name }
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index 60fb555f0a..d1757a4a93 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -292,7 +292,7 @@ module Bundler
def fetch
git_proxy.checkout
rescue GitError
- raise unless Bundler.settings[:allow_offline_install]
+ raise unless Bundler.feature_flag.allow_offline_install?
Bundler.ui.warn "Using cached git data because of network errors"
end
end
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index 4b76d18735..c44f00d7b1 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -224,6 +224,11 @@ module Bundler
raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application"
end
+ # TODO: Replace this with Open3 when upgrading to bundler 2
+ # Similar to #git_null, as Open3 is not cross-platform,
+ # a temporary way is to use Tempfile to capture the stderr.
+ # When replacing this using Open3, make sure git_null is
+ # also replaced by Open3, so stdout and stderr all got handled properly.
def capture_and_filter_stderr(uri)
return_value, captured_err = ""
backup_stderr = STDERR.dup
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 69bb0c1af2..87a490446c 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -46,7 +46,7 @@ module Bundler
def to_lock
out = String.new("PATH\n")
- out << " remote: #{relative_path}\n"
+ out << " remote: #{lockfile_path}\n"
out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
out << " specs:\n"
end
@@ -129,6 +129,11 @@ module Bundler
"`#{somepath}`.\nThe error message was: #{e.message}."
end
+ def lockfile_path
+ return relative_path if path.absolute?
+ expand(path).relative_path_from(Bundler.root)
+ end
+
def app_cache_path(custom_path = nil)
@app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname)
end
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index aedad7086d..89f7673eb8 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -140,7 +140,8 @@ module Bundler
:bin_dir => bin_path.to_s,
:ignore_dependencies => true,
:wrappers => true,
- :env_shebang => true
+ :env_shebang => true,
+ :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum
).install
end
spec.full_gem_path = installed_spec.full_gem_path
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index f2ccac4742..fe31b17f0e 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -24,17 +24,9 @@ module Bundler
dep = deps.shift
next if handled[dep] || skip.include?(dep.name)
- spec = lookup[dep.name].find do |s|
- if match_current_platform
- Gem::Platform.match(s.platform)
- else
- s.match_platform(dep.__platform)
- end
- end
-
handled[dep] = true
- if spec
+ if spec = spec_for_dependency(dep, match_current_platform)
specs << spec
spec.dependencies.each do |d|
@@ -99,6 +91,20 @@ module Bundler
SpecSet.new(materialized.compact)
end
+ # Materialize for all the specs in the spec set, regardless of what platform they're for
+ # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform)
+ # @return [Array<Gem::Specification>]
+ def materialized_for_all_platforms
+ names = @specs.map(&:name).uniq
+ @specs.map do |s|
+ next s unless s.is_a?(LazySpecification)
+ s.source.dependency_names = names if s.source.respond_to?(:dependency_names=)
+ spec = s.__materialize__
+ raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec
+ spec
+ end
+ end
+
def merge(set)
arr = sorted.dup
set.each do |s|
@@ -133,10 +139,7 @@ module Bundler
def lookup
@lookup ||= begin
lookup = Hash.new {|h, k| h[k] = [] }
- specs = @specs.sort_by do |s|
- s.platform.to_s == "ruby" ? "\0" : s.platform.to_s
- end
- specs.reverse_each do |s|
+ Index.sort_specs(@specs).reverse_each do |s|
lookup[s.name] << s
end
lookup
@@ -147,6 +150,18 @@ module Bundler
@specs.each {|s| yield s }
end
+ def spec_for_dependency(dep, match_current_platform)
+ if match_current_platform
+ Bundler.rubygems.platforms.reverse_each do |pl|
+ match = GemHelpers.select_best_platform_match(lookup[dep.name], pl)
+ return match if match
+ end
+ nil
+ else
+ GemHelpers.select_best_platform_match(lookup[dep.name], dep.__platform)
+ end
+ end
+
def tsort_each_child(s)
s.dependencies.sort_by(&:name).each do |d|
next if d.type == :development
diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt
index 30c7b93609..ad8d88b6e4 100644
--- a/lib/bundler/templates/newgem/README.md.tt
+++ b/lib/bundler/templates/newgem/README.md.tt
@@ -32,7 +32,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
## Contributing
-Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %>
+Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:git_user_name] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %>
<% if config[:mit] %>
## License
diff --git a/lib/bundler/templates/newgem/bin/console.tt b/lib/bundler/templates/newgem/bin/console.tt
index f402bd639e..a27f82430f 100644
--- a/lib/bundler/templates/newgem/bin/console.tt
+++ b/lib/bundler/templates/newgem/bin/console.tt
@@ -11,4 +11,4 @@ require "<%= config[:namespaced_path] %>"
# Pry.start
require "irb"
-IRB.start
+IRB.start(__FILE__)
diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb
index 5c1fa61568..697290f795 100644
--- a/lib/bundler/ui/shell.rb
+++ b/lib/bundler/ui/shell.rb
@@ -83,6 +83,10 @@ module Bundler
with_level("silent", &blk)
end
+ def unprinted_warnings
+ []
+ end
+
private
# valimism
diff --git a/lib/bundler/ui/silent.rb b/lib/bundler/ui/silent.rb
index 367eaa58c2..5e0037f488 100644
--- a/lib/bundler/ui/silent.rb
+++ b/lib/bundler/ui/silent.rb
@@ -2,6 +2,10 @@
module Bundler
module UI
class Silent
+ def initialize
+ @warnings = []
+ end
+
def add_color(string, color)
string
end
@@ -13,6 +17,7 @@ module Bundler
end
def warn(message, newline = nil)
+ @warnings |= [message]
end
def error(message, newline = nil)
@@ -44,6 +49,10 @@ module Bundler
def silence
yield
end
+
+ def unprinted_warnings
+ @warnings
+ end
end
end
end
diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb
deleted file mode 100644
index 257e4c109e..0000000000
--- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-require "pathname"
-require "set"
-
-class Bundler::CompactIndexClient
- class Error < StandardError; end
-
- require "bundler/vendor/compact_index_client/lib/compact_index_client/cache"
- require "bundler/vendor/compact_index_client/lib/compact_index_client/updater"
- require "bundler/vendor/compact_index_client/lib/compact_index_client/version"
-
- attr_reader :directory
-
- # @return [Lambda] A lambda that takes an array of inputs and a block, and
- # maps the inputs with the block in parallel.
- #
- attr_accessor :in_parallel
-
- def initialize(directory, fetcher)
- @directory = Pathname.new(directory)
- @updater = Updater.new(fetcher)
- @cache = Cache.new(@directory)
- @endpoints = Set.new
- @info_checksums_by_name = {}
- @parsed_checksums = false
- @in_parallel = lambda do |inputs, &blk|
- inputs.map(&blk)
- end
- end
-
- def names
- update(@cache.names_path, "names")
- @cache.names
- end
-
- def versions
- update(@cache.versions_path, "versions")
- versions, @info_checksums_by_name = @cache.versions
- versions
- end
-
- def dependencies(names)
- in_parallel.call(names) do |name|
- update_info(name)
- @cache.dependencies(name).map {|d| d.unshift(name) }
- end.flatten(1)
- end
-
- def spec(name, version, platform = nil)
- update_info(name)
- @cache.specific_dependency(name, version, platform)
- end
-
- def update_and_parse_checksums!
- return @info_checksums_by_name if @parsed_checksums
- update(@cache.versions_path, "versions")
- @info_checksums_by_name = @cache.checksums
- @parsed_checksums = true
- end
-
-private
-
- def update(local_path, remote_path)
- return unless @endpoints.add?(remote_path)
- @updater.update(local_path, url(remote_path))
- end
-
- def update_info(name)
- path = @cache.info_path(name)
- checksum = @updater.checksum_for_file(path)
- return unless existing = @info_checksums_by_name[name]
- return if checksum == existing
- update(path, "info/#{name}")
- end
-
- def url(path)
- path
- end
-end
diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb
deleted file mode 100644
index d2639ee717..0000000000
--- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-# frozen_string_literal: true
-require "digest/md5"
-class Bundler::CompactIndexClient
- class Cache
- attr_reader :directory
-
- def initialize(directory)
- @directory = Pathname.new(directory).expand_path
- info_roots.each {|dir| FileUtils.mkdir_p(dir) }
- end
-
- def names
- lines(names_path)
- end
-
- def names_path
- directory.join("names")
- end
-
- def versions
- versions_by_name = Hash.new {|hash, key| hash[key] = [] }
- info_checksums_by_name = {}
-
- lines(versions_path).each do |line|
- name, versions_string, info_checksum = line.split(" ", 3)
- info_checksums_by_name[name] = info_checksum || ""
- versions_string.split(",").each do |version|
- if version.start_with?("-")
- version = version[1..-1].split("-", 2).unshift(name)
- versions_by_name[name].delete(version)
- else
- version = version.split("-", 2).unshift(name)
- versions_by_name[name] << version
- end
- end
- end
-
- [versions_by_name, info_checksums_by_name]
- end
-
- def versions_path
- directory.join("versions")
- end
-
- def checksums
- checksums = {}
-
- lines(versions_path).each do |line|
- name, _, checksum = line.split(" ", 3)
- checksums[name] = checksum
- end
-
- checksums
- end
-
- def dependencies(name)
- lines(info_path(name)).map do |line|
- parse_gem(line)
- end
- end
-
- def info_path(name)
- name = name.to_s
- if name =~ /[^a-z0-9_-]/
- name += "-#{Digest::MD5.hexdigest(name).downcase}"
- info_roots.last.join(name)
- else
- info_roots.first.join(name)
- end
- end
-
- def specific_dependency(name, version, platform)
- pattern = [version, platform].compact.join("-")
- return nil if pattern.empty?
-
- gem_lines = info_path(name).read
- gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0]
- gem_line ? parse_gem(gem_line) : nil
- end
-
- private
-
- def lines(path)
- return [] unless path.file?
- lines = path.read.split("\n")
- header = lines.index("---")
- lines = header ? lines[header + 1..-1] : lines
- end
-
- def parse_gem(string)
- version_and_platform, rest = string.split(" ", 2)
- version, platform = version_and_platform.split("-", 2)
- dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest
- dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : []
- requirements = requirements ? requirements.map {|r| parse_dependency(r) } : []
- [version, platform, dependencies, requirements]
- end
-
- def parse_dependency(string)
- dependency = string.split(":")
- dependency[-1] = dependency[-1].split("&") if dependency.size > 1
- dependency
- end
-
- def info_roots
- [
- directory.join("info"),
- directory.join("info-special-characters"),
- ]
- end
- end
-end
diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb
deleted file mode 100644
index 40c61644e3..0000000000
--- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-require "fileutils"
-require "stringio"
-require "tmpdir"
-require "zlib"
-
-class Bundler::CompactIndexClient
- class Updater
- class MisMatchedChecksumError < Error
- def initialize(path, server_checksum, local_checksum)
- @path = path
- @server_checksum = server_checksum
- @local_checksum = local_checksum
- end
-
- def message
- "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \
- "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})."
- end
- end
-
- def initialize(fetcher)
- @fetcher = fetcher
- end
-
- def update(local_path, remote_path, retrying = nil)
- headers = {}
-
- Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir|
- local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename)
-
- # first try to fetch any new bytes on the existing file
- if retrying.nil? && local_path.file?
- FileUtils.cp local_path, local_temp_path
- headers["If-None-Match"] = etag_for(local_temp_path)
- headers["Range"] = "bytes=#{local_temp_path.size}-"
- else
- # Fastly ignores Range when Accept-Encoding: gzip is set
- headers["Accept-Encoding"] = "gzip"
- end
-
- response = @fetcher.call(remote_path, headers)
- return if response.is_a?(Net::HTTPNotModified)
-
- content = response.body
- if response["Content-Encoding"] == "gzip"
- content = Zlib::GzipReader.new(StringIO.new(content)).read
- end
-
- mode = response.is_a?(Net::HTTPPartialContent) ? "a" : "w"
- local_temp_path.open(mode) {|f| f << content }
-
- response_etag = response["ETag"].gsub(%r{\AW/}, "")
- if etag_for(local_temp_path) == response_etag
- FileUtils.mv(local_temp_path, local_path)
- return
- end
-
- if retrying.nil?
- update(local_path, remote_path, :retrying)
- else
- raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path))
- end
- end
- end
-
- def etag_for(path)
- sum = checksum_for_file(path)
- sum ? %("#{sum}") : nil
- end
-
- def checksum_for_file(path)
- return nil unless path.file?
- # This must use IO.read instead of Digest.file().hexdigest
- # because we need to preserve \n line endings on windows when calculating
- # the checksum
- Digest::MD5.hexdigest(IO.read(path))
- end
- end
-end
diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb
deleted file mode 100644
index 64520daead..0000000000
--- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-class Bundler::CompactIndexClient
- VERSION = "0.1.0".freeze
-end
diff --git a/lib/bundler/yaml_serializer.rb b/lib/bundler/yaml_serializer.rb
index dede8fd5fd..3c9eccafc2 100644
--- a/lib/bundler/yaml_serializer.rb
+++ b/lib/bundler/yaml_serializer.rb
@@ -52,7 +52,7 @@ module Bundler
stack = [res]
last_hash = nil
last_empty_key = nil
- str.split("\n").each do |line|
+ str.split(/\r?\n/).each do |line|
if match = HASH_REGEX.match(line)
indent, key, _, val = match.captures
key = convert_to_backward_compatible_key(key)
diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn
index 3ea6d10973..dc701c584f 100644
--- a/man/bundle-config.ronn
+++ b/man/bundle-config.ronn
@@ -142,7 +142,7 @@ learn more about their operation in [bundle install(1)][bundle-install].
and key in PEM format.
* `cache_path` (`BUNDLE_CACHE_PATH`): The directory that bundler will place
cached gems in when running <code>bundle package</code>, and that bundler
- will look in when installing gems.
+ will look in when installing gems. Defaults to `vendor/bundle`.
* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`): When set, Gemfiles
containing multiple sources will produce errors instead of warnings. Use
`bundle config --delete disable_multisource` to unset.
diff --git a/spec/bundler/env_spec.rb b/spec/bundler/env_spec.rb
index 73d1f1d7df..245073d9b8 100644
--- a/spec/bundler/env_spec.rb
+++ b/spec/bundler/env_spec.rb
@@ -15,6 +15,7 @@ describe Bundler::Env do
expect(out).to include(Gem::VERSION)
expect(out).to include(env.send(:ruby_version))
expect(out).to include(env.send(:git_version))
+ expect(out).to include(OpenSSL::OPENSSL_VERSION)
end
context "when there is a Gemfile and a lockfile and print_gemfile is true" do
@@ -48,6 +49,14 @@ describe Bundler::Env do
end
end
+ context "when there no Gemfile and print_gemfile is true" do
+ let(:output) { env.report(:print_gemfile => true) }
+
+ it "prints the environment" do
+ expect(output).to start_with("Environment")
+ end
+ end
+
context "when Gemfile contains a gemspec and print_gemspecs is true" do
let(:gemspec) do
<<-GEMSPEC.gsub(/^\s+/, "")
diff --git a/spec/bundler/fetcher/compact_index_spec.rb b/spec/bundler/fetcher/compact_index_spec.rb
index f6c6ba2ee1..e3f36666cc 100644
--- a/spec/bundler/fetcher/compact_index_spec.rb
+++ b/spec/bundler/fetcher/compact_index_spec.rb
@@ -15,7 +15,7 @@ describe Bundler::Fetcher::CompactIndex do
it "has only one thread open at the end of the run" do
compact_index.specs_for_names(["lskdjf"])
- thread_count = Thread.list.select {|thread| thread.status == "run" }.count
+ thread_count = Thread.list.count {|thread| thread.status == "run" }
expect(thread_count).to eq 1
end
diff --git a/spec/bundler/fetcher/dependency_spec.rb b/spec/bundler/fetcher/dependency_spec.rb
index bf7749d07a..d021a246f7 100644
--- a/spec/bundler/fetcher/dependency_spec.rb
+++ b/spec/bundler/fetcher/dependency_spec.rb
@@ -262,13 +262,13 @@ describe Bundler::Fetcher::Dependency do
let(:uri) { URI("http://gem-api.com") }
context "with gem names" do
- let(:gem_names) { [%w(foo bar), %w(bundler rubocop)] }
+ let(:gem_names) { %w(foo bar bundler rubocop) }
before { allow(subject).to receive(:fetch_uri).and_return(uri) }
it "should return an api calling uri with the gems in the query" do
expect(subject.dependency_api_uri(gem_names).to_s).to eq(
- "http://gem-api.com/api/v1/dependencies?gems=foo%2Cbar%2Cbundler%2Crubocop"
+ "http://gem-api.com/api/v1/dependencies?gems=bar%2Cbundler%2Cfoo%2Crubocop"
)
end
end
diff --git a/spec/bundler/mirror_spec.rb b/spec/bundler/mirror_spec.rb
index eb0ccf0bdf..6a81ef2af4 100644
--- a/spec/bundler/mirror_spec.rb
+++ b/spec/bundler/mirror_spec.rb
@@ -131,6 +131,16 @@ describe Bundler::Settings::Mirror do
end
end
end
+
+ describe "#==" do
+ it "returns true if uri and fallback timeout are the same" do
+ uri = "https://ruby.taobao.org"
+ mirror = Bundler::Settings::Mirror.new(uri, 1)
+ another_mirror = Bundler::Settings::Mirror.new(uri, 1)
+
+ expect(mirror == another_mirror).to be true
+ end
+ end
end
end
diff --git a/spec/bundler/remote_specification_spec.rb b/spec/bundler/remote_specification_spec.rb
index 6a8e9a6434..d958ca85eb 100644
--- a/spec/bundler/remote_specification_spec.rb
+++ b/spec/bundler/remote_specification_spec.rb
@@ -158,16 +158,30 @@ describe Bundler::RemoteSpecification do
describe "method missing" do
context "and is present in Gem::Specification" do
- let(:remote_spec) { double(:remote_spec) }
+ let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
before do
- allow_any_instance_of(Gem::Specification).to receive(:respond_to?).and_return(true)
allow(subject).to receive(:_remote_specification).and_return(remote_spec)
+ expect(subject.methods.map(&:to_sym)).not_to include(:authors)
end
it "should send through to Gem::Specification" do
- expect(remote_spec).to receive(:send).with(:missing_method_call).once
- subject.missing_method_call
+ expect(subject.authors).to eq("abcd")
+ end
+ end
+ end
+
+ describe "respond to missing?" do
+ context "and is present in Gem::Specification" do
+ let(:remote_spec) { double(:remote_spec, :authors => "abcd") }
+
+ before do
+ allow(subject).to receive(:_remote_specification).and_return(remote_spec)
+ expect(subject.methods.map(&:to_sym)).not_to include(:authors)
+ end
+
+ it "should send through to Gem::Specification" do
+ expect(subject.respond_to?(:authors)).to be_truthy
end
end
end
diff --git a/spec/bundler/settings_spec.rb b/spec/bundler/settings_spec.rb
index 66189eae72..5a9d0cb08b 100644
--- a/spec/bundler/settings_spec.rb
+++ b/spec/bundler/settings_spec.rb
@@ -106,6 +106,18 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow
end
end
+ describe "#temporary" do
+ it "reset after used" do
+ Bundler.settings[:no_install] = true
+
+ Bundler.settings.temporary(:no_install => false) do
+ expect(Bundler.settings[:no_install]).to eq false
+ end
+
+ expect(Bundler.settings[:no_install]).to eq true
+ end
+ end
+
describe "#set_global" do
context "when it's not possible to write to the file" do
it "raises an PermissionError with explanation" do
diff --git a/spec/bundler/shared_helpers_spec.rb b/spec/bundler/shared_helpers_spec.rb
index 4c0d61cf0a..8826dcd4dd 100644
--- a/spec/bundler/shared_helpers_spec.rb
+++ b/spec/bundler/shared_helpers_spec.rb
@@ -234,7 +234,9 @@ describe Bundler::SharedHelpers do
shared_examples_for "ENV['RUBYLIB'] gets set correctly" do
let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
- before { allow(File).to receive(:expand_path).and_return(ruby_lib_path) }
+ before do
+ allow(Bundler::SharedHelpers).to receive(:bundler_ruby_lib).and_return(ruby_lib_path)
+ end
it "ensures bundler's ruby version lib path is in ENV['RUBYLIB']" do
subject.set_bundle_environment
@@ -324,7 +326,6 @@ describe Bundler::SharedHelpers do
let(:ruby_lib_path) { "stubbed_ruby_lib_dir" }
before do
- allow(File).to receive(:expand_path).and_return(ruby_lib_path)
ENV["RUBYLIB"] = ruby_lib_path
end
diff --git a/spec/bundler/source_spec.rb b/spec/bundler/source_spec.rb
index 61c6aea05c..ea171d387c 100644
--- a/spec/bundler/source_spec.rb
+++ b/spec/bundler/source_spec.rb
@@ -22,7 +22,7 @@ describe Bundler::Source do
end
describe "#version_message" do
- let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6") }
+ let(:spec) { double(:spec, :name => "nokogiri", :version => ">= 1.6", :platform => rb) }
shared_examples_for "the lockfile specs are not relevant" do
it "should return a string with the spec name and version" do
diff --git a/spec/bundler/yaml_serializer_spec.rb b/spec/bundler/yaml_serializer_spec.rb
index 0b3261336c..bf86d2a076 100644
--- a/spec/bundler/yaml_serializer_spec.rb
+++ b/spec/bundler/yaml_serializer_spec.rb
@@ -125,6 +125,29 @@ describe Bundler::YAMLSerializer do
expect(serializer.load(yaml)).to eq(hash)
end
+
+ it "handles windows-style CRLF line endings" do
+ yaml = strip_whitespace(<<-YAML).gsub("\n", "\r\n")
+ ---
+ nested_hash:
+ contains_array:
+ - "Why shouldn't you write with a broken pencil?"
+ - "Because it's pointless!"
+ - oh so silly
+ YAML
+
+ hash = {
+ "nested_hash" => {
+ "contains_array" => [
+ "Why shouldn't you write with a broken pencil?",
+ "Because it's pointless!",
+ "oh so silly",
+ ],
+ },
+ }
+
+ expect(serializer.load(yaml)).to eq(hash)
+ end
end
describe "against yaml lib" do
diff --git a/spec/cache/gems_spec.rb b/spec/cache/gems_spec.rb
index 474233a83c..19dd16e4e9 100644
--- a/spec/cache/gems_spec.rb
+++ b/spec/cache/gems_spec.rb
@@ -238,7 +238,7 @@ describe "bundle cache" do
gem "platform_specific"
G
- expect(cached_gem("platform_specific-1.0-#{Gem::Platform.local}")).to exist
+ expect(cached_gem("platform_specific-1.0-#{Bundler.local_platform}")).to exist
expect(cached_gem("platform_specific-1.0-java")).to exist
end
diff --git a/spec/commands/doctor_spec.rb b/spec/commands/doctor_spec.rb
index 236138a6c8..8debeb55e4 100644
--- a/spec/commands/doctor_spec.rb
+++ b/spec/commands/doctor_spec.rb
@@ -55,7 +55,10 @@ describe "bundle doctor" do
expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"]
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false)
- expect { doctor.run }.to raise_error SystemExit
- expect(@stdout.string).to include("libicui18n.57.1.dylib")
+ expect { doctor.run }.to raise_error Bundler::ProductionError, strip_whitespace(<<-E).strip
+ The following gems are missing OS dependencies:
+ * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib
+ E
end
end
diff --git a/spec/commands/exec_spec.rb b/spec/commands/exec_spec.rb
index 906abc0555..4dc47919de 100644
--- a/spec/commands/exec_spec.rb
+++ b/spec/commands/exec_spec.rb
@@ -565,6 +565,42 @@ describe "bundle exec" do
it_behaves_like "it runs"
end
+ context "regarding $0 and __FILE__" do
+ let(:executable) { super() + <<-'RUBY' }
+
+ puts "$0: #{$0.inspect}"
+ puts "__FILE__: #{__FILE__.inspect}"
+ RUBY
+
+ let(:expected) { super() + <<-EOS.chomp }
+
+$0: #{path.to_s.inspect}
+__FILE__: #{path.to_s.inspect}
+ EOS
+
+ it_behaves_like "it runs"
+
+ context "when the path is relative" do
+ let(:path) { super().relative_path_from(bundled_app) }
+
+ if LessThanProc.with(RUBY_VERSION).call("1.9")
+ pending "relative paths have ./ __FILE__"
+ else
+ it_behaves_like "it runs"
+ end
+ end
+
+ context "when the path is relative with a leading ./" do
+ let(:path) { Pathname.new("./#{super().relative_path_from(Pathname.pwd)}") }
+
+ if LessThanProc.with(RUBY_VERSION).call("< 1.9")
+ pending "relative paths with ./ have absolute __FILE__"
+ else
+ it_behaves_like "it runs"
+ end
+ end
+ end
+
context "signals being trapped by bundler" do
let(:executable) { strip_whitespace <<-RUBY }
#{shebang}
diff --git a/spec/commands/install_spec.rb b/spec/commands/install_spec.rb
index eb78ced86e..457fec26cb 100644
--- a/spec/commands/install_spec.rb
+++ b/spec/commands/install_spec.rb
@@ -216,7 +216,7 @@ describe "bundle install with gem sources" do
G
run "require 'platform_specific' ; puts PLATFORM_SPECIFIC"
- expect(out).to eq("1.0.0 #{Gem::Platform.local}")
+ expect(out).to eq("1.0.0 #{Bundler.local_platform}")
end
it "falls back on plain ruby" do
diff --git a/spec/commands/lock_spec.rb b/spec/commands/lock_spec.rb
index 1e189a9659..1c00aa3c1f 100644
--- a/spec/commands/lock_spec.rb
+++ b/spec/commands/lock_spec.rb
@@ -10,9 +10,11 @@ describe "bundle lock" do
strip_lockfile bundled_app(file).read
end
+ let(:repo) { gem_repo1 }
+
before :each do
gemfile <<-G
- source "file://#{gem_repo1}"
+ source "file://#{repo}"
gem "rails"
gem "with_license"
gem "foo"
@@ -20,7 +22,7 @@ describe "bundle lock" do
@lockfile = strip_lockfile <<-L
GEM
- remote: file:#{gem_repo1}/
+ remote: file:#{repo}/
specs:
actionmailer (2.3.2)
activesupport (= 2.3.2)
@@ -77,7 +79,7 @@ describe "bundle lock" do
it "writes a lockfile when there is an outdated lockfile using --update" do
lockfile @lockfile.gsub("2.3.2", "2.3.1")
- bundle "lock --update"
+ bundle! "lock --update"
expect(read_lockfile).to eq(@lockfile)
end
@@ -104,6 +106,54 @@ describe "bundle lock" do
expect(read_lockfile).to eq(@lockfile)
end
+ # see update_spec for more coverage on same options. logic is shared so it's not necessary
+ # to repeat coverage here.
+ context "conservative updates" do
+ before do
+ build_repo4 do
+ build_gem "foo", %w(1.4.3 1.4.4) do |s|
+ s.add_dependency "bar", "~> 2.0"
+ end
+ build_gem "foo", %w(1.4.5 1.5.0) do |s|
+ s.add_dependency "bar", "~> 2.1"
+ end
+ build_gem "foo", %w(1.5.1) do |s|
+ s.add_dependency "bar", "~> 3.0"
+ end
+ build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0)
+ build_gem "qux", %w(1.0.0 1.0.1 1.1.0 2.0.0)
+ end
+
+ # establish a lockfile set to 1.4.3
+ install_gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo', '1.4.3'
+ gem 'bar', '2.0.3'
+ gem 'qux', '1.0.0'
+ G
+
+ # remove 1.4.3 requirement and bar altogether
+ # to setup update specs below
+ gemfile <<-G
+ source "file://#{gem_repo4}"
+ gem 'foo'
+ gem 'qux'
+ G
+ end
+
+ it "single gem updates dependent gem to minor" do
+ bundle "lock --update foo --patch"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(foo-1.4.5 bar-2.1.1 qux-1.0.0).sort)
+ end
+
+ it "minor preferred with strict" do
+ bundle "lock --update --minor --strict"
+
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(foo-1.5.0 bar-2.1.1 qux-1.1.0).sort)
+ end
+ end
+
it "supports adding new platforms" do
bundle! "lock --add-platform java x86-mingw32"
@@ -132,4 +182,119 @@ describe "bundle lock" do
bundle "lock --remove-platform #{local}"
expect(out).to include("Removing all platforms from the bundle is not allowed")
end
+
+ # from https://github.com/bundler/bundler/issues/4896
+ it "properly adds platforms when platform requirements come from different dependencies" do
+ build_repo4 do
+ build_gem "ffi", "1.9.14"
+ build_gem "ffi", "1.9.14" do |s|
+ s.platform = mingw
+ end
+
+ build_gem "gssapi", "0.1"
+ build_gem "gssapi", "0.2"
+ build_gem "gssapi", "0.3"
+ build_gem "gssapi", "1.2.0" do |s|
+ s.add_dependency "ffi", ">= 1.0.1"
+ end
+
+ build_gem "mixlib-shellout", "2.2.6"
+ build_gem "mixlib-shellout", "2.2.6" do |s|
+ s.platform = "universal-mingw32"
+ s.add_dependency "win32-process", "~> 0.8.2"
+ end
+
+ # we need all these versions to get the sorting the same as it would be
+ # pulling from rubygems.org
+ %w(0.8.3 0.8.2 0.8.1 0.8.0).each do |v|
+ build_gem "win32-process", v do |s|
+ s.add_dependency "ffi", ">= 1.0.0"
+ end
+ end
+ end
+
+ gemfile <<-G
+ source "file:#{gem_repo4}"
+
+ gem "mixlib-shellout"
+ gem "gssapi"
+ G
+
+ simulate_platform(mingw) { bundle! :lock }
+
+ expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G))
+ GEM
+ remote: file:#{gem_repo4}/
+ specs:
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+
+ simulate_platform(rb) { bundle! :lock }
+
+ expect(the_bundle.lockfile).to read_as(strip_whitespace(<<-G))
+ GEM
+ remote: file:#{gem_repo4}/
+ specs:
+ ffi (1.9.14)
+ ffi (1.9.14-x86-mingw32)
+ gssapi (1.2.0)
+ ffi (>= 1.0.1)
+ mixlib-shellout (2.2.6)
+ mixlib-shellout (2.2.6-universal-mingw32)
+ win32-process (~> 0.8.2)
+ win32-process (0.8.3)
+ ffi (>= 1.0.0)
+
+ PLATFORMS
+ ruby
+ x86-mingw32
+
+ DEPENDENCIES
+ gssapi
+ mixlib-shellout
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ G
+ end
+
+ context "when an update is available" do
+ let(:repo) { gem_repo2 }
+
+ before do
+ lockfile(@lockfile)
+ build_repo2 do
+ build_gem "foo", "2.0"
+ end
+ end
+
+ it "does not implicitly update" do
+ bundle! "lock"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "accounts for changes in the gemfile" do
+ gemfile gemfile.gsub('"foo"', '"foo", "2.0"')
+ bundle! "lock"
+
+ expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)"))
+ end
+ end
end
diff --git a/spec/commands/newgem_spec.rb b/spec/commands/newgem_spec.rb
index cb3c48132a..6e80aa7a60 100644
--- a/spec/commands/newgem_spec.rb
+++ b/spec/commands/newgem_spec.rb
@@ -112,6 +112,35 @@ describe "bundle gem" do
end
end
+ context "README.md" do
+ let(:gem_name) { "test_gem" }
+ let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) }
+
+ context "git config user.name present" do
+ before do
+ execute_bundle_gem(gem_name)
+ end
+
+ it "contribute URL set to git username" do
+ expect(bundled_app("test_gem/README.md").read).not_to include("[USERNAME]")
+ end
+ end
+
+ context "git config user.name is absent" do
+ before do
+ `git config --unset user.name`
+ reset!
+ in_app_root
+ bundle "gem #{gem_name}"
+ remove_push_guard(gem_name)
+ end
+
+ it "contribute URL set to [USERNAME]" do
+ expect(bundled_app("test_gem/README.md").read).to include("[USERNAME]")
+ end
+ end
+ end
+
it "generates a valid gemspec" do
system_gems ["rake-10.0.2"]
diff --git a/spec/commands/outdated_spec.rb b/spec/commands/outdated_spec.rb
index 6420c28ac7..ab00e64b9d 100644
--- a/spec/commands/outdated_spec.rb
+++ b/spec/commands/outdated_spec.rb
@@ -72,6 +72,120 @@ describe "bundle outdated" do
end
end
+ describe "with --group option" do
+ def test_group_option(group = nil, gems_list_size = 1)
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem "duradura", '7.0'
+ gem 'activesupport', '2.3.5'
+ end
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "terranova", "9"
+ build_gem "duradura", "8.0"
+ end
+
+ bundle "outdated --group #{group}"
+
+ # Gem names are one per-line, between "*" and their parenthesized version.
+ gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact
+ expect(gem_list).to eq(gem_list.sort)
+ expect(gem_list.size).to eq gems_list_size
+ end
+
+ it "not outdated gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+
+ bundle "outdated --group"
+ expect(out).to include("Bundle up to date!")
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'default'" do
+ test_group_option("default")
+
+ expect(out).to include("===== Group default =====")
+ expect(out).to include("terranova (")
+
+ expect(out).not_to include("===== Group development, test =====")
+ expect(out).not_to include("activesupport")
+ expect(out).not_to include("duradura")
+ end
+
+ it "returns a sorted list of outdated gems from one group => 'development'" do
+ test_group_option("development", 2)
+
+ expect(out).not_to include("===== Group default =====")
+ expect(out).not_to include("terranova (")
+
+ expect(out).to include("===== Group development, test =====")
+ expect(out).to include("activesupport")
+ expect(out).to include("duradura")
+ end
+ end
+
+ describe "with --groups option" do
+ it "not outdated gems" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+
+ bundle "outdated --groups"
+ expect(out).to include("Bundle up to date!")
+ end
+
+ it "returns a sorted list of outdated gems by groups" do
+ install_gemfile <<-G
+ source "file://#{gem_repo2}"
+
+ gem "weakling", "~> 0.0.1"
+ gem "terranova", '8'
+ group :development, :test do
+ gem 'activesupport', '2.3.5'
+ gem "duradura", '7.0'
+ end
+ G
+
+ update_repo2 do
+ build_gem "activesupport", "3.0"
+ build_gem "terranova", "9"
+ build_gem "duradura", "8.0"
+ end
+
+ bundle "outdated --groups"
+ expect(out).to include("===== Group default =====")
+ expect(out).to include("terranova (newest 9, installed 8, requested = 8)")
+ expect(out).to include("===== Group development, test =====")
+ expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)")
+ expect(out).to include("duradura (newest 8.0, installed 7.0, requested = 7.0)")
+
+ expect(out).not_to include("weakling (")
+
+ # TODO: check gems order inside the group
+ end
+ end
+
describe "with --local option" do
it "doesn't hit repo2" do
FileUtils.rm_rf(gem_repo2)
diff --git a/spec/install/gemfile/eval_gemfile_spec.rb b/spec/install/gemfile/eval_gemfile_spec.rb
index 2660ac98c2..29f27550e4 100644
--- a/spec/install/gemfile/eval_gemfile_spec.rb
+++ b/spec/install/gemfile/eval_gemfile_spec.rb
@@ -26,6 +26,24 @@ describe "bundle install with gemfile that uses eval_gemfile" do
end
end
+ context "eval-ed Gemfile has relative-path gems" do
+ before do
+ build_lib("a", :path => "gems/a")
+ create_file "nested/Gemfile-nested", <<-G
+ gem "a", :path => "../gems/a"
+ G
+
+ gemfile <<-G
+ eval_gemfile "nested/Gemfile-nested"
+ G
+ end
+
+ it "installs the path gem" do
+ bundle! :install
+ expect(the_bundle).to include_gem("a 1.0")
+ end
+ end
+
context "Gemfile uses gemspec paths after eval-ing a Gemfile" do
before { create_file "other/Gemfile-other" }
diff --git a/spec/install/gemfile/platform_spec.rb b/spec/install/gemfile/platform_spec.rb
index 58129bb313..9caa0b5731 100644
--- a/spec/install/gemfile/platform_spec.rb
+++ b/spec/install/gemfile/platform_spec.rb
@@ -184,7 +184,7 @@ describe "bundle install with platform conditionals" do
gemfile <<-G
source "file://#{gem_repo1}"
- gem "some_gem", platform: :rbx
+ gem "some_gem", :platform => :rbx
G
bundle "install --local"
@@ -204,6 +204,23 @@ describe "bundle install with platform conditionals" do
bundle "install --local"
expect(out).not_to match(/Could not find gem 'some_gem/)
end
+
+ it "prints a helpful warning when a dependency is unused on any platform" do
+ simulate_platform "ruby"
+ simulate_ruby_engine "ruby"
+
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+
+ gem "rack", :platform => :jruby
+ G
+
+ bundle! "install"
+
+ expect(out).to include <<-O.strip
+The dependency #{Gem::Dependency.new("rack", ">= 0")} will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for java. To add those platforms to the bundle, run `bundle lock --add-platform jruby`.
+ O
+ end
end
describe "when a gem has no architecture" do
diff --git a/spec/install/gemfile/specific_platform_spec.rb b/spec/install/gemfile/specific_platform_spec.rb
new file mode 100644
index 0000000000..3e12f94c86
--- /dev/null
+++ b/spec/install/gemfile/specific_platform_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+describe "bundle install with specific_platform enabled" do
+ before do
+ bundle "config specific_platform true"
+
+ build_repo2 do
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1")
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5.1") {|s| s.platform = "universal-darwin" }
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.5")
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "universal-darwin" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.4")
+
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3")
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86_64-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x86-linux" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "x64-mingw32" }
+ build_gem("google-protobuf", "3.0.0.alpha.5.0.3") {|s| s.platform = "universal-darwin" }
+
+ build_gem("google-protobuf", "3.0.0.alpha.4.0")
+ build_gem("google-protobuf", "3.0.0.alpha.3.1.pre")
+ build_gem("google-protobuf", "3.0.0.alpha.3")
+ build_gem("google-protobuf", "3.0.0.alpha.2.0")
+ build_gem("google-protobuf", "3.0.0.alpha.1.1")
+ build_gem("google-protobuf", "3.0.0.alpha.1.0")
+ end
+ end
+
+ let(:google_protobuf) { <<-G }
+ source "file:#{gem_repo2}"
+ gem "google-protobuf"
+ G
+
+ context "when on a darwin machine" do
+ before { simulate_platform "x86_64-darwin-15" }
+
+ it "locks to both the specific darwin platform and ruby" do
+ install_gemfile!(google_protobuf)
+ expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")])
+ expect(the_bundle).to include_gem("google-protobuf 3.0.0.alpha.5.0.5.1 universal-darwin")
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ ))
+ end
+
+ it "caches both the universal-darwin and ruby gems when --all-platforms is passed" do
+ gemfile(google_protobuf)
+ bundle! "package --all-platforms"
+ expect([cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1"), cached_gem("google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin")]).
+ to all(exist)
+ end
+
+ context "when adding a platform via lock --add_platform" do
+ it "adds the foreign platform" do
+ install_gemfile!(google_protobuf)
+ bundle! "lock --add-platform=#{x64_mingw}"
+
+ expect(the_bundle.locked_gems.platforms).to eq([rb, x64_mingw, pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ google-protobuf-3.0.0.alpha.5.0.5.1-x64-mingw32
+ ))
+ end
+
+ it "falls back on plain ruby when that version doesnt have a platform-specific gem" do
+ install_gemfile!(google_protobuf)
+ bundle! "lock --add-platform=#{java}"
+
+ expect(the_bundle.locked_gems.platforms).to eq([java, rb, pl("x86_64-darwin-15")])
+ expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(
+ google-protobuf-3.0.0.alpha.5.0.5.1
+ google-protobuf-3.0.0.alpha.5.0.5.1-universal-darwin
+ ))
+ end
+ end
+ end
+end
diff --git a/spec/install/gems/compact_index_spec.rb b/spec/install/gems/compact_index_spec.rb
index b34d9e872d..001118b513 100644
--- a/spec/install/gems/compact_index_spec.rb
+++ b/spec/install/gems/compact_index_spec.rb
@@ -696,6 +696,49 @@ The checksum of /versions does not match the checksum provided by the server! So
expect(the_bundle).to include_gems "rack 1.0.0"
end
+ it "fails gracefully when the source URI has an invalid scheme" do
+ install_gemfile <<-G
+ source "htps://rubygems.org"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(15) if exitstatus
+ expect(out).to end_with(<<-E.strip)
+ The request uri `htps://index.rubygems.org/versions` has an invalid scheme (`htps`). Did you mean `http` or `https`?
+ E
+ end
+
+ describe "checksum validation", :rubygems => ">= 2.3.0" do
+ it "raises when the checksum does not match" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(19) if exitstatus
+ expect(out).
+ to include("The checksum for the downloaded `rack-1.0.0.gem` did not match the checksum given by the API.").
+ and include("This means that the contents of the gem appear to be different from what was uploaded, and could be an indicator of a security issue.").
+ and match(/\(The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/).
+ and include("Bundler cannot continue installing rack (1.0.0).")
+ end
+
+ it "raises when the checksum is the wrong length" do
+ install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum", :env => { "BUNDLER_SPEC_RACK_CHECKSUM" => "checksum!" }
+ source "#{source_uri}"
+ gem "rack"
+ G
+ expect(exitstatus).to eq(5) if exitstatus
+ expect(out).to include("The given checksum for rack-1.0.0 (\"checksum!\") is not a valid SHA256 hexdigest nor base64digest")
+ end
+
+ it "does not raise when disable_checksum_validation is set" do
+ bundle! "config disable_checksum_validation true"
+ install_gemfile! <<-G, :artifice => "compact_index_wrong_gem_checksum"
+ source "#{source_uri}"
+ gem "rack"
+ G
+ end
+ end
+
it "works when cache dir is world-writable" do
install_gemfile! <<-G, :artifice => "compact_index"
File.umask(0000)
diff --git a/spec/install/gems/resolving_spec.rb b/spec/install/gems/resolving_spec.rb
index 816799c0f8..0204a222f9 100644
--- a/spec/install/gems/resolving_spec.rb
+++ b/spec/install/gems/resolving_spec.rb
@@ -119,20 +119,58 @@ describe "bundle install with install-time dependencies" do
end
context "allows no gems" do
- it "does not try to install those gems" do
+ before do
build_repo2 do
build_gem "require_ruby" do |s|
s.required_ruby_version = "> 9000"
end
end
+ end
- install_gemfile <<-G
- source "file://#{gem_repo2}"
- gem 'require_ruby'
- G
+ let(:ruby_requirement) { %("#{RUBY_VERSION}") }
+ let(:error_message_requirement) { "~> #{RUBY_VERSION}.0" }
+
+ shared_examples_for "ruby version conflicts" do
+ it "raises an error during resolution" do
+ install_gemfile <<-G, :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2 }
+ source "http://localgemserver.test/"
+ ruby #{ruby_requirement}
+ gem 'require_ruby'
+ G
+
+ expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000")
+
+ nice_error = strip_whitespace(<<-E).strip
+ Fetching gem metadata from http://localgemserver.test/.
+ Fetching version metadata from http://localgemserver.test/
+ Resolving dependencies...
+ Bundler could not find compatible versions for gem "ruby\0":
+ In Gemfile:
+ ruby\0 (#{error_message_requirement})
+
+ require_ruby was resolved to 1.0, which depends on
+ ruby\0 (> 9000)
+
+ Could not find gem 'ruby\0 (> 9000)', which is required by gem 'require_ruby', in any of the sources.
+ E
+ expect(out).to eq(nice_error)
+ end
+ end
+
+ it_behaves_like "ruby version conflicts"
+
+ describe "with a < requirement" do
+ let(:ruby_requirement) { %("< 5000") }
+ let(:error_message_requirement) { "< 5000" }
+
+ it_behaves_like "ruby version conflicts"
+ end
+
+ describe "with a compound requirement" do
+ let(:ruby_requirement) { %("< 5000", "> 0.1") }
+ let(:error_message_requirement) { "< 5000, > 0.1" }
- expect(out).to_not include("Gem::InstallError: require_ruby requires Ruby version > 9000")
- expect(out).to include("require_ruby-1.0 requires ruby version > 9000, which is incompatible with the current version, #{Bundler::RubyVersion.system}")
+ it_behaves_like "ruby version conflicts"
end
end
end
diff --git a/spec/install/gemspecs_spec.rb b/spec/install/gemspecs_spec.rb
index 3e6021b7e2..8f719bf601 100644
--- a/spec/install/gemspecs_spec.rb
+++ b/spec/install/gemspecs_spec.rb
@@ -50,7 +50,7 @@ describe "bundle install" do
context "when ruby version is specified in gemspec and gemfile" do
it "installs when patch level is not specified and the version matches" do
build_lib("foo", :path => bundled_app) do |s|
- s.required_ruby_version = RUBY_VERSION
+ s.required_ruby_version = "~> #{RUBY_VERSION}.0"
end
install_gemfile <<-G
diff --git a/spec/install/path_spec.rb b/spec/install/path_spec.rb
index 3d84fffd58..03c42f008c 100644
--- a/spec/install/path_spec.rb
+++ b/spec/install/path_spec.rb
@@ -130,21 +130,21 @@ describe "bundle install" do
end
end
- describe "to a dead symlink" do
+ describe "to a file" do
before do
in_app_root do
- `ln -s /tmp/idontexist bundle`
+ `touch /tmp/idontexist bundle`
end
end
- it "reports the symlink is dead" do
+ it "reports the file exists" do
gemfile <<-G
source "file://#{gem_repo1}"
gem "rack"
G
bundle "install --path bundle"
- expect(out).to match(/invalid symlink/)
+ expect(out).to match(/file already exists/)
end
end
end
diff --git a/spec/other/major_deprecation_spec.rb b/spec/other/major_deprecation_spec.rb
index c8a2633279..6505023d13 100644
--- a/spec/other/major_deprecation_spec.rb
+++ b/spec/other/major_deprecation_spec.rb
@@ -4,6 +4,22 @@ require "spec_helper"
describe "major deprecations" do
let(:warnings) { out } # change to err in 2.0
+ context "in a .99 version" do
+ before do
+ simulate_bundler_version "1.99.1"
+ bundle "config --delete major_deprecations"
+ end
+
+ it "prints major deprecations without being configured" do
+ ruby <<-R
+ require "bundler"
+ Bundler::SharedHelpers.major_deprecation(Bundler::VERSION)
+ R
+
+ expect(warnings).to have_major_deprecation("1.99.1")
+ end
+ end
+
before do
bundle "config major_deprecations true"
diff --git a/spec/other/trampoline_spec.rb b/spec/other/trampoline_spec.rb
index 2aac0a2c1d..9a8e0a4a5d 100644
--- a/spec/other/trampoline_spec.rb
+++ b/spec/other/trampoline_spec.rb
@@ -59,6 +59,24 @@ describe "bundler version trampolining" do
end
end
+ context "without BUNDLE_ENABLE_TRAMPOLINE" do
+ before { ENV["BUNDLE_ENABLE_TRAMPOLINE"] = nil }
+
+ context "when the version is >= 2" do
+ let(:version) { "2.7182818285" }
+ before do
+ simulate_bundler_version version do
+ install_gemfile! ""
+ end
+ end
+
+ it "trampolines automatically", :realworld => true do
+ bundle "--version"
+ expect(err).to include("Installing locked Bundler version #{version}...")
+ end
+ end
+ end
+
context "installing missing bundler versions", :realworld => true do
before do
ENV["BUNDLER_VERSION"] = "1.12.3"
diff --git a/spec/resolver/basic_spec.rb b/spec/resolver/basic_spec.rb
index b7b8b4c3b8..3e8883d1d4 100644
--- a/spec/resolver/basic_spec.rb
+++ b/spec/resolver/basic_spec.rb
@@ -92,15 +92,18 @@ describe "Resolving" do
gem "bar", "2.0.0" do |s|
s.required_ruby_version = "~> 2.0.0"
end
+
+ gem "ruby\0", "1.8.7"
end
dep "foo"
+ dep "ruby\0", "1.8.7"
deps = []
@deps.each do |d|
deps << Bundler::DepProxy.new(d, "ruby")
end
- should_resolve_and_include %w(foo-1.0.0 bar-1.0.0), [{}, [], Bundler::RubyVersion.new("1.8.7", nil, nil, nil)]
+ should_resolve_and_include %w(foo-1.0.0 bar-1.0.0), [{}, []]
end
context "conservative" do
diff --git a/spec/resolver/platform_spec.rb b/spec/resolver/platform_spec.rb
index d5f217684c..fa91eab9c2 100644
--- a/spec/resolver/platform_spec.rb
+++ b/spec/resolver/platform_spec.rb
@@ -50,7 +50,7 @@ describe "Resolving platform craziness" do
# mingw is _not_ hardcoded to add CPU x86 in rubygems
platforms "x86-mingw32"
dep "thin"
- should_resolve_as %w(thin-1.2.7-x86-mingw32)
+ should_resolve_as %w(thin-1.2.7-mingw32)
end
it "finds x64-mingw gems" do
diff --git a/spec/runtime/inline_spec.rb b/spec/runtime/inline_spec.rb
index 3119045be4..4d9a1f7fe4 100644
--- a/spec/runtime/inline_spec.rb
+++ b/spec/runtime/inline_spec.rb
@@ -173,4 +173,24 @@ describe "bundler/inline#gemfile" do
expect(err).to be_empty
expect(exitstatus).to be_zero if exitstatus
end
+
+ it "allows calling gemfile twice" do
+ script <<-RUBY
+ gemfile do
+ path "#{lib_path}" do
+ gem "two"
+ end
+ end
+
+ gemfile do
+ path "#{lib_path}" do
+ gem "four"
+ end
+ end
+ RUBY
+
+ expect(out).to eq("two\nfour")
+ expect(err).to be_empty
+ expect(exitstatus).to be_zero if exitstatus
+ end
end
diff --git a/spec/runtime/platform_spec.rb b/spec/runtime/platform_spec.rb
index 666864a88c..4fd09cf4b7 100644
--- a/spec/runtime/platform_spec.rb
+++ b/spec/runtime/platform_spec.rb
@@ -88,4 +88,20 @@ describe "Bundler.setup with multi platform stuff" do
expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100"
end
+
+ it "allows specifying only-ruby-platform" do
+ simulate_platform "java"
+
+ install_gemfile! <<-G
+ source "file://#{gem_repo1}"
+ gem "nokogiri"
+ gem "platform_specific"
+ G
+
+ bundle! "config force_ruby_platform true"
+
+ bundle! "install"
+
+ expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY"
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a3251ea640..642bfabc18 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -17,6 +17,10 @@ rescue LoadError
abort "Run rake spec:deps to install development dependencies"
end
+if File.expand_path(__FILE__) =~ %r{([^\w/\.])}
+ abort "The bundler specs cannot be run from a path that contains special characters (particularly #{$1.inspect})"
+end
+
require "bundler"
# Require the correct version of popen for the current platform
diff --git a/spec/support/artifice/compact_index.rb b/spec/support/artifice/compact_index.rb
index d6b1e0cecd..0afd7fc526 100644
--- a/spec/support/artifice/compact_index.rb
+++ b/spec/support/artifice/compact_index.rb
@@ -78,7 +78,12 @@ class CompactIndexAPI < Endpoint
reqs = d.requirement.requirements.map {|r| r.join(" ") }.join(", ")
CompactIndex::Dependency.new(d.name, reqs)
end
- CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, nil, nil,
+ checksum = begin
+ Digest::SHA256.file("#{GEM_REPO}/gems/#{spec.original_name}.gem").base64digest
+ rescue
+ nil
+ end
+ CompactIndex::GemVersion.new(spec.version.version, spec.platform.to_s, checksum, nil,
deps, spec.required_ruby_version, spec.required_rubygems_version)
end
CompactIndex::Gem.new(name, gem_versions)
diff --git a/spec/support/artifice/compact_index_wrong_gem_checksum.rb b/spec/support/artifice/compact_index_wrong_gem_checksum.rb
new file mode 100644
index 0000000000..3a12a59ae7
--- /dev/null
+++ b/spec/support/artifice/compact_index_wrong_gem_checksum.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+require File.expand_path("../compact_index", __FILE__)
+
+Artifice.deactivate
+
+class CompactIndexWrongGemChecksum < CompactIndexAPI
+ get "/info/:name" do
+ etag_response do
+ name = params[:name]
+ gem = gems.find {|g| g.name == name }
+ checksum = ENV.fetch("BUNDLER_SPEC_#{name.upcase}_CHECKSUM") { "ab" * 22 }
+ versions = gem ? gem.versions : []
+ versions.each {|v| v.checksum = checksum }
+ CompactIndex.info(versions)
+ end
+ end
+end
+
+Artifice.activate_with(CompactIndexWrongGemChecksum)
diff --git a/spec/support/builders.rb b/spec/support/builders.rb
index 337234f14a..16ced2b920 100644
--- a/spec/support/builders.rb
+++ b/spec/support/builders.rb
@@ -79,8 +79,8 @@ module Spec
end
build_gem "platform_specific" do |s|
- s.platform = Gem::Platform.local
- s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'"
+ s.platform = Bundler.local_platform
+ s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'"
end
build_gem "platform_specific" do |s|
@@ -610,7 +610,10 @@ module Spec
end
def _default_files
- @_default_files ||= { "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}'" }
+ @_default_files ||= begin
+ platform_string = " #{@spec.platform}" unless @spec.platform == Gem::Platform::RUBY
+ { "lib/#{name}.rb" => "#{Builders.constantize(name)} = '#{version}#{platform_string}'" }
+ end
end
def _default_path
diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb
index 65603cb529..6b30afd480 100644
--- a/spec/support/helpers.rb
+++ b/spec/support/helpers.rb
@@ -218,7 +218,11 @@ module Spec
end
def gemfile(*args)
- create_file("Gemfile", *args)
+ if args.empty?
+ File.open("Gemfile", "r", &:read)
+ else
+ create_file("Gemfile", *args)
+ end
end
def lockfile(*args)
diff --git a/spec/support/indexes.rb b/spec/support/indexes.rb
index 9a7879bc74..29780014fc 100644
--- a/spec/support/indexes.rb
+++ b/spec/support/indexes.rb
@@ -62,7 +62,7 @@ module Spec
s.level = opts.first
s.strict = opts.include?(:strict)
end
- should_resolve_and_include specs, [{}, @base, nil, search]
+ should_resolve_and_include specs, [{}, @base, search]
end
def an_awesome_index
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index 9476f18984..9248360639 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -110,15 +110,9 @@ module Spec
define_compound_matcher :read_as, [exist] do |file_contents|
diffable
- attr_reader :strip_whitespace
-
- chain :stripping_whitespace do
- @strip_whitespace = true
- end
match do |actual|
@actual = Bundler.read_file(actual)
- file_contents = strip_whitespace(file_contents) if strip_whitespace
values_match?(file_contents, @actual)
end
end
diff --git a/spec/support/platforms.rb b/spec/support/platforms.rb
index b1dedb05fa..a2a3afba00 100644
--- a/spec/support/platforms.rb
+++ b/spec/support/platforms.rb
@@ -11,6 +11,10 @@ module Spec
Gem::Platform.new("x86-darwin-10")
end
+ def x64_mac
+ Gem::Platform.new("x86_64-darwin-15")
+ end
+
def java
Gem::Platform.new([nil, "java", nil])
end
diff --git a/spec/support/the_bundle.rb b/spec/support/the_bundle.rb
index 86df9cd9c7..742d393425 100644
--- a/spec/support/the_bundle.rb
+++ b/spec/support/the_bundle.rb
@@ -27,5 +27,10 @@ module Spec
def lockfile
bundle_dir.join("Gemfile.lock")
end
+
+ def locked_gems
+ raise "Cannot read lockfile if it doesn't exist" unless locked?
+ Bundler::LockfileParser.new(lockfile.read)
+ end
end
end