diff options
130 files changed, 2762 insertions, 1339 deletions
diff --git a/.gitignore b/.gitignore index 874a3b8e2f..4f79ffe3ca 100644 --- a/.gitignore +++ b/.gitignore @@ -3,23 +3,14 @@ # https://help.github.com/articles/ignoring-files and find useful gitignore # samples at https://github.com/github/gitignore -# system crap -.DS_Store -.*.swp - # files created by running the specs -tmp/ - -# built gems -pkg/ -*.gem +/tmp/ -# rubinius bytecode -*.rbc -.rbx/ +# gems built by `rake build` +/pkg/ # output from ronn -lib/bundler/man/ +/lib/bundler/man/ # output from ci_reporter -spec/reports/ +/spec/reports/ @@ -1,2 +1,3 @@ --format documentation --color +--warnings diff --git a/.travis.yml b/.travis.yml index d9311830e1..ec981a5a2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: ruby script: rake spec:travis before_script: travis_retry rake spec:travis:deps + branches: only: - master + - 1-8-stable - 1-7-stable - 1-6-stable - 1-5-stable @@ -11,39 +13,50 @@ branches: - 1-2-stable - 1-1-stable - 1-0-stable + notifications: email: # andre - secure: "bCcvqJT7YrBawtkXXwHhT+jOFth7r2Qv/30PkkbhQxk6Jb3xambjCOJ3U6vJ\ngYmiL50exi5lUp3oc3SEbHN5t2CrZqOZDQ6o7P8EAmB5c0oH2RrYaFOkI5Gt\nul/jGH/96A9sj0aMwG7JfdMSfhqj1DUKAm2PnnbXPL853VfmT24=" # terence - secure: "MQ8eA5Jb8YzEpAo58DRGfVJklAPcEbAulpBZnTxp0am6ldneDtJHbQk21w6R\nj5GsDHlzr/lMp/GHIimtUZ7rLohfND8fj/W7fs1Dkd4eN02/ERt98x3pHlqv\nvZgSnZ39uVYv+OcphraE24QaRaGWLhWZAMYQTVe/Yz50NyG8g1U=" - campfire: + slack: on_success: change on_failure: always rooms: - # Bundler Ops - secure: MNTSGIySYwHia5gIgRiZxXtPMPDIP9KmzQk7Kq2ZoVvP3mIk8W1TMkvcyFkEf6uCasyVZZixzUBfY+E0BlHAz1ycQpTh1jvSpuIpEVYW48ShJldJ+8W8xfzafyOHii3z7VrDaomEffmMDdmHRsbQAfekMjdR4bTpXtT9V+wOXlg= + - secure: JxBi7DDJGkIF/7f/FSN/HUHpvV4EKfQccZHTPd1b2pNJn3GXo6u+tNVbAw2WjxYzPyPQI3ZcYBCU9SEXp/i7VmG8uMzh8Kyildw+miSKYKVb90uYqcsXWzbxwyNBgJLvyDkzST45H5lgnyAicee3WkFes/WDZikIajbH7ztdb04= + rvm: - - 2.1.1 + - 2.1 - 2.0.0 - 1.9.3 - 1.8.7 + # Rubygems versions MUST be available as rake tasks # see Rakefile:66 for the list of possible RGV values 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.2.2 + - RGV=v2.4.4 + matrix: + fast_finish: true include: + # Ruby 2.1, Rubygems 2.2.2 and up + - rvm: 2.1 + env: RGV=v2.2.2 # Ruby 2.0.0, Rubygems 2.0 and up - rvm: 2.0.0 + env: RGV=v2.2.2 + - rvm: 2.0.0 env: RGV=v2.1.11 - rvm: 2.0.0 env: RGV=v2.0.14 # Ruby 1.9.3, Rubygems 1.5.3 and up - rvm: 1.9.3 + env: RGV=v2.2.2 + - rvm: 1.9.3 env: RGV=v2.1.11 - rvm: 1.9.3 env: RGV=v2.0.14 @@ -57,7 +70,7 @@ matrix: env: RGV=v1.5.3 # Ruby 1.8.7, Rubygems 1.3.6 and up - rvm: 1.8.7 - env: RGV=v2.1.11 + env: RGV=v2.2.2 - rvm: 1.8.7 env: RGV=v2.0.14 - rvm: 1.8.7 @@ -82,20 +95,14 @@ matrix: # Ruby 1.9.2 sanity check # (but it's just too slow and sometimes goes over the Travis limit) - rvm: 1.9.2 - env: RGV=v2.2.2 + env: RGV=v2.3.0 # Ruby-head (we want to know how we're doing, but not fail the build) - rvm: ruby-head env: RGV=master - # JRuby, the latest (not maintained, but good to know) - - rvm: jruby - env: RGV=v2.2.2 - # Rubinius, the latest (not maintained, but good to know) - - rvm: rbx - env: RGV=v2.2.2 allow_failures: - rvm: 1.8.7 - env: RGV=2.1.11 + env: RGV=v2.1.11 - rvm: 1.9.2 - rvm: ruby-head - rvm: jruby - - rvm: rbx + - rvm: rbx-2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7f609c77..d7bde91d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,120 @@ +## 1.8.8 (2015-04-29) + +Bugfixes: + + - Respect Gemfile sources when installing a gem present in two sources (#3585, @tmoore) + +## 1.8.7 (2015-04-07) + +Bugfixes: + + - stop suppressing errors inside gems that get required (#3549, @indirect) + +## 1.8.6 (2015-03-30) + +Bugfixes: + + - keep gems locked when updating another gem from the same source (#3250, @indirect) + - resolve race that could build gems without saved arguments (#3404, @indirect) + +## 1.8.5 (2015-03-11) + +Bugfixes: + + - remove MIT license from gemspec when removing license file (@indirect) + - respect 'no' immediately as well as saving it in `gem` config (@kirs) + +## 1.8.4 (2015-03-05) + +Bugfixes: + + - document --all-platforms option (#3449, @moeffju) + - find gems from all sources on exec after install (#3450, @TimMoore) + +## 1.8.3 (2015-02-24) + +Bugfixes: + + - handle boolean values for gem settings (@EduardoBautista) + - stop always looking for updated `path` gems (#3414, #3417, #3429, @TimMoore) + +## 1.8.2 (2015-02-14) + +Bugfixes: + + - allow config settings for gems with 'http' in the name again (#3398, @TimMoore) + +## 1.8.1 (2015-02-13) + +Bugfixes: + + - synchronize building git gem native extensions (#3385, @antifuchs & @indirect) + - set gemspec bindir correctly (#3392, @TimMoore) + - request lockfile deletion when it is malformed (#3396, @indirect) + - explain problem when mirror config is missing (#3386, @indirect) + - explain problem when caching causes permission error (#3390, @indirect) + - normalize URLs in config keys (#3391, @indirect) + +## 1.8.0 (2015-02-10) + +Bugfixes: + + - gemfile `github` blocks now work (#3379, @indirect) + +Bugfixes from v1.7.13: + + - look up installed gems in remote sources (#3300, #3368, #3377, #3380, #3381, @indirect) + - look up gems across all sources to satisfy dependencies (#3365, @keiths-osc) + - request dependencies for no more than 100 gems at a time (#3367, @segiddins) + +## 1.8.0.rc (2015-01-26) + +Features: + + - add `config disable_multisource` option to ensure sources can't compete (@indirect) + +Bugfixes: + + - don't add extra quotes around long, quoted config values (@aroben, #3338) + +Security: + + - warn when more than one top-level source is present (@indirect) + +## 1.8.0.pre (2015-01-26) + +Features: + + - add metadata allowed_push_host to new gem template (#3002, @juanitofatas) + - adds a `--no-install` flag to `bundle package` (@d-reinhold) + - add `bundle config auto_install true` to install automatically (@smashwilson) + - add `bundle viz --without` to exclude gem groups from resulting graph (@fnichol) + - prevent whitespace in gem declarations with clear messaging (@benlakey) + - tries to find a `bundler-<command>` executable on your path for non-bundler commands (@andremedeiros) + - tries to find `gems.rb` and it's new counterpart, `gems.locked` (@andremedeiros) + - change the initial version of new gems from `0.0.1` to `0.1.0` (@petedmarsh) + - add `package --all-platforms` to cache gems for each known platform (@ccutrer) + - speed up `exec` when running commands on the $PATH (@kirs) + - add gem code of conduct file and option (@kirs) + - add config settings for gem license and tests (@kirs) + - add `bin/setup` and `bin/console` to new gems (@indirect) + - include configured user-agent in network requests (@indirect) + - support `github`, `gist`, and `bitbucket` options on git gems (@indirect) + - add `package --cache-path` and `config cache_path` for cache location (@jnraine) + - allow `config` to work even when a Gemfile is not present (@dholdren) + - add `config gemfile /path` for other Gemfile locations (@dholdren) + +Bugfixes: + + - reduce memory usage with threaded parallel workers (@Who828) + - support read-only git gems (@pmahoney) + - various resolver performance improvements (@dubek) + - untaint git gem paths for Rubygems compatibility (@tdtds) + +Documentation: + + - add missing Gemfile global `path` explanation (@agenteo) + ## 1.7.15 (2015-04-29) Bugfixes: @@ -121,6 +238,22 @@ Bugfixes: - Warn on ambiguous gems available from more than one source (@TimMoore) +## 1.6.7 (2014-10-19) + +Features: + + - warn to upgrade when using useless source blocks (@danfinnie) + +Documentation: + + - explain how to use gem server credentials via ENV (@hwartig) + +## 1.6.6 (2014-08-23) + +Bugfixes: + + - restore Gemfile credentials to Gemfile.lock (@indirect) + ## 1.6.5 (2014-07-23) Bugfixes: @@ -202,6 +335,7 @@ Features: - `bundle show --verbose` Add gem summary to the output (@lardcanoe) - `bundle gem GEM --ext` now generates a skeleton for a C extension (@superdealloc) - Avoid using threequals operator where possible (@as-cii) + - Add `bundle update --group` to update specific group (#2731 @banyan) Documentation: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..e5e8828d73 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,40 @@ +# Bundler Code of Conduct + +The Bundler project strongly values contributors from anywhere, regardless of gender, sexual orientation, disability, physical appearance, body size, race, or religion. As a result, the Bundler team has agreed to and enforces this code of conduct in order to provide a harassment-free experience for everyone who participates in the development of Bundler. + +### Summary + +Harassment in code and discussion or violation of physical boundaries is completely unacceptable anywhere in the Bundler project’s codebases, issue trackers, IRC channel, Campfire, mailing lists, meetups, and other events. Violators will be warned and then blocked or banned by the core team at or before the 3rd violation. + +### In detail + +Harassment includes offensive verbal comments related to gender, sexual orientation, disability, physical appearance, body size, race, religion, sexual images, deliberate intimidation, stalking, sustained disruption, and unwelcome sexual attention. + +Individuals asked to stop any harassing behavior are expected to comply immediately. + +Maintainers, including the core team, are also subject to the anti-harassment policy. + +If anyone engages in harassing behavior, including maintainers, we may take appropriate action, up to and including warning the offender, deletion of comments, removal from the project’s codebase and communication systems, and escalation to Github support. + +If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact a member of [the core team](http://bundler.io/contributors.html) or [email the core team](mailto:team@bundler.io) immediately. + +We expect everyone to follow these rules anywhere in the Bundler project’s codebases, issue trackers, IRC channel, group chat, and mailing lists. + +Finally, don't forget that it is human to make mistakes! We all do. Let’s work together to help each other, resolve issues, and learn from the mistakes that we will all inevitably make from time to time. + + +### Thanks + +Thanks to the [JSConf Code of Conduct](http://jsconf.com/codeofconduct.html) and [Fedora Code of Conduct](http://fedoraproject.org/code-of-conduct) for inspiration and ideas. Additional thanks to [Contributor Covenant](http://contributor-covenant.org) for the [default code of conduct](https://github.com/bundler/bundler/blob/master/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt) included in generated gems. + + +### License + +<p class="license" xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#"> + To the extent possible under law, <a rel="dct:publisher" href="http://bundler.io">The Bundler Team</a> has waived all copyright and related or neighboring rights to the <span property="dct:title">Bundler Code of Conduct</span>. This work is published from the <span property="vcard:Country" datatype="dct:ISO3166" content="US" about="http://bundler.io">United States.</span> + <br> + <br> + <a rel="license" href="http://creativecommons.org/publicdomain/zero/1.0/"> + <img src="http://i.creativecommons.org/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0"> + </a> +</p>
\ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2fc0cec3d..24e0cdaa26 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,18 +2,20 @@ Bundler welcomes contributions from *everyone*. While contributing, please follow the project [code of conduct](http://bundler.io/conduct.html), so that everyone can be included. -Here are some ways you can contribute: - - - by using prerelease versions - - by reporting bugs - - by suggesting new features - - by writing or editing documentation - - by closing issues - - by reviewing patches - - by refactoring code - - by writing code (no patch is too small! fix typos or bad whitespace) - -If you'd like to help make Bundler better, you totally rock! Please check out the [DEVELOPMENT](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) file for an introduction to the project, guidelines for contributing, and details about what would be helpful. +If you'd like to help make Bundler better, you totally rock! Here are some ways you can contribute: + + - by using prerelease versions (run `gem install bundler --pre`) + - by [reporting bugs you encounter](https://github.com/bundler/bundler/issues/new) + - by [suggesting new features](https://github.com/bundler/bundler-features/issues/new) + - by adding to or editing [the Bundler documentation website](http://bundler.io) and [Bundler man pages](http://bundler.io/man/bundle.1.html) + - by [checking issues for completeness](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md#bug-triage) + - by closing issues that are not complete + - by adding a failing test for reproducible [reported bugs](https://github.com/bundler/bundler/issues) + - by reviewing [pull requests](https://github.com/bundler/bundler/pulls) and suggesting improvements + - by improving existing code, including [suggestions from PullReview](https://www.pullreview.com/github/bundler/bundler/reviews/master) + - by [writing code](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) (no patch is too small! fix typos or bad whitespace) + +If you need help getting started, check out the [DEVELOPMENT](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) file for steps that will get you up and running. Thanks for helping us make Bundler better. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index dc52a9907a..a31c5a3781 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,7 +2,7 @@ Great to have you here! Here are a few ways you can help out with [Bundler](http # Where should I start? -You can start learning about Bundler by reading [the documentation](http://bundler.io). If you want, you can also read a (lengthy) explanation of [why Bundler exists and what it does](http://bundler.io/v1.5/rationale.html). You can also check out discussions about Bundler on the [Bundler mailing list](https://groups.google.com/group/ruby-bundler) and in the [Bundler IRC channel](http://webchat.freenode.net/?channels=%23bundler), which is #bundler on Freenode. +You can start learning about Bundler by reading [the documentation](http://bundler.io). If you want, you can also read a (lengthy) explanation of [why Bundler exists and what it does](http://bundler.io/rationale.html). You can also check out discussions about Bundler on the [Bundler mailing list](https://groups.google.com/group/ruby-bundler) and in the [Bundler IRC channel](http://webchat.freenode.net/?channels=%23bundler), which is #bundler on Freenode. Please note that this project is released with a contributor [code of conduct](http://bundler.io/conduct.html). By participating in this project you agree to abide by its terms. ## Your first commits @@ -70,7 +70,7 @@ Finally, the ticket may be a duplicate of another older ticket. If you notice a If you would like to add a new feature to Bundler, please follow these steps: - 1. [Create an issue](https://github.com/bundler/bundler-features/issues/new) to discuss your feature. + 1. [Create an issue](https://github.com/bundler/bundler-features/issues/new) in the bundler-features repo to discuss your feature. 2. Base your commits on the master branch, since we follow [SemVer](http://semver.org) and don't add new features to old releases. 3. Commit the code and at least one test covering your changes to a feature branch in your fork. 4. Put a line in the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md) summarizing your changes under the next release under the "Features" heading. @@ -115,3 +115,5 @@ Finally, sharing your experiences and discoveries by writing them up is a valuab Examples of how Bundler is used help everyone, and we’ve discovered that people already use it in ways that we never imagined when we were writing it. If you’re still not sure what to write about, there are also several projects doing interesting things based on Bundler. They could probably use publicity too. If you let someone on the core team know you wrote about Bundler, we will add your post to the list of Bundler resources on the Github project wiki. + +Finally, participate carefully in the all contributors to the Bundler project must agree to the contributor [code of conduct](http://bundler.io/conduct.html). By participating in this project you agree to abide by its terms. @@ -10,7 +10,7 @@ discuss features. The bundler issue tracker is only for bugs.** Instructions for common Bundler uses can be found on the [Bundler documentation site](http://bundler.io/). -Detailed information about each Bundler command, including help with common problems, can be found in the [Bundler man pages](http://bundler.io/v1.3/man/bundle.1.html). +Detailed information about each Bundler command, including help with common problems, can be found in the [Bundler man pages](http://bundler.io/man/bundle.1.html). ## Troubleshooting @@ -1,9 +1,11 @@ -[![Code Climate](https://img.shields.io/codeclimate/github/bundler/bundler.svg)](https://codeclimate.com/github/bundler/bundler) -[![Build Status](https://img.shields.io/travis/bundler/bundler/master.svg)](https://travis-ci.org/bundler/bundler) -[![Version ](https://img.shields.io/gem/v/bundler.svg)](https://rubygems.org/gems/bundler) +[![Version ](https://img.shields.io/gem/v/bundler.svg?style=flat)](https://rubygems.org/gems/bundler) +[![Build Status](https://img.shields.io/travis/bundler/bundler/master.svg?style=flat)](https://travis-ci.org/bundler/bundler) +[![Code Climate](https://img.shields.io/codeclimate/github/bundler/bundler.svg?style=flat)](https://codeclimate.com/github/bundler/bundler) +[![Inline docs ](http://inch-ci.org/github/bundler/bundler.svg?style=flat)](http://inch-ci.org/github/bundler/bundler) # Bundler: a gem to bundle gems -Bundler keeps ruby applications running the same code on every machine. + +Bundler makes sure Ruby applications run the same code on every machine. It does this by managing the gems that the application depends on. Given a list of gems, it can automatically download and install those gems, as well as any other gems needed by the gems that are listed. Before installing gems, it checks the versions of every gem to make sure that they are compatible, and can all be loaded at the same time. After the gems have been installed, Bundler can help you update some or all of them when new versions become available. Finally, it records the exact versions that have been installed, so that others can install the exact same gems. @@ -23,18 +25,12 @@ See [bundler.io](http://bundler.io) for the full documentation. For help with common problems, see [ISSUES](https://github.com/bundler/bundler/blob/master/ISSUES.md). -### Contributing - -If you'd like to contribute to Bundler, that's awesome, and we <3 you. There's a guide to contributing to Bundler (both code and general help) over in [DEVELOPMENT](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) - -The `master` branch contains our current progress towards version 1.5. Versions 1.0-1.3 each have their own stable branches. Please submit bugfixes as pull requests to the stable branch for the version you would like to fix. - -### Core Team - -The Bundler core team consists of André Arko ([@indirect](http://github.com/indirect)), Terence Lee ([@hone](http://github.com/hone)), and Jessica Lynn Suttles ([@jlsuttles](http://github.com/jlsuttles)), with support and advice from original Bundler author Yehuda Katz ([@wycats](http://github.com/wycats)). - ### Other questions To see what has changed in recent versions of Bundler, see the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md). Feel free to chat with the Bundler core team (and many other users) on IRC in the [#bundler](irc://irc.freenode.net/bundler) channel on Freenode, or via email on the [Bundler mailing list](http://groups.google.com/group/ruby-bundler). + +### Contributing + +If you'd like to contribute to Bundler, that's awesome, and we <3 you. There's a guide to contributing to Bundler (both code and general help) over in [DEVELOPMENT](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) @@ -1,10 +1,10 @@ # -*- encoding: utf-8 -*- $:.unshift File.expand_path("../lib", __FILE__) -require 'rubygems' require 'shellwords' require 'benchmark' RUBYGEMS_REPO = File.expand_path("tmp/rubygems") +BUNDLER_SPEC = Gem::Specification.load("bundler.gemspec") def safe_task(&block) yield @@ -30,8 +30,7 @@ end namespace :spec do desc "Ensure spec dependencies are installed" task :deps do - spec = Gem::Specification.load("bundler.gemspec") - deps = Hash[spec.development_dependencies.map do |d| + deps = Hash[BUNDLER_SPEC.development_dependencies.map do |d| [d.name, d.requirement.to_s] end] @@ -65,7 +64,7 @@ namespace :spec do # https://github.com/rubygems/rubygems/issues/784 sh "gem update --system 2.1.11" else - # Downgrade Rubygems so RSpec 3 can be instaled + # Downgrade Rubygems so RSpec 3 can be installed # https://github.com/rubygems/rubygems/issues/813 sh "gem update --system 2.2.0" end @@ -76,13 +75,12 @@ namespace :spec do end begin + rspec = BUNDLER_SPEC.development_dependencies.find{|d| d.name == "rspec" } + gem 'rspec', rspec.requirement.to_s require 'rspec/core/rake_task' desc "Run specs" - RSpec::Core::RakeTask.new do |t| - t.rspec_opts = %w(--format documentation --color) - t.ruby_opts = %w(-w) - end + RSpec::Core::RakeTask.new task :spec => "man:build" namespace :spec do @@ -114,7 +112,7 @@ begin rubyopt = ENV["RUBYOPT"] # When editing this list, also edit .travis.yml! branches = %w(master 2.2) - 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.2) + 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.2 v2.4.4) (branches + releases).each do |rg| desc "Run specs with Rubygems #{rg}" RSpec::Core::RakeTask.new(rg) do |t| @@ -160,7 +158,9 @@ begin end task "setup_co" do - ENV["RUBYOPT"] = "-I#{File.expand_path ENV['RG']} #{rubyopt}" + rg = File.expand_path ENV['RG'] + puts "Running specs against Rubygems in #{rg}..." + ENV["RUBYOPT"] = "-I#{rg} #{rubyopt}" end task "co" => "setup_co" @@ -234,12 +234,15 @@ begin task :clean do rm_rf "lib/bundler/man" end + + task(:require) { } end rescue LoadError namespace :man do - task(:build) { warn "Install the ronn gem to be able to release!" } - task(:clean) { warn "Install the ronn gem to be able to release!" } + task(:require) { abort "Install the ronn gem to be able to release!" } + task(:build) { warn "Install the ronn gem to build the help pages" } + task(:clean) { } end end @@ -251,6 +254,6 @@ end require 'bundler/gem_tasks' task :build => ["man:clean", "man:build"] -task :release => ["man:clean", "man:build"] +task :release => ["man:require", "man:clean", "man:build"] task :default => :spec diff --git a/bin/bundle b/bin/bundle index 63285e96e2..89c823eae3 100755 --- a/bin/bundle +++ b/bin/bundle @@ -6,7 +6,7 @@ Signal.trap("INT") { exit 1 } require 'bundler' # Check if an older version of bundler is installed $LOAD_PATH.each do |path| - if path =~ %r'/bundler-0.(\d+)' && $1.to_i < 9 + if path =~ %r'/bundler-0\.(\d+)' && $1.to_i < 9 err = "Looks like you have a version of bundler that's older than 0.9.\n" err << "Please remove your old versions.\n" err << "An easy way to do this is by running `gem cleanup bundler`." diff --git a/bundler.gemspec b/bundler.gemspec index 89439612b1..bd7e32e7c9 100644 --- a/bundler.gemspec +++ b/bundler.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |s| s.version = Bundler::VERSION s.licenses = ['MIT'] s.authors = ["André Arko", "Terence Lee", "Carl Lerche", "Yehuda Katz"] - s.email = ["andre@arko.net"] + s.email = ["andre.arko+terence.lee@gmail.com"] s.homepage = "http://bundler.io" s.summary = %q{The best way to manage your application's dependencies} s.description = %q{Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably} @@ -21,9 +21,10 @@ Gem::Specification.new do |s| s.add_development_dependency 'ronn', '~> 0.7.3' s.add_development_dependency 'rspec', '~> 3.0' - s.files = `git ls-files -z`.split("\x0") - s.files += Dir.glob('lib/bundler/man/**/*') # man/ is ignored by git - s.test_files = s.files.grep(%r{^spec/}) + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + # we don't check in man pages, but we need to ship them because + # we use them to generate the long-form help for each command. + s.files += Dir.glob('lib/bundler/man/**/*') s.executables = %w(bundle bundler) s.require_paths = ["lib"] diff --git a/lib/bundler.rb b/lib/bundler.rb index 428723c082..025337418c 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -191,7 +191,13 @@ module Bundler end def root - @root ||= default_gemfile.dirname.expand_path + @root ||= begin + default_gemfile.dirname.expand_path + rescue GemfileNotFound + bundle_dir = default_bundle_dir + raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir + Pathname.new(File.expand_path("..", bundle_dir)) + end end def app_config_path @@ -202,7 +208,7 @@ module Bundler def app_cache(custom_path = nil) path = custom_path || root - path.join("vendor/cache") + path.join(self.settings.app_cache_path) end def tmp(name = Process.pid.to_s) @@ -210,11 +216,16 @@ module Bundler @tmp.join(name) end + def cleanup + FileUtils.remove_entry_secure(@tmp) if @tmp + rescue + end + def settings return @settings if defined?(@settings) @settings = Settings.new(app_config_path) rescue GemfileNotFound - @settings = Settings.new + @settings = Settings.new(Pathname.new(".bundle").expand_path) end def with_original_env @@ -253,6 +264,10 @@ module Bundler SharedHelpers.default_lockfile end + def default_bundle_dir + SharedHelpers.default_bundle_dir + end + def system_bindir # Gem.bindir doesn't always return the location that Rubygems will install # system binaries. If you put '-n foo' in your .gemrc, Rubygems will @@ -369,6 +384,10 @@ module Bundler @ruby_version ||= SystemRubyVersion.new end + def reset! + @definition = nil + end + private def eval_yaml_gemspec(path, contents) diff --git a/lib/bundler/anonymizable_uri.rb b/lib/bundler/anonymizable_uri.rb index 032346fd25..333a568f84 100644 --- a/lib/bundler/anonymizable_uri.rb +++ b/lib/bundler/anonymizable_uri.rb @@ -3,14 +3,30 @@ module Bundler attr_reader :original_uri, :without_credentials - def initialize(original_uri) - @original_uri = original_uri.freeze - @without_credentials ||= - if original_uri.userinfo - original_uri.dup.tap { |uri| uri.user = uri.password = nil }.freeze - else - original_uri - end + def initialize(original_uri, fallback_auth = nil) + @original_uri = apply_auth(original_uri, fallback_auth).freeze + @without_credentials = remove_auth(@original_uri).freeze end + + private + + def apply_auth(uri, auth = nil) + if auth && uri.userinfo.nil? + uri = uri.dup + uri.userinfo = auth + end + + uri + end + + def remove_auth(uri) + if uri.userinfo + uri = uri.dup + uri.user = uri.password = nil + end + + uri + end + end end diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index c3e5d8b468..6a22072565 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -4,19 +4,25 @@ require 'bundler/vendored_thor' module Bundler class CLI < Thor include Thor::Actions + AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean] def self.start(*) super rescue Exception => e Bundler.ui = UI::Shell.new raise e + ensure + Bundler.cleanup end - def initialize(*) + def initialize(*args) super - ENV['BUNDLE_GEMFILE'] = File.expand_path(options[:gemfile]) if options[:gemfile] + current_cmd = args.last[:current_command].name + custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] + ENV['BUNDLE_GEMFILE'] = File.expand_path(custom_gemfile) if custom_gemfile Bundler::Retry.attempts = options[:retry] || Bundler.settings[:retry] || Bundler::Retry::DEFAULT_ATTEMPTS Bundler.rubygems.ui = UI::RGProxy.new(Bundler.ui) + auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) rescue UnknownArgumentError => e raise InvalidOption, e.message ensure @@ -29,10 +35,10 @@ module Bundler stop_on_unknown_option! :exec default_task :install - class_option "no-color", :type => :boolean, :banner => "Disable colorization in output" - class_option "verbose", :type => :boolean, :banner => "Enable verbose output mode", :aliases => "-V" - class_option "retry", :type => :numeric, :aliases => "-r", :banner => - "Specify the number of times you wish to attempt network commands" + class_option "no-color", :type => :boolean, :desc => "Disable colorization in output" + class_option "retry", :type => :numeric, :aliases => "-r", :banner => "NUM", + :desc => "Specify the number of times you wish to attempt network commands" + class_option "verbose", :type => :boolean, :desc => "Enable verbose output mode", :aliases => "-V" def help(cli = nil) case cli @@ -64,6 +70,12 @@ module Bundler end end + def self.handle_no_command_error(command, has_namespace = $thor_runner) + return super unless command_path = Bundler.which("bundler-#{command}") + + Kernel.exec(command_path, *ARGV[1..-1]) + end + desc "init [OPTIONS]", "Generates a Gemfile into the current working directory" long_desc <<-D Init generates a default Gemfile in the current working directory. When adding a @@ -82,12 +94,12 @@ module Bundler all gems are found, Bundler prints a success message and exits with a status of 0. If not, the first missing gem is listed and Bundler exits status 1. D + method_option "dry-run", :type => :boolean, :default => false, :banner => + "Lock the Gemfile" method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" method_option "path", :type => :string, :banner => "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" - method_option "dry-run", :type => :boolean, :default => false, :banner => - "Lock the Gemfile" def check require 'bundler/cli/check' Check.new(options).run @@ -104,41 +116,41 @@ module Bundler If the bundle has already been installed, bundler will tell you so and then exit. D - method_option "without", :type => :array, :banner => - "Exclude gems that are part of the specified named group." + method_option "binstubs", :type => :string, :lazy_default => "bin", :banner => + "Generate bin stubs for bundled gems to ./bin" + method_option "clean", :type => :boolean, :banner => + "Run bundle clean automatically after install" + method_option "deployment", :type => :boolean, :banner => + "Install using defaults tuned for deployment environments" + method_option "frozen", :type => :boolean, :banner => + "Do not allow the Gemfile.lock to be updated after this install" + method_option "full-index", :type => :boolean, :banner => + "Use the rubygems modern index instead of the API endpoint" method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" - method_option "no-prune", :type => :boolean, :banner => - "Don't remove stale gems from the cache." + method_option "jobs", :aliases => "-j", :type => :numeric, :banner => + "Specify the number of jobs to run in parallel" + method_option "local", :type => :boolean, :banner => + "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "no-cache", :type => :boolean, :banner => "Don't update the existing gem cache." + method_option "no-prune", :type => :boolean, :banner => + "Don't remove stale gems from the cache." + method_option "path", :type => :string, :banner => + "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." - method_option "local", :type => :boolean, :banner => - "Do not attempt to fetch gems remotely and use the gem cache instead" - method_option "binstubs", :type => :string, :lazy_default => "bin", :banner => - "Generate bin stubs for bundled gems to ./bin" method_option "shebang", :type => :string, :banner => "Specify a different shebang executable name than the default (usually 'ruby')" - method_option "path", :type => :string, :banner => - "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" - method_option "system", :type => :boolean, :banner => - "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" - method_option "frozen", :type => :boolean, :banner => - "Do not allow the Gemfile.lock to be updated after this install" - method_option "deployment", :type => :boolean, :banner => - "Install using defaults tuned for deployment environments" method_option "standalone", :type => :array, :lazy_default => [], :banner => "Make a bundle that can work without the Bundler runtime" - method_option "full-index", :type => :boolean, :banner => - "Use the rubygems modern index instead of the API endpoint" - method_option "clean", :type => :boolean, :banner => - "Run bundle clean automatically after install" + method_option "system", :type => :boolean, :banner => + "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" method_option "trust-policy", :alias => "P", :type => :string, :banner => "Gem trust policy (like gem install -P). Must be one of " + Bundler.rubygems.security_policy_keys.join('|') - method_option "jobs", :aliases => "-j", :type => :numeric, :banner => - "Specify the number of jobs to run in parallel" + method_option "without", :type => :array, :banner => + "Exclude gems that are part of the specified named group." def install require 'bundler/cli/install' @@ -151,17 +163,18 @@ module Bundler update when you have changed the Gemfile, or if you want to get the newest possible versions of the gems in the bundle. D - method_option "source", :type => :array, :banner => "Update a specific source (and all gems associated with it)" + method_option "full-index", :type => :boolean, :banner => + "Use the rubygems modern index instead of the API endpoint" + method_option "group", :aliases => "-g", :type => :array, :banner => + "Update a specific group" + method_option "jobs", :aliases => "-j", :type => :numeric, :banner => + "Specify the number of jobs to run in parallel" method_option "local", :type => :boolean, :banner => "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." - method_option "full-index", :type => :boolean, :banner => - "Use the rubygems modern index instead of the API endpoint" - method_option "jobs", :aliases => "-j", :type => :numeric, :banner => - "Specify the number of jobs to run in parallel" - method_option "group", :aliases => "-g", :type => :array, :banner => - "Update a specific group" + method_option "source", :type => :array, :banner => + "Update a specific source (and all gems associated with it)" def update(*gems) require 'bundler/cli/update' Update.new(options, gems).run @@ -174,21 +187,23 @@ module Bundler D method_option "paths", :type => :boolean, :banner => "List the paths of all gems that are required by your Gemfile." + method_option "outdated", :type => :boolean, + :banner => "Show verbose output including whether gems are outdated." def show(gem_name = nil) require 'bundler/cli/show' Show.new(options, gem_name).run end map %w(list) => "show" - desc "binstubs GEM [OPTIONS]", "install the binstubs of the listed gem" + desc "binstubs GEM [OPTIONS]", "Install the binstubs of the listed gem" long_desc <<-D Generate binstubs for executables in [GEM]. Binstubs are put into bin, or the --binstubs directory if one has been set. D - method_option "path", :type => :string, :lazy_default => "bin", :banner => - "binstub destination directory (default bin)" method_option "force", :type => :boolean, :default => false, :banner => - "overwrite existing binstubs if they exist" + "Overwrite existing binstubs if they exist" + method_option "path", :type => :string, :lazy_default => "bin", :banner => + "Binstub destination directory (default bin)" def binstubs(*gems) require 'bundler/cli/binstubs' Binstubs.new(options, gems).run @@ -201,10 +216,10 @@ 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 "pre", :type => :boolean, :banner => "Check for newer pre-release gems" - method_option "source", :type => :array, :banner => "Check against a specific source" 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" + method_option "source", :type => :array, :banner => "Check against a specific source" method_option "strict", :type => :boolean, :banner => "Only list newer versions allowed by your Gemfile requirements" def outdated(*gems) @@ -213,20 +228,25 @@ module Bundler end desc "cache [OPTIONS]", "Cache all the gems to vendor/cache", :hide => true - method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)." + method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms, not just the current one" + method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." def cache require 'bundler/cli/cache' Cache.new(options).run end desc "package [OPTIONS]", "Locks and then caches all of the gems into vendor/cache" - method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)." - method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." + method_option "all-platforms", :type => :boolean, :banner => "Include gems for all platforms, not just the current one" + method_option "cache-path", :type => :string, :banner => + "Specify a different cache path than the default (vendor/cache)." + method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" + method_option "no-install", :type => :boolean, :banner => "Don't actually install the gems, just package." + method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." method_option "path", :type => :string, :banner => "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" - method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" + method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." long_desc <<-D The package command will copy the .gem files for every gem in the bundle into the directory ./vendor/cache. If you then check that directory into your source @@ -274,16 +294,10 @@ module Bundler Open.new(options, name).run end - CONSOLES = { - 'pry' => :Pry, - 'ripl' => :Ripl, - 'irb' => :IRB, - } - desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded" def console(group = nil) require 'bundler/cli/console' - Console.new(options, group, CONSOLES).run + Console.new(options, group).run end desc "version", "Prints the bundler's version information" @@ -313,23 +327,25 @@ module Bundler The associated gems must also be installed via 'bundle install'. D method_option :file, :type => :string, :default => 'gem_graph', :aliases => '-f', :banner => "The name to use for the generated file. see format option" - method_option :version, :type => :boolean, :default => false, :aliases => '-v', :banner => "Set to show each gem version." - method_option :requirements, :type => :boolean, :default => false, :aliases => '-r', :banner => "Set to show the version of each required dependency." method_option :format, :type => :string, :default => "png", :aliases => '-F', :banner => "This is output format option. Supported format is png, jpg, svg, dot ..." + method_option :requirements, :type => :boolean, :default => false, :aliases => '-r', :banner => "Set to show the version of each required dependency." + method_option :version, :type => :boolean, :default => false, :aliases => '-v', :banner => "Set to show each gem version." + method_option :without, :type => :array, :default => [], :banner => "Exclude gems that are part of the specified named group." def viz require 'bundler/cli/viz' Viz.new(options).run end desc "gem GEM [OPTIONS]", "Creates a skeleton for creating a rubygem" - method_option :bin, :type => :boolean, :default => false, :aliases => '-b', :banner => "Generate a binary for your library." - method_option :test, :type => :string, :lazy_default => 'rspec', :aliases => '-t', :banner => "Generate a test directory for your library: 'rspec' is the default, but 'minitest' is also supported." - method_option :edit, :type => :string, :aliases => "-e", - :lazy_default => [ENV['BUNDLER_EDITOR'], ENV['VISUAL'], ENV['EDITOR']].find{|e| !e.nil? && !e.empty? }, - :required => false, :banner => "/path/to/your/editor", - :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" - method_option :ext, :type => :boolean, :detailt => false, :banner => "Generate the boilerplate for C extension code" - + method_option :bin, :type => :boolean, :default => false, :aliases => '-b', :desc => "Generate a binary for your library. Set a default with `bundle config gem.mit true`." + method_option :coc, :type => :boolean, :desc => "Generate a code of conduct file. Set a default with `bundle config gem.coc true`." + method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR", + :lazy_default => [ENV['BUNDLER_EDITOR'], ENV['VISUAL'], ENV['EDITOR']].find{|e| !e.nil? && !e.empty? }, + :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" + method_option :ext, :type => :boolean, :default => false, :desc => "Generate the boilerplate for C extension code" + method_option :mit, :type => :boolean, :desc => "Generate an MIT license file" + method_option :test, :type => :string, :lazy_default => 'rspec', :aliases => '-t', :banner => "rspec", + :desc => "Generate a test directory for your library, either rspec or minitest. Set a default with `bundle config gem.test rspec`." def gem(name) require 'bundler/cli/gem' Gem.new(options, name, self).run @@ -341,9 +357,9 @@ module Bundler desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory" method_option "dry-run", :type => :boolean, :default => false, :banner => - "only print out changes, do not actually clean gems" + "Only print out changes, do not actually clean gems" method_option "force", :type => :boolean, :default => false, :banner => - "forces clean even if --path is not set" + "Forces clean even if --path is not set" def clean require 'bundler/cli/clean' Clean.new(options.dup).run @@ -368,5 +384,26 @@ module Bundler Env.new.write($stdout) end + private + + # Automatically invoke `bundle install` and resume if + # Bundler.settings[:auto_install] exists. This is set through config cmd + # `bundle config auto_install 1`. + # + # Note that this method `nil`s out the global Definition object, so it + # should be called first, before you instantiate anything like an + # `Installer` that'll keep a reference to the old one instead. + def auto_install + return unless Bundler.settings[:auto_install] + + begin + Bundler.definition.specs + rescue GemNotFound + Bundler.ui.info "Automatically installing missing gems." + Bundler.reset! + invoke :install, [] + Bundler.reset! + end + end end end diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb index 653ead7f56..751cb80149 100644 --- a/lib/bundler/cli/cache.rb +++ b/lib/bundler/cli/cache.rb @@ -9,6 +9,7 @@ module Bundler Bundler.definition.validate_ruby! Bundler.definition.resolve_with_cache! setup_cache_all + Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms") Bundler.load.cache Bundler.settings[:no_prune] = true if options["no-prune"] Bundler.load.lock diff --git a/lib/bundler/cli/clean.rb b/lib/bundler/cli/clean.rb index e7a4c01460..99508820e2 100644 --- a/lib/bundler/cli/clean.rb +++ b/lib/bundler/cli/clean.rb @@ -7,10 +7,17 @@ module Bundler end def run - if Bundler.settings[:path] || options[:force] - Bundler.load.clean(options[:"dry-run"]) - else - Bundler.ui.error "Can only use bundle clean when --path is set or --force is set" + require_path_or_force + Bundler.load.clean(options[:"dry-run"]) + end + + protected + + def require_path_or_force + if !Bundler.settings[:path] && !options[:force] + Bundler.ui.error "Cleaning all the gems on your system is dangerous! " \ + "If you're sure you want to remove every system gem not in this " \ + "bundle, run `bundle clean --force`." exit 1 end end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 0548525af0..83315a272e 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -25,6 +25,8 @@ module Bundler else ask_for_spec_from(specs) end + rescue RegexpError + raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies) end def self.ask_for_spec_from(specs) diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb index bf000936fc..bc4f377d25 100644 --- a/lib/bundler/cli/console.rb +++ b/lib/bundler/cli/console.rb @@ -1,41 +1,37 @@ module Bundler class CLI::Console - attr_reader :options, :group, :consoles - def initialize(options, group, consoles) + attr_reader :options, :group + def initialize(options, group) @options = options @group = group - @consoles = consoles end def run group ? Bundler.require(:default, *(group.split.map! {|g| g.to_sym })) : Bundler.require ARGV.clear - preferred = Bundler.settings[:console] || 'irb' - - # See if console is available - begin - require preferred || true - rescue LoadError - # Is it in Gemfile? - Bundler.ui.error "Could not load the #{preferred} console" - Bundler.ui.info "Falling back on IRB..." - - require 'irb' - preferred = 'irb' - end - - constant = consoles[preferred] + console = get_console(Bundler.settings[:console] || 'irb') + console.start + end - console = begin - Object.const_get(constant) - rescue NameError => e - Bundler.ui.error e.inspect - Bundler.ui.error "Could not load the #{constant} console" - return - end + def get_console(name) + require name + get_constant(name) + rescue LoadError + Bundler.ui.error "Couldn't load console #{name}" + get_constant('irb') + end - console.start + def get_constant(name) + const_name = { + 'pry' => :Pry, + 'ripl' => :Ripl, + 'irb' => :IRB, + }[name] + Object.const_get(const_name) + rescue NameError + Bundler.ui.error "Could not find constant #{const_name}" + exit 1 end end diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb index 1739ab6f78..7713906f26 100644 --- a/lib/bundler/cli/exec.rb +++ b/lib/bundler/cli/exec.rb @@ -1,36 +1,43 @@ module Bundler class CLI::Exec - attr_reader :options, :args + attr_reader :options, :args, :cmd def initialize(options, args) @options = options + @cmd = args.shift @args = args + + if RUBY_VERSION >= "2.0" + @args << { :close_others => !options.keep_file_descriptors? } + elsif options.keep_file_descriptors? + Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec." + end end def run - Bundler.definition.validate_ruby! - Bundler.load.setup_environment + raise ArgumentError if cmd.nil? - begin - if RUBY_VERSION >= "2.0" - @args << { :close_others => !options.keep_file_descriptors? } - elsif options.keep_file_descriptors? - Bundler.ui.warn "Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec." - end - - # Run - Kernel.exec(*args) - rescue Errno::EACCES - Bundler.ui.error "bundler: not executable: #{args.first}" - exit 126 - rescue Errno::ENOENT - Bundler.ui.error "bundler: command not found: #{args.first}" - Bundler.ui.warn "Install missing gem executables with `bundle install`" - exit 127 - rescue ArgumentError - Bundler.ui.error "bundler: exec needs a command to run" - exit 128 + # First, try to exec directly to something in PATH + SharedHelpers.set_bundle_environment + bin_path = Bundler.which(@cmd) + if bin_path + Kernel.exec(bin_path, *args) end + + # If that didn't work, set up the whole bundle + Bundler.definition.validate_ruby! + Bundler.load.setup_environment + Kernel.exec(@cmd, *args) + rescue Errno::EACCES + Bundler.ui.error "bundler: not executable: #{cmd}" + exit 126 + rescue Errno::ENOENT + Bundler.ui.error "bundler: command not found: #{cmd}" + Bundler.ui.warn "Install missing gem executables with `bundle install`" + exit 127 + rescue ArgumentError + Bundler.ui.error "bundler: exec needs a command to run" + exit 128 end end diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 243f02856b..313cfbabfb 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -1,31 +1,32 @@ +require 'pathname' + module Bundler class CLI::Gem - attr_reader :options, :gem_name, :thor + attr_reader :options, :gem_name, :thor, :name, :target + def initialize(options, gem_name, thor) @options = options - @gem_name = gem_name + @gem_name = resolve_name(gem_name) @thor = thor + + @name = @gem_name + @target = Pathname.pwd.join(gem_name) + + validate_ext_name if options[:ext] end def run - if options[:ext] && gem_name.index('-') - Bundler.ui.error "You have specified a gem name which does not conform to the \n" \ - "naming guidelines for C extensions. For more information, \n" \ - "see the 'Extension Naming' section at the following URL:\n" \ - "http://guides.rubygems.org/gems-with-extensions/\n" - exit 1 - end + Bundler.ui.confirm "Creating gem '#{name}'..." - name = gem_name.chomp("/") # remove trailing slash if present underscored_name = name.tr('-', '_') namespaced_path = name.tr('-', '/') - target = File.join(Dir.pwd, name) - constant_name = name.split('_').map{|p| p[0..0].upcase + p[1..-1] }.join + constant_name = name.split('_').map{|p| p[0..0].upcase + p[1..-1] unless p.empty?}.join constant_name = constant_name.split('-').map{|q| q[0..0].upcase + q[1..-1] }.join('::') if constant_name =~ /-/ constant_array = constant_name.split('::') git_user_name = `git config user.name`.chomp git_user_email = `git config user.email`.chomp - opts = { + + config = { :name => name, :underscored_name => underscored_name, :namespaced_path => namespaced_path, @@ -35,44 +36,130 @@ module Bundler :author => git_user_name.empty? ? "TODO: Write your name" : git_user_name, :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email, :test => options[:test], - :ext => options[:ext] + :ext => options[:ext], + :bin => options[:bin] + } + + templates = { + "Gemfile.tt" => "Gemfile", + "gitignore.tt" => ".gitignore", + "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb", + "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb", + "newgem.gemspec.tt" => "#{name}.gemspec", + "Rakefile.tt" => "Rakefile", + "README.md.tt" => "README.md", + "bin/console.tt" => "bin/console", + "bin/setup.tt" => "bin/setup" } - gemspec_dest = File.join(target, "#{name}.gemspec") - thor.template(File.join("newgem/Gemfile.tt"), File.join(target, "Gemfile"), opts) - thor.template(File.join("newgem/Rakefile.tt"), File.join(target, "Rakefile"), opts) - thor.template(File.join("newgem/LICENSE.txt.tt"), File.join(target, "LICENSE.txt"), opts) - thor.template(File.join("newgem/README.md.tt"), File.join(target, "README.md"), opts) - thor.template(File.join("newgem/gitignore.tt"), File.join(target, ".gitignore"), opts) - thor.template(File.join("newgem/newgem.gemspec.tt"), gemspec_dest, opts) - thor.template(File.join("newgem/lib/newgem.rb.tt"), File.join(target, "lib/#{namespaced_path}.rb"), opts) - thor.template(File.join("newgem/lib/newgem/version.rb.tt"), File.join(target, "lib/#{namespaced_path}/version.rb"), opts) - if options[:bin] - thor.template(File.join("newgem/bin/newgem.tt"), File.join(target, 'bin', name), opts) + + if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?", + "Codes of conduct can increase contributions to your project by contributors who " \ + "prefer collaborative, safe spaces. You can read more about the code of conduct at " \ + "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \ + "of enforcing it, so be sure that you are prepared to do that. For suggestions about " \ + "how to enforce codes of conduct, see bit.ly/coc-enforcement." + ) + templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md") end - case options[:test] - when 'rspec' - thor.template(File.join("newgem/rspec.tt"), File.join(target, ".rspec"), opts) - thor.template(File.join("newgem/spec/spec_helper.rb.tt"), File.join(target, "spec/spec_helper.rb"), opts) - thor.template(File.join("newgem/spec/newgem_spec.rb.tt"), File.join(target, "spec/#{namespaced_path}_spec.rb"), opts) - when 'minitest' - thor.template(File.join("newgem/test/minitest_helper.rb.tt"), File.join(target, "test/minitest_helper.rb"), opts) - thor.template(File.join("newgem/test/test_newgem.rb.tt"), File.join(target, "test/test_#{namespaced_path}.rb"), opts) + + if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?", + "This means that any other developer or company will be legally allowed to use your code " \ + "for free as long as they admit you created it. You can read more about the MIT license " \ + "at choosealicense.com/licenses/mit." + ) + config[:mit] = true + templates.merge!("LICENSE.txt.tt" => "LICENSE.txt") end - if options[:test] - thor.template(File.join("newgem/.travis.yml.tt"), File.join(target, ".travis.yml"), opts) + + if test_framework = ask_and_set_test_framework + templates.merge!(".travis.yml.tt" => ".travis.yml") + + case test_framework + when 'rspec' + templates.merge!( + "rspec.tt" => ".rspec", + "spec/spec_helper.rb.tt" => "spec/spec_helper.rb", + "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb" + ) + when 'minitest' + templates.merge!( + "test/minitest_helper.rb.tt" => "test/minitest_helper.rb", + "test/test_newgem.rb.tt" => "test/test_#{namespaced_path}.rb" + ) + end end + + templates.merge!("exe/newgem.tt" => "exe/#{name}") if options[:bin] + if options[:ext] - thor.template(File.join("newgem/ext/newgem/extconf.rb.tt"), File.join(target, "ext/#{name}/extconf.rb"), opts) - thor.template(File.join("newgem/ext/newgem/newgem.h.tt"), File.join(target, "ext/#{name}/#{underscored_name}.h"), opts) - thor.template(File.join("newgem/ext/newgem/newgem.c.tt"), File.join(target, "ext/#{name}/#{underscored_name}.c"), opts) + templates.merge!( + "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", + "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c" + ) end + + templates.each do |src, dst| + thor.template("newgem/#{src}", target.join(dst), config) + end + Bundler.ui.info "Initializing git repo in #{target}" Dir.chdir(target) { `git init`; `git add .` } if options[:edit] - thor.run("#{options["edit"]} \"#{gemspec_dest}\"") # Open gemspec in editor + # Open gemspec in editor + thor.run("#{options["edit"]} \"#{target.join("#{name}.gemspec")}\"") end end + private + + def resolve_name(name) + Pathname.pwd.join(name).basename.to_s + end + + def ask_and_set(key, header, message) + choice = options[key] || Bundler.settings["gem.#{key}"] + + if choice.nil? + Bundler.ui.confirm header + choice = (Bundler.ui.ask("#{message} y/(n):") =~ /y|yes/) + Bundler.settings.set_global("gem.#{key}", choice) + end + + choice + end + + def validate_ext_name + return unless gem_name.index('-') + + Bundler.ui.error "You have specified a gem name which does not conform to the \n" \ + "naming guidelines for C extensions. For more information, \n" \ + "see the 'Extension Naming' section at the following URL:\n" \ + "http://guides.rubygems.org/gems-with-extensions/\n" + exit 1 + end + + def ask_and_set_test_framework + test_framework = options[:test] || Bundler.settings["gem.test"] + + if test_framework.nil? + Bundler.ui.confirm "Do you want to generate tests with your gem?" + result = Bundler.ui.ask "Type 'rspec' or 'minitest' to generate those test files now and " \ + "in the future. rspec/minitest/(none):" + if result =~ /rspec|minitest/ + test_framework = result + else + test_framework = false + end + end + + if Bundler.settings["gem.test"].nil? + Bundler.settings.set_global("gem.test", test_framework) + end + + test_framework + end + end end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index b2f16ed26f..43dd40a500 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -44,7 +44,7 @@ module Bundler "before deploying." end - if Bundler.root.join("vendor/cache").exist? + if Bundler.app_cache.exist? options[:local] = true end @@ -66,6 +66,7 @@ module Bundler Bundler.settings[:shebang] = options["shebang"] if options["shebang"] Bundler.settings[:jobs] = options["jobs"] if options["jobs"] Bundler.settings[:no_prune] = true if options["no-prune"] + Bundler.settings[:no_install] = true if options["no-install"] Bundler.settings[:clean] = options["clean"] if options["clean"] Bundler.settings.without = options[:without] Bundler::Fetcher.disable_endpoint = options["full-index"] @@ -77,23 +78,24 @@ module Bundler definition = Bundler.definition definition.validate_ruby! Installer.install(Bundler.root, definition, options) - Bundler.load.cache if Bundler.root.join("vendor/cache").exist? && !options["no-cache"] && !Bundler.settings[:frozen] + Bundler.load.cache if Bundler.app_cache.exist? && !options["no-cache"] && !Bundler.settings[:frozen] + + Bundler.ui.confirm "Bundle complete! #{dependencies_count_for(definition)}, #{gems_installed_for(definition)}." + confirm_without_groups if Bundler.settings[:path] absolute_path = File.expand_path(Bundler.settings[:path]) relative_path = absolute_path.sub(File.expand_path('.'), '.') - Bundler.ui.confirm "Your bundle is complete!" - without_groups_messages - Bundler.ui.confirm "It was installed into #{relative_path}" + Bundler.ui.confirm "Bundled gems are installed into #{relative_path}." else - Bundler.ui.confirm "Your bundle is complete!" - without_groups_messages Bundler.ui.confirm "Use `bundle show [gemname]` to see where a bundled gem is installed." end + Installer.post_install_messages.to_a.each do |name, msg| Bundler.ui.confirm "Post-install message from #{name}:" Bundler.ui.info msg end + Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris| Bundler.ui.error "Warning: the gem '#{name}' was found in multiple sources." Bundler.ui.error "Installed from: #{installed_from_uri}" @@ -111,7 +113,7 @@ module Bundler end rescue GemNotFound, VersionConflict => e if options[:local] && Bundler.app_cache.exist? - Bundler.ui.warn "Some gems seem to be missing from your vendor/cache directory." + Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory." end unless Bundler.definition.has_rubygems_remotes? @@ -133,11 +135,22 @@ module Bundler "application for all non-root users on this machine.", :wrap => true end - def without_groups_messages + def confirm_without_groups if Bundler.settings.without.any? require "bundler/cli/common" Bundler.ui.confirm Bundler::CLI::Common.without_groups_message end end + + def dependencies_count_for(definition) + count = definition.dependencies.count + "#{count} Gemfile #{count == 1 ? 'dependency' : 'dependencies'}" + end + + def gems_installed_for(definition) + count = definition.specs.count + "#{count} #{count == 1 ? 'gem' : 'gems'} now installed" + end + end end diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index ee6387acb7..f8779d762a 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -70,7 +70,7 @@ module Bundler end if out_count.zero? - Bundler.ui.info "Your bundle is up to date!\n" + Bundler.ui.info "Bundle up to date!\n" else exit 1 end diff --git a/lib/bundler/cli/package.rb b/lib/bundler/cli/package.rb index 5adad44c35..f99678c9ec 100644 --- a/lib/bundler/cli/package.rb +++ b/lib/bundler/cli/package.rb @@ -9,6 +9,8 @@ module Bundler def run Bundler.ui.level = "error" if options[:quiet] Bundler.settings[:path] = File.expand_path(options[:path]) if options[:path] + Bundler.settings[:cache_all_platforms] = options["all-platforms"] if options.key?("all-platforms") + Bundler.settings[:cache_path] = options["cache-path"] if options.key?("cache-path") setup_cache_all install @@ -22,7 +24,12 @@ module Bundler def install require 'bundler/cli/install' - Bundler::CLI::Install.new(options.dup).run + options = self.options.dup + if Bundler.settings[:cache_all_platforms] + options["local"] = false + options["update"] = true + end + Bundler::CLI::Install.new(options).run end def setup_cache_all diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb index 07ff63dcd8..96c1490209 100644 --- a/lib/bundler/cli/show.rb +++ b/lib/bundler/cli/show.rb @@ -2,10 +2,12 @@ require 'bundler/cli/common' module Bundler class CLI::Show - attr_reader :options, :gem_name + attr_reader :options, :gem_name, :latest_specs def initialize(options, gem_name) @options = options @gem_name = gem_name + @verbose = options[:verbose] || options[:outdated] + @latest_specs = fetch_latest_specs if @verbose end def run @@ -36,13 +38,37 @@ module Bundler Bundler.ui.info "Gems included by the bundle:" Bundler.load.specs.sort_by { |s| s.name }.each do |s| desc = " * #{s.name} (#{s.version}#{s.git_version})" - if @options[:verbose] - Bundler.ui.info "#{desc} - #{s.summary || 'No description available.'}" + if @verbose + latest = latest_specs.find { |l| l.name == s.name } + Bundler.ui.info <<-END.gsub(/^ +/, '') + #{desc} + \tSummary: #{s.summary || 'No description available.'} + \tHomepage: #{s.homepage || 'No website available.'} + \tStatus: #{outdated?(s, latest) ? "Outdated - #{s.version} < #{latest.version}" : "Up to date"} + END else Bundler.ui.info desc end end end end + + private + + def fetch_latest_specs + definition = Bundler.definition(true) + if options[:outdated] + Bundler.ui.info "Fetching remote specs for outdated check...\n\n" + Bundler.ui.silence { definition.resolve_remotely! } + else + definition.resolve_with_cache! + end + definition.specs + end + + def outdated?(current, latest) + return false unless latest + Gem::Version.new(current.version) < Gem::Version.new(latest.version) + end end end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index dd15de9936..6ed38f2c3a 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -49,14 +49,14 @@ module Bundler Bundler.definition.validate_ruby! Installer.install Bundler.root, Bundler.definition, opts - Bundler.load.cache if Bundler.root.join("vendor/cache").exist? + Bundler.load.cache if Bundler.app_cache.exist? if Bundler.settings[:clean] && Bundler.settings[:path] require "bundler/cli/clean" Bundler::CLI::Clean.new(options).run end - Bundler.ui.confirm "Your bundle is updated!" + Bundler.ui.confirm "Bundle updated!" without_groups_messages end diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb index c404ddbbc5..09557e599c 100644 --- a/lib/bundler/cli/viz.rb +++ b/lib/bundler/cli/viz.rb @@ -8,7 +8,7 @@ module Bundler def run require 'graphviz' output_file = File.expand_path(options[:file]) - graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format]) + graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without]) graph.viz rescue LoadError => e Bundler.ui.error e.inspect diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 2b15b15573..86dbfba2a5 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -129,7 +129,7 @@ module Bundler # @return [Bundler::SpecSet] def specs @specs ||= begin - specs = resolve.materialize(requested_dependencies) + specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies) unless specs["bundler"].any? local = Bundler.settings[:frozen] ? rubygems_index : index @@ -201,11 +201,7 @@ module Bundler sources.all_sources.each do |source| source.dependency_names = dependency_names.dup idx.add_source source.specs - - if source.is_a?(Source::Git) || source.is_a?(Source::Path) - dependency_names -= source.specs.map{|s| s.name }.uniq - end - + dependency_names -= pinned_spec_names(source.specs) dependency_names.push(*source.unmet_deps).uniq! end end @@ -409,11 +405,18 @@ module Bundler if locked unlocking = @locked_specs.any? do |locked_spec| - locked_spec.source != locked + locked_spec.source.class == locked.class && locked_spec.source != locked end end - !locked || unlocking || source.specs != locked.specs + !locked || unlocking || dependencies_for_source_changed?(locked) || source.specs != locked.specs + end + + def dependencies_for_source_changed?(source) + deps_for_source = @dependencies.select { |s| s.source == source } + locked_deps_for_source = @locked_deps.select { |s| s.source == source } + + deps_for_source != locked_deps_for_source end # Get all locals and override their matching sources. @@ -607,5 +610,19 @@ module Bundler source_requirements end + def pinned_spec_names(specs) + names = [] + specs.each do |s| + # TODO when two sources without blocks is an error, we can change + # this check to !s.source.is_a?(Source::LocalRubygems). For now, + # we need to ask every Rubygems for every gem name. + if s.source.is_a?(Source::Git) || s.source.is_a?(Source::Path) + names << s.name + end + end + names.uniq! + names + end + end end diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 0224c3feca..6a9284fd56 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -26,6 +26,14 @@ module Bundler :jruby_18 => Gem::Platform::JAVA, :jruby_19 => Gem::Platform::JAVA, :mswin => Gem::Platform::MSWIN, + :mswin_18 => Gem::Platform::MSWIN, + :mswin_19 => Gem::Platform::MSWIN, + :mswin_20 => Gem::Platform::MSWIN, + :mswin_21 => Gem::Platform::MSWIN, + :mswin64 => Gem::Platform::MSWIN64, + :mswin64_19 => Gem::Platform::MSWIN64, + :mswin64_20 => Gem::Platform::MSWIN64, + :mswin64_21 => Gem::Platform::MSWIN64, :mingw => Gem::Platform::MINGW, :mingw_18 => Gem::Platform::MINGW, :mingw_19 => Gem::Platform::MINGW, @@ -93,7 +101,6 @@ module Bundler out << "\n" end - def specific? super rescue NoMethodError diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index ef5c6fbaaa..9736525a4f 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -24,14 +24,14 @@ module Bundler @platforms = [] @env = nil @ruby_version = nil - add_github_sources + add_git_sources end def eval_gemfile(gemfile, contents = nil) contents ||= Bundler.read_file(gemfile.to_s) instance_eval(contents, gemfile.to_s, 1) rescue SyntaxError => e - syntax_msg = e.message.gsub("#{gemfile.to_s}:", 'on line ') + syntax_msg = e.message.gsub("#{gemfile}:", 'on line ') raise GemfileError, "Gemfile syntax error #{syntax_msg}" rescue ScriptError, RegexpError, NameError, ArgumentError => e e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})" @@ -112,6 +112,7 @@ module Bundler if block_given? with_source(@sources.add_rubygems_source("remotes" => source), &blk) else + check_primary_source_safety(@sources) @sources.add_rubygems_remote(source) end end @@ -148,6 +149,14 @@ module Bundler with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk) end + def github(repo, options = {}) + raise ArgumentError, "Github sources require a block" unless block_given? + github_uri = @git_sources["github"].call(repo) + git_options = normalize_hash(options).merge("uri" => github_uri) + git_source = @sources.add_git_source(git_options) + with_source(git_source) { yield } + end + def to_definition(lockfile, unlock) Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version) end @@ -182,13 +191,19 @@ module Bundler private - def add_github_sources + def add_git_sources git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "git://github.com/#{repo_name}.git" end git_source(:gist){ |repo_name| "https://gist.github.com/#{repo_name}.git" } + + git_source(:bitbucket) do |repo_name| + user_name, repo_name = repo_name.split '/' + repo_name ||= user_name + "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git" + end end def with_source(source) @@ -214,7 +229,10 @@ module Bundler def normalize_options(name, version, opts) if name.is_a?(Symbol) - raise GemfileError, %{You need to specify gem names as Strings. Use 'gem "#{name.to_s}"' instead.} + raise GemfileError, %{You need to specify gem names as Strings. Use 'gem "#{name}"' instead.} + end + if name =~ /\s/ + raise GemfileError, %{'#{name}' is not a valid gem name because it contains whitespace.} end normalize_hash(opts) @@ -290,5 +308,24 @@ module Bundler raise GemfileError, "Unknown source '#{source}'" end end + + def check_primary_source_safety(source) + return unless source.rubygems_primary_remotes.any? + + if Bundler.settings[:disable_multisource] + raise GemspecError, "Warning: this Gemfile contains multiple primary sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source. To downgrade this error to a warning, run " \ + "`bundle config --delete disable_multisource`." + else + Bundler.ui.warn "Warning: this Gemfile contains multiple primary sources. " \ + "Using `source` more than once without a block is a security risk, and " \ + "may result in installing unexpected gems. To resolve this warning, use " \ + "a block to indicate which gems should come from the secondary source. " \ + "To upgrade this warning to an error, run `bundle config " \ + "disable_multisource true`." + end + end + end end diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 8566f26562..71d5e115fc 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -18,7 +18,7 @@ module Bundler end # needed for standalone, load required_paths from local gemspec - # after the gem in installed + # after the gem is installed def require_paths if @remote_specification @remote_specification.require_paths diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb index 0f6f315346..031d7fd97b 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -1,43 +1,44 @@ +require 'bundler/rubygems_integration' +require 'bundler/source/git/git_proxy' + module Bundler class Env def write(io) - io.write(report) + io.write report(:print_gemfile => true) end - def report - out = "Bundler #{Bundler::VERSION}\n" - - out << "Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}" - out << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL - out << ") [#{RUBY_PLATFORM}]\n" - - out << "Rubygems #{Gem::VERSION}\n" - - out << "rvm #{ENV['rvm_version']}\n" if ENV['rvm_version'] - - out << "GEM_HOME #{ENV['GEM_HOME']}\n" - - out << "GEM_PATH #{ENV['GEM_PATH']}\n" unless ENV['GEM_PATH'] == ENV['GEM_HOME'] + def report(options = {}) + print_gemfile = options.delete(:print_gemfile) + out = "Environment\n\n" + out << " Bundler #{Bundler::VERSION}\n" + out << " Rubygems #{Gem::VERSION}\n" + out << " Ruby #{ruby_version}" + out << " GEM_HOME #{ENV['GEM_HOME']}\n" unless ENV['GEM_HOME'].nil? || ENV['GEM_HOME'].empty? + 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" %w(rubygems-bundler open_gem).each do |name| - specs = Gem::Specification.find_all{|s| s.name == name } - out << "#{name} (#{specs.map(&:version).join(',')})\n" unless specs.empty? + specs = Bundler.rubygems.find_name(name) + out << " #{name} (#{specs.map(&:version).join(',')})\n" unless specs.empty? end - out << "\nBundler settings\n" unless Bundler.settings.all.empty? + out << "\nBundler settings\n\n" unless Bundler.settings.all.empty? Bundler.settings.all.each do |setting| - out << " #{setting}\n" + out << " " << setting << "\n" Bundler.settings.pretty_values_for(setting).each do |line| - out << " " << line << "\n" + out << " " << line << "\n" end end - out << "\n\n" << "Gemfile\n" - out << read_file("Gemfile") << "\n" + if print_gemfile + out << "\nGemfile\n\n" + out << " " << read_file(Bundler.default_gemfile).gsub(/\n/, "\n ") << "\n" - out << "\n\n" << "Gemfile.lock\n" - out << read_file("Gemfile.lock") << "\n" + out << "\n" << "Gemfile.lock\n\n" + out << " " << read_file(Bundler.default_lockfile).gsub(/\n/, "\n ") << "\n" + end out end @@ -45,12 +46,30 @@ module Bundler private def read_file(filename) - File.read(filename).strip + File.read(filename.to_s).strip rescue Errno::ENOENT "<No #{filename} found>" rescue => e "#{e.class}: #{e.message}" end + def ruby_version + str = "#{RUBY_VERSION}" + if RUBY_VERSION < '1.9' + str << " (#{RUBY_RELEASE_DATE}" + str << " patchlevel #{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + str << ") [#{RUBY_PLATFORM}]\n" + else + str << "p#{RUBY_PATCHLEVEL}" if defined? RUBY_PATCHLEVEL + str << " (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]\n" + end + end + + def git_version + Bundler::Source::Git::GitProxy.new(nil, nil, nil).version + rescue Bundler::Source::Git::GitNotInstalledError + "not installed" + end + end end diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index e9eec30370..46ac8e44e8 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -6,6 +6,8 @@ module Bundler # Handles all the fetching with the rubygems server class Fetcher + # This error is raised when it looks like the network is down + class NetworkDownError < HTTPError; end # This error is raised if the API returns a 413 (only printed in verbose) class FallbackError < HTTPError; end # This is the error raised if OpenSSL fails the cert verification @@ -73,18 +75,27 @@ module Bundler ruby = Bundler.ruby_version agent = "bundler/#{Bundler::VERSION}" - agent += " rubygems/#{Gem::VERSION}" - agent += " ruby/#{ruby.version}" - agent += " (#{ruby.host})" - agent += " command/#{ARGV.first}" + agent << " rubygems/#{Gem::VERSION}" + agent << " ruby/#{ruby.version}" + agent << " (#{ruby.host})" + agent << " command/#{ARGV.first}" if ruby.engine != "ruby" # engine_version raises on unknown engines engine_version = ruby.engine_version rescue "???" - agent += " #{ruby.engine}/#{engine_version}" + agent << " #{ruby.engine}/#{engine_version}" end + + agent << " options/#{Bundler.settings.all.join(",")}" + # add a random ID so we can consolidate runs server-side agent << " " << SecureRandom.hex(8) + + # add any user agent strings set in the config + extra_ua = Bundler.settings[:user_agent] + agent << " " << extra_ua if extra_ua + + agent end end @@ -95,7 +106,7 @@ module Bundler @api_timeout = 10 # How long to wait for each API call @max_retries = 3 # How many retries for the API call - @anonymizable_uri = resolve_remote_uri(remote_uri) + @anonymizable_uri = configured_uri_for(remote_uri) Socket.do_not_reverse_lookup = true connection # create persistent connection @@ -231,6 +242,8 @@ module Bundler elsif fetch(dependency_api_uri) @use_api = true end + rescue NetworkDownError => e + raise HTTPError, e.message rescue AuthenticationRequiredError # We got a 401 from the server. Don't fall back to the full index, just fail. raise @@ -270,7 +283,7 @@ module Bundler when Net::HTTPRequestEntityTooLarge raise FallbackError, response.body when Net::HTTPUnauthorized - raise AuthenticationRequiredError, remote_uri + raise AuthenticationRequiredError, remote_uri.host else raise HTTPError, "#{response.class}: #{response.body}" end @@ -289,7 +302,13 @@ module Bundler raise CertificateFailureError.new(uri) rescue *HTTP_ERRORS => e Bundler.ui.trace e - raise HTTPError, "Network error while fetching #{uri}" + case e.message + when /host down:/, /getaddrinfo: nodename nor servname provided/ + raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ + "connection and try again." + else + raise HTTPError, "Network error while fetching #{uri}" + end end def dependency_api_uri(gem_names = []) @@ -304,7 +323,7 @@ module Bundler gem_list = [] deps_list = [] - gem_names.each_slice(Source::Rubygems::API_REQUEST_LIMIT) do |names| + gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| marshalled_deps = fetch dependency_api_uri(names) gem_list += Bundler.load_marshal(marshalled_deps) end @@ -377,17 +396,10 @@ module Bundler private - def resolve_remote_uri(uri) - add_configured_credentials(Bundler::Source.mirror_for(uri)) - end - - def add_configured_credentials(uri) - auth = Bundler.settings[uri.to_s] - if auth - uri = uri.dup - uri.user, uri.password = *auth.split(":", 2) - end - AnonymizableURI.new(uri) + def configured_uri_for(uri) + uri = Bundler::Source.mirror_for(uri) + config_auth = Bundler.settings[uri.to_s] || Bundler.settings[uri.host] + AnonymizableURI.new(uri, config_auth) end def fetch_uri diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index 818a6b55f7..fde582c7f7 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require "bundler/vendored_thor" module Bundler @@ -33,10 +34,42 @@ module Bundler rescue SystemExit => e exit e.status rescue Exception => e - Bundler.ui.error <<-ERR, :wrap => true - Unfortunately, a fatal error has occurred. Please see the Bundler \ - troubleshooting documentation at http://bit.ly/bundler-issues. Thanks! - ERR - raise e + request_issue_report_for(e) + exit 1 + end + + def self.request_issue_report_for(e) + Bundler.ui.info <<-EOS.gsub(/^ {6}/, '') + #{'――― ERROR REPORT TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――'} + - What did you do? + - What did you expect to happen? + - What happened instead? + + Error details + + #{e.class}: #{e.message} + #{e.backtrace.join("\n ")} + + #{Bundler::Env.new.report(:print_gemfile => false).gsub(/\n/, "\n ").strip} + #{'――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'} + + EOS + + Bundler.ui.error "Unfortunately, an unexpected error occurred, and Bundler cannot continue." + + Bundler.ui.warn <<-EOS.gsub(/^ {6}/, '') + + First, try this link to see if there are any existing issue reports for this error: + #{issues_url(e)} + + If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at: + https://github.com/bundler/bundler/issues/new + EOS end + + def self.issues_url(exception) + 'https://github.com/bundler/bundler/search?q=' \ + "#{CGI.escape(exception.message)}&type=Issues" + end + end diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index 6b7a1760ca..6db31901e4 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -44,9 +44,22 @@ module Bundler install_gem(built_gem_path) end - desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to Rubygems" - task 'release' => 'build' do - release_gem(built_gem_path) + desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to Rubygems\n" \ + "To prevent publishing in Rubygems use `gem_push=no rake release`" + task 'release' => ['build', 'release:guard_clean', + 'release:source_control_push', 'release:rubygem_push'] do + end + + task 'release:guard_clean' do + guard_clean + end + + task 'release:source_control_push' do + tag_version { git_push } unless already_tagged? + end + + task 'release:rubygem_push' do + rubygem_push(built_gem_path) if gem_push? end GemHelper.instance = self @@ -70,13 +83,6 @@ module Bundler Bundler.ui.confirm "#{name} (#{version}) installed." end - def release_gem(built_gem_path=nil) - guard_clean - built_gem_path ||= build_gem - tag_version { git_push } unless already_tagged? - rubygem_push(built_gem_path) if gem_push? - end - protected def rubygem_push(path) if Pathname.new("~/.gem/credentials").expand_path.exist? diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb index 2040604b78..a3bb514954 100644 --- a/lib/bundler/gem_helpers.rb +++ b/lib/bundler/gem_helpers.rb @@ -5,6 +5,7 @@ module Bundler GENERICS = [ [Gem::Platform.new('java'), Gem::Platform.new('java')], [Gem::Platform.new('mswin32'), Gem::Platform.new('mswin32')], + [Gem::Platform.new('mswin64'), Gem::Platform.new('mswin64')], [Gem::Platform.new('x64-mingw32'), Gem::Platform.new('x64-mingw32')], [Gem::Platform.new('x86_64-mingw32'), Gem::Platform.new('x64-mingw32')], [Gem::Platform.new('mingw32'), Gem::Platform.new('x86-mingw32')] diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb index 92f538b9fd..0b5c5723b4 100644 --- a/lib/bundler/graph.rb +++ b/lib/bundler/graph.rb @@ -3,12 +3,13 @@ module Bundler class Graph GRAPH_NAME = :Gemfile - def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png") + def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = []) @env = env @output_file = output_file @show_version = show_version @show_requirements = show_requirements @output_format = output_format + @without_groups = without.map(&:to_sym) @groups = [] @relations = Hash.new {|h, k| h[k] = Set.new} @@ -53,6 +54,8 @@ module Bundler relations = Hash.new {|h, k| h[k] = Set.new} @env.current_dependencies.each do |dependency| dependency.groups.each do |group| + next if @without_groups.include?(group) + relations[group.to_s].add(dependency) @relations[group.to_s].add(dependency.name) diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index 30f1940323..8076c685e5 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -16,7 +16,7 @@ module Bundler def initialize @sources = [] @cache = {} - @specs = Hash.new { |h,k| h[k] = [] } + @specs = Hash.new { |h,k| h[k] = Hash.new } @all_specs = Hash.new { |h,k| h[k] = [] } end @@ -24,11 +24,11 @@ module Bundler super @sources = @sources.dup @cache = {} - @specs = Hash.new { |h,k| h[k] = [] } + @specs = Hash.new { |h,k| h[k] = Hash.new } @all_specs = Hash.new { |h,k| h[k] = [] } - o.specs.each do |name, array| - @specs[name] = array.dup + o.specs.each do |name, hash| + @specs[name] = hash.dup end o.all_specs.each do |name, array| @all_specs[name] = array.dup @@ -88,19 +88,14 @@ module Bundler alias [] search def <<(spec) - arr = specs_by_name(spec.name) + @specs[spec.name]["#{spec.version}-#{spec.platform}"] = spec - arr.delete_if do |s| - same_version?(s.version, spec.version) && s.platform == spec.platform - end - - arr << spec spec end def each(&blk) - specs.values.each do |specs| - specs.each(&blk) + specs.values.each do |spec_sets| + spec_sets.values.each(&blk) end end @@ -123,9 +118,9 @@ module Bundler if (dupes = search_by_spec(s)) && dupes.any? @all_specs[s.name] = [s] + dupes next unless override_dupes - @specs[s.name] -= dupes + self << s end - @specs[s.name] << s + self << s end self end @@ -155,7 +150,7 @@ module Bundler private def specs_by_name(name) - @specs[name] + @specs[name].values end def search_by_dependency(dependency, base = nil) @@ -182,9 +177,8 @@ module Bundler end def search_by_spec(spec) - specs_by_name(spec.name).select do |s| - same_version?(s.version, spec.version) && Gem::Platform.new(s.platform) == Gem::Platform.new(spec.platform) - end + spec = @specs[spec.name]["#{spec.version}-#{spec.platform}"] + spec ? [spec] : [] end if RUBY_VERSION < '1.9' diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index a4747c61c9..0654d91f09 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -1,6 +1,6 @@ require 'erb' require 'rubygems/dependency_installer' -require 'bundler/parallel_workers' +require 'bundler/worker' module Bundler class Installer < Environment @@ -84,7 +84,7 @@ module Bundler # that said, it's a rare situation (other than rake), and parallel # installation is just SO MUCH FASTER. so we let people opt in. jobs = [Bundler.settings[:jobs].to_i-1, 1].max - if jobs > 1 && can_install_parallely? + if jobs > 1 && can_install_in_parallel? install_in_parallel jobs, options[:standalone] else install_sequentially options[:standalone] @@ -96,20 +96,26 @@ module Bundler def install_gem_from_spec(spec, standalone = false, worker = 0) # Fetch the build settings, if there are any - settings = Bundler.settings["build.#{spec.name}"] - install_message = nil - post_install_message = nil - debug_message = nil - Bundler.rubygems.with_build_args [settings] do - install_message, post_install_message, debug_message = spec.source.install(spec) - if install_message.include? 'Installing' - Bundler.ui.confirm install_message - else - Bundler.ui.info install_message + settings = Bundler.settings["build.#{spec.name}"] + messages = nil + + if settings + Bundler.rubygems.with_build_args [settings] do + messages = spec.source.install(spec) end - Bundler.ui.debug debug_message if debug_message - Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}" + else + messages = spec.source.install(spec) + end + + install_message, post_install_message, debug_message = *messages + + if install_message.include? 'Installing' + Bundler.ui.confirm install_message + else + Bundler.ui.info install_message end + Bundler.ui.debug debug_message if debug_message + Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}" if Bundler.settings[:bin] && standalone generate_standalone_bundler_executable_stubs(spec) @@ -194,14 +200,13 @@ module Bundler private - def can_install_parallely? - min_rubygems = "2.0.7" - if Bundler.current_ruby.mri? || Bundler.rubygems.provides?(">= #{min_rubygems}") + def can_install_in_parallel? + if Bundler.rubygems.provides?(">= 2.1.0") true else Bundler.ui.warn "Rubygems #{Gem::VERSION} is not threadsafe, so your "\ - "gems must be installed one at a time. Upgrade to Rubygems " \ - "#{min_rubygems} or higher to enable parallel gem installation." + "gems must be installed one at a time. Upgrade to Rubygems 2.1.0 " \ + "or higher to enable parallel gem installation." false end end @@ -254,7 +259,7 @@ module Bundler file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]" file.puts "path = File.expand_path('..', __FILE__)" paths.each do |path| - file.puts %{$:.unshift File.expand_path("\#{path}/#{path}")} + file.puts %{$:.unshift "\#{path}/#{path}"} end end end @@ -277,9 +282,9 @@ module Bundler remains[spec.name] = true end - worker_pool = ParallelWorkers.worker_pool size, lambda { |name, worker| + worker_pool = Worker.new size, lambda { |name, worker_num| spec = name2spec[name] - message = install_gem_from_spec spec, standalone, worker + message = install_gem_from_spec spec, standalone, worker_num { :name => spec.name, :post_install => message } } diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 32eb43a4dc..7030508988 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -47,14 +47,18 @@ module Bundler end @sources << @rubygems_aggregate @specs = @specs.values + rescue ArgumentError => e + Bundler.ui.debug(e) + raise LockfileError, "Your lockfile is unreadable. Run `rm Gemfile.lock` " \ + "and then `bundle install` to generate a new lockfile." end private TYPES = { - "GIT" => Bundler::Source::Git, - "GEM" => Bundler::Source::Rubygems, - "PATH" => Bundler::Source::Path + GIT => Bundler::Source::Git, + GEM => Bundler::Source::Rubygems, + PATH => Bundler::Source::Path, } def parse_source(line) @@ -64,18 +68,18 @@ module Bundler @opts, @type = {}, line when SPECS case @type - when "PATH" + when PATH @current_source = TYPES[@type].from_lock(@opts) @sources << @current_source - when "GIT" + when GIT @current_source = TYPES[@type].from_lock(@opts) # Strip out duplicate GIT sections - if @type == "GIT" && @sources.include?(@current_source) + if @sources.include?(@current_source) @current_source = @sources.find { |s| s == @current_source } else @sources << @current_source end - when "GEM" + when GEM Array(@opts["remote"]).each do |url| @rubygems_aggregate.add_remote(url) end diff --git a/lib/bundler/parallel_workers.rb b/lib/bundler/parallel_workers.rb deleted file mode 100644 index b28f630461..0000000000 --- a/lib/bundler/parallel_workers.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'thread' - -require "bundler/parallel_workers/worker" - -module Bundler - module ParallelWorkers - autoload :UnixWorker, "bundler/parallel_workers/unix_worker" - autoload :ThreadWorker, "bundler/parallel_workers/thread_worker" - - def self.worker_pool(size, job) - if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby? || Bundler.current_ruby.rbx? - ThreadWorker.new(size, job) - else - UnixWorker.new(size, job) - end - end - end -end diff --git a/lib/bundler/parallel_workers/thread_worker.rb b/lib/bundler/parallel_workers/thread_worker.rb deleted file mode 100644 index ef2c9ecd18..0000000000 --- a/lib/bundler/parallel_workers/thread_worker.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Bundler - module ParallelWorkers - class ThreadWorker < Worker - - private - - # On platforms where fork is not available - # use Threads for parallely downloading gems - # - # @param size [Integer] Size of thread worker pool - # @param func [Proc] Job to be run inside thread worker pool - def prepare_workers(size, func) - @threads = size.times.map do |i| - Thread.start do - loop do - obj = @request_queue.deq - break if obj.equal? POISON - begin - @response_queue.enq func.call(obj, i) - rescue Exception => e - @response_queue.enq(WrappedException.new(e)) - end - end - end - end - end - - end - end -end diff --git a/lib/bundler/parallel_workers/unix_worker.rb b/lib/bundler/parallel_workers/unix_worker.rb deleted file mode 100644 index 94a07bf970..0000000000 --- a/lib/bundler/parallel_workers/unix_worker.rb +++ /dev/null @@ -1,101 +0,0 @@ -module Bundler - module ParallelWorkers - # UnixWorker is used only on platforms where fork is available. The way - # this code works is, it forks a preconfigured number of workers and then - # It starts preconfigured number of threads that write to the connected pipe. - class UnixWorker < Worker - - class JobHandler < Struct.new(:pid, :io_r, :io_w) - def work(obj) - Marshal.dump obj, io_w - Marshal.load io_r - rescue IOError, Errno::EPIPE - nil - end - end - - def initialize(size, job) - # Close the persistent connections for the main thread before forking - Net::HTTP::Persistent.new('bundler', :ENV).shutdown - super - end - - private - - # Start forked workers for downloading gems. This version of worker - # is only used on platforms where fork is available. - # - # @param size [Integer] Size of worker pool - # @param func [Proc] Job that should be executed in the worker - def prepare_workers(size, func) - @workers = size.times.map do |num| - child_read, parent_write = IO.pipe - parent_read, child_write = IO.pipe - - pid = Process.fork do - begin - parent_read.close - parent_write.close - - while !child_read.eof? - obj = Marshal.load child_read - Marshal.dump func.call(obj, num), child_write - end - rescue Exception => e - begin - Marshal.dump WrappedException.new(e), child_write - rescue Errno::EPIPE - nil - end - ensure - child_read.close - child_write.close - end - end - - child_read.close - child_write.close - JobHandler.new pid, parent_read, parent_write - end - end - - # Start the threads whose job is basically to wait for incoming messages - # on request queue and write that message to the connected pipe. Also retrieve - # messages from child worker via connected pipe and write the message to response queue - # - # @param size [Integer] Number of threads to be started - def prepare_threads(size) - @threads = size.times.map do |i| - Thread.start do - worker = @workers[i] - loop do - obj = @request_queue.deq - break if obj.equal? POISON - @response_queue.enq worker.work(obj) - end - end - end - end - - # Kill the forked workers by sending SIGINT to them - def stop_workers - @workers.each do |worker| - worker.io_r.close unless worker.io_r.closed? - worker.io_w.close unless worker.io_w.closed? - begin - Process.kill :INT, worker.pid - rescue Errno::ESRCH - nil - end - end - @workers.each do |worker| - begin - Process.waitpid worker.pid - rescue Errno::ECHILD - nil - end - end - end - end - end -end diff --git a/lib/bundler/parallel_workers/worker.rb b/lib/bundler/parallel_workers/worker.rb deleted file mode 100644 index 0e101d1c5f..0000000000 --- a/lib/bundler/parallel_workers/worker.rb +++ /dev/null @@ -1,69 +0,0 @@ -module Bundler - module ParallelWorkers - class Worker - POISON = Object.new - - class WrappedException < StandardError - attr_reader :exception - def initialize(exn) - @exception = exn - end - end - - # Creates a worker pool of specified size - # - # @param size [Integer] Size of pool - # @param func [Proc] job to run in inside the worker pool - def initialize(size, func) - @request_queue = Queue.new - @response_queue = Queue.new - prepare_workers size, func - prepare_threads size - trap("INT") { @threads.each {|i| i.exit }; stop_workers; exit 1 } - end - - # Enqueue a request to be executed in the worker pool - # - # @param obj [String] mostly it is name of spec that should be downloaded - def enq(obj) - @request_queue.enq obj - end - - # Retrieves results of job function being executed in worker pool - def deq - result = @response_queue.deq - if result.is_a?(WrappedException) - raise result.exception - end - result - end - - # Stop the forked workers and started threads - def stop - stop_threads - stop_workers - end - - private - # Stop the worker threads by sending a poison object down the request queue - # so as worker threads after retrieving it, shut themselves down - def stop_threads - @threads.each do - @request_queue.enq POISON - end - @threads.each do |thread| - thread.join - end - end - - # To be overridden by child classes - def prepare_threads(size) - end - - # To be overridden by child classes - def stop_workers - end - - end - end -end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index a8d3d1a7ff..709ad67a34 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -141,6 +141,7 @@ module Bundler @index = index @deps_for = {} @missing_gems = Hash.new(0) + @prereleases_cache = Hash.new { |h,k| h[k] = k.prerelease? } @source_requirements = source_requirements @iteration_counter = 0 @started_at = Time.now @@ -269,7 +270,7 @@ module Bundler reqs = reqs.sort_by do |a| [ activated[a.name] ? 0 : 1, - a.requirement.prerelease? ? 0 : 1, + @prereleases_cache[a.requirement] ? 0 : 1, @errors[a.name] ? 0 : 1, activated[a.name] ? 0 : @gems_size[a] ] end diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 862dd35a61..da6ebae080 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -38,7 +38,7 @@ module Bundler patchlevel == other.patchlevel end - # Returns a tuple of thsee things: + # Returns a tuple of these things: # [diff, this, other] # The priority of attributes are # 1. engine diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 659a04b11d..55f2139a59 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -149,6 +149,7 @@ module Gem class Platform JAVA = Gem::Platform.new('java') unless defined?(JAVA) MSWIN = Gem::Platform.new('mswin32') unless defined?(MSWIN) + MSWIN64 = Gem::Platform.new('mswin64') unless defined?(MSWIN64) MINGW = Gem::Platform.new('x86-mingw32') unless defined?(MINGW) X64_MINGW = Gem::Platform.new('x64-mingw32') unless defined?(X64_MINGW) diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index eb354605e7..0107663057 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -1,3 +1,4 @@ +require 'monitor' require 'rubygems' require 'rubygems/config_file' @@ -134,6 +135,10 @@ module Bundler Gem::DefaultUserInteraction.ui = obj end + def ext_lock + @ext_lock ||= Monitor.new + end + def fetch_specs(all, pre, &blk) specs = Gem::SpecFetcher.new.list(all, pre) specs.each { yield } if block_given? @@ -156,12 +161,14 @@ module Bundler end def with_build_args(args) - old_args = self.build_args - begin - self.build_args = args - yield - ensure - self.build_args = old_args + ext_lock.synchronize do + old_args = self.build_args + begin + self.build_args = args + yield + ensure + self.build_args = old_args + end end end @@ -306,12 +313,12 @@ module Bundler if exec_name spec = specs.find { |s| s.executables.include?(exec_name) } + spec or raise Gem::Exception, "can't find executable #{exec_name}" unless spec.name == name warn "Bundler is using a binstub that was created for a different gem.\n" \ "This is deprecated, in future versions you may need to `bundle binstub #{name}` " \ "to work around a system/bundle conflict." end - spec or raise Gem::Exception, "can't find executable #{exec_name}" else spec = specs.find { |s| s.name == name } exec_name = spec.default_executable or raise Gem::Exception, "no default executable for #{spec.full_name}" @@ -554,9 +561,43 @@ module Bundler end end + class MoreFuture < Future + def initialize + super + backport_ext_builder_monitor + end + + def backport_ext_builder_monitor + require 'rubygems/ext' + + Gem::Ext::Builder.class_eval do + if !const_defined?(:CHDIR_MONITOR) + const_set(:CHDIR_MONITOR, Monitor.new) + end + + if const_defined?(:CHDIR_MUTEX) + remove_const(:CHDIR_MUTEX) + const_set(:CHDIR_MUTEX, const_get(:CHDIR_MONITOR)) + end + end + end + + def ext_lock + Gem::Ext::Builder::CHDIR_MONITOR + end + + def find_name(name) + Gem::Specification.stubs.find_all do |spec| + spec.name == name + end.map(&:to_spec) + end + end + end - if RubygemsIntegration.provides?(">= 1.99.99") + if RubygemsIntegration.provides?(">= 2.1.0") + @rubygems = RubygemsIntegration::MoreFuture.new + elsif RubygemsIntegration.provides?(">= 1.99.99") @rubygems = RubygemsIntegration::Future.new elsif RubygemsIntegration.provides?('>= 1.8.20') @rubygems = RubygemsIntegration::MoreModern.new diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index b09a8814e5..be61c2b15b 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -46,7 +46,7 @@ module Bundler self end - REGEXPS = [ + REQUIRE_ERRORS = [ /^no such file to load -- (.+)$/i, /^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i, /^Missing API definition file in (.+)$/i, @@ -76,18 +76,16 @@ module Bundler Kernel.require file end rescue LoadError => e - REGEXPS.find { |r| r =~ e.message } + REQUIRE_ERRORS.find { |r| r =~ e.message } raise if dep.autorequire || $1 != required_file if dep.autorequire.nil? && dep.name.include?('-') begin namespaced_file = dep.name.gsub('-', '/') Kernel.require namespaced_file - rescue LoadError - REGEXPS.find { |r| r =~ e.message } - regex_name = $1 - raise e if dep.autorequire || (regex_name && regex_name.gsub('-', '/') != namespaced_file) - raise e if regex_name.nil? + rescue LoadError => e + REQUIRE_ERRORS.find { |r| r =~ e.message } + raise if $1 != namespaced_file end end end @@ -105,12 +103,13 @@ module Bundler alias gems specs def cache(custom_path = nil) - cache_path = cache_path(custom_path) + cache_path = Bundler.app_cache(custom_path) FileUtils.mkdir_p(cache_path) unless File.exist?(cache_path) - Bundler.ui.info "Updating files in vendor/cache" + Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}" specs.each do |spec| next if spec.name == 'bundler' + spec.source.send(:fetch_gem, spec) if Bundler.settings[:cache_all_platforms] && spec.source.respond_to?(:fetch_gem, true) spec.source.cache(spec, custom_path) if spec.source.respond_to?(:cache) end @@ -119,15 +118,14 @@ module Bundler FileUtils.touch(File.expand_path("../.bundlecache", git_dir)) end - prune_cache(custom_path) unless Bundler.settings[:no_prune] + prune_cache(cache_path) unless Bundler.settings[:no_prune] end - def prune_cache(custom_path) - cache_path = cache_path(custom_path) + def prune_cache(cache_path) FileUtils.mkdir_p(cache_path) unless File.exist?(cache_path) resolve = @definition.resolve - prune_gem_cache(resolve, custom_path) - prune_git_and_path_cache(resolve, custom_path) + prune_gem_cache(resolve, cache_path) + prune_git_and_path_cache(resolve, cache_path) end def clean(dry_run = false) @@ -218,31 +216,16 @@ module Bundler ENV["BUNDLE_BIN_PATH"] = File.expand_path("../../../bin/bundle", __FILE__) end - # Set PATH - paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR) - paths.unshift "#{Bundler.bundle_path}/bin" - ENV["PATH"] = paths.uniq.join(File::PATH_SEPARATOR) - # Set BUNDLE_GEMFILE ENV["BUNDLE_GEMFILE"] = default_gemfile.to_s - # Set RUBYOPT - rubyopt = [ENV["RUBYOPT"]].compact - if rubyopt.empty? || rubyopt.first !~ /-rbundler\/setup/ - rubyopt.unshift %|-rbundler/setup| - ENV["RUBYOPT"] = rubyopt.join(' ') - end - - # Set RUBYLIB - rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR) - rubylib.unshift File.expand_path('../..', __FILE__) - ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR) + SharedHelpers.set_bundle_environment end private - def prune_gem_cache(resolve, custom_path) - cached = Dir["#{cache_path(custom_path)}/*.gem"] + def prune_gem_cache(resolve, cache_path) + cached = Dir["#{cache_path}/*.gem"] cached = cached.delete_if do |path| spec = Bundler.rubygems.spec_from_gem path @@ -253,7 +236,7 @@ module Bundler end if cached.any? - Bundler.ui.info "Removing outdated .gem files from vendor/cache" + Bundler.ui.info "Removing outdated .gem files from #{Bundler.settings.app_cache_path}" cached.each do |path| Bundler.ui.info " * #{File.basename(path)}" @@ -262,8 +245,8 @@ module Bundler end end - def prune_git_and_path_cache(resolve, custom_path) - cached = Dir["#{cache_path(custom_path)}/*/.bundlecache"] + def prune_git_and_path_cache(resolve, cache_path) + cached = Dir["#{cache_path}/*/.bundlecache"] cached = cached.delete_if do |path| name = File.basename(File.dirname(path)) @@ -275,7 +258,7 @@ module Bundler end if cached.any? - Bundler.ui.info "Removing outdated git and path gems from vendor/cache" + Bundler.ui.info "Removing outdated git and path gems from #{Bundler.settings.app_cache_path}" cached.each do |path| path = File.dirname(path) @@ -302,9 +285,5 @@ module Bundler end end - def cache_path(custom_path = nil) - path = custom_path || root - path.join("vendor/cache") - end end end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index d229ba8134..a29e7104c7 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -1,15 +1,24 @@ +require 'uri' + module Bundler class Settings + BOOL_KEYS = %w(frozen cache_all no_prune disable_local_branch_check gem.mit gem.coc).freeze + def initialize(root = nil) @root = root @local_config = load_config(local_config_file) @global_config = load_config(global_config_file) end - def [](key) - the_key = key_for(key) - value = (@local_config[the_key] || ENV[the_key] || @global_config[the_key]) - is_bool(key) ? to_bool(value) : value + def [](name) + key = key_for(name) + value = (@local_config[key] || ENV[key] || @global_config[key]) + + if !value.nil? && is_bool(name) + to_bool(value) + else + value + end end def []=(key, value) @@ -115,18 +124,29 @@ module Bundler ENV['BUNDLE_IGNORE_CONFIG'] end + def app_cache_path + @app_cache_path ||= begin + path = self[:cache_path] || "vendor/cache" + raise InvalidOption, "Cache path must be relative to the bundle path" if path.start_with?("/") + path + end + end + private def key_for(key) - key = key.to_s.sub(".", "__").upcase + if key.is_a?(String) && /https?:/ =~ key + key = normalize_uri(key).to_s + end + key = key.to_s.gsub(".", "__").upcase "BUNDLE_#{key}" end def is_bool(key) - %w(frozen cache_all no_prune disable_local_branch_check).include? key.to_s + BOOL_KEYS.include?(key.to_s) end def to_bool(value) - !(value.nil? || value == '' || value =~ /^(false|f|no|n|0)$/i) + !(value.nil? || value == '' || value =~ /^(false|f|no|n|0)$/i || value == false) end def set_key(key, value, hash, file) @@ -172,7 +192,9 @@ module Bundler uri = uri.to_s uri = "#{uri}/" unless uri =~ %r[/\Z] uri = URI(uri) - raise ArgumentError, "Gem mirror sources must be absolute URIs (configured: #{mirror_source})" unless uri.absolute? + unless uri.absolute? + raise ArgumentError, "Gem sources must be absolute. You provided '#{uri}'." + end uri end diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb index 3059975d4e..6bf598337e 100644 --- a/lib/bundler/setup.rb +++ b/lib/bundler/setup.rb @@ -2,7 +2,8 @@ require 'bundler/shared_helpers' if Bundler::SharedHelpers.in_bundle? require 'bundler' - if STDOUT.tty? + + if STDOUT.tty? || ENV['BUNDLER_FORCE_TTY'] begin Bundler.setup rescue Bundler::BundlerError => e diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 20db09c3e5..cd4ad71737 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -1,4 +1,3 @@ -require 'monitor' require 'pathname' require 'rubygems' @@ -19,7 +18,6 @@ end module Bundler module SharedHelpers attr_accessor :gem_loaded - CHDIR_MONITOR = Monitor.new def default_gemfile gemfile = find_gemfile @@ -28,25 +26,36 @@ module Bundler end def default_lockfile - Pathname.new("#{default_gemfile}.lock") + gemfile = default_gemfile + + case gemfile.basename.to_s + when 'gems.rb' then Pathname.new(gemfile.sub(/.rb$/, '.locked')) + else Pathname.new("#{gemfile}.lock") + end end - def in_bundle? - find_gemfile + def default_bundle_dir + bundle_dir = find_directory(".bundle") + return nil unless bundle_dir + + global_bundle_dir = File.join(Bundler.rubygems.user_home, ".bundle") + return nil if bundle_dir == global_bundle_dir + + Pathname.new(bundle_dir) end - def chdir_monitor - CHDIR_MONITOR + def in_bundle? + find_gemfile end def chdir(dir, &blk) - chdir_monitor.synchronize do + Bundler.rubygems.ext_lock.synchronize do Dir.chdir dir, &blk end end def pwd - chdir_monitor.synchronize do + Bundler.rubygems.ext_lock.synchronize do Dir.pwd end end @@ -64,12 +73,47 @@ module Bundler keys.each {|key| ENV[key] = old_env[key] } end + def set_bundle_environment + # Set PATH + paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR) + paths.unshift "#{Bundler.bundle_path}/bin" + ENV["PATH"] = paths.uniq.join(File::PATH_SEPARATOR) + + # Set RUBYOPT + rubyopt = [ENV["RUBYOPT"]].compact + if rubyopt.empty? || rubyopt.first !~ /-rbundler\/setup/ + rubyopt.unshift %|-rbundler/setup| + ENV["RUBYOPT"] = rubyopt.join(' ') + end + + # Set RUBYLIB + rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR) + rubylib.unshift File.expand_path('../..', __FILE__) + ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR) + end + private def find_gemfile given = ENV['BUNDLE_GEMFILE'] return given if given && !given.empty? + find_file('Gemfile', 'gems.rb') + end + + def find_file(*names) + search_up(*names) {|filename| + return filename if File.file?(filename) + } + end + + def find_directory(*names) + search_up(*names) do |dirname| + return dirname if File.directory?(dirname) + end + end + + def search_up(*names) previous = nil current = File.expand_path(SharedHelpers.pwd) @@ -79,9 +123,10 @@ module Bundler return nil if File.file?(File.join(current, 'bundler.gemspec')) end - # otherwise return the Gemfile if it's there - filename = File.join(current, 'Gemfile') - return filename if File.file?(filename) + names.each do |name| + filename = File.join(current, name) + yield filename + end current, previous = File.expand_path("..", current), current end end diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 1f8cf2cbe7..b544451dc0 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -21,12 +21,16 @@ module Bundler end def version_message(spec) - locked_spec = Bundler.locked_gems.specs.find { |s| s.name == spec.name } if Bundler.locked_gems - locked_spec_version = locked_spec.version if locked_spec message = "#{spec.name} #{spec.version}" - if locked_spec_version && spec.version != locked_spec_version - message << " (was #{locked_spec_version})" + + if Bundler.locked_gems + locked_spec = Bundler.locked_gems.specs.find { |s| s.name == spec.name } + locked_spec_version = locked_spec.version if locked_spec + if locked_spec_version && spec.version != locked_spec_version + message << " (was #{locked_spec_version})" + end end + message end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 756b96dcc2..62d615088c 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -20,7 +20,7 @@ module Bundler # Stringify options that could be set as symbols %w(ref branch tag revision).each{|k| options[k] = options[k].to_s if options[k] } - @uri = options["uri"] + @uri = options["uri"] || '' @branch = options["branch"] @ref = options["ref"] || options["branch"] || options["tag"] || 'master' @submodules = options["submodules"] @@ -46,6 +46,10 @@ module Bundler out << " specs:\n" end + def hash + [self.class, uri, ref, branch, name, version, submodules].hash + end + def eql?(o) o.is_a?(Git) && uri == o.uri && diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 29100a1e74..89c784e0fa 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -60,6 +60,10 @@ module Bundler end end + def version + git("--version").sub("git version", "").strip + end + def checkout if path.exist? return if has_revision_cached? diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 0d0d3ae683..59131a52b4 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -54,19 +54,19 @@ module Bundler end def hash - self.class.hash + [self.class, expanded_path, version].hash end def eql?(o) o.instance_of?(Path) && - expand(path) == expand(o.path) && + expanded_path == expand(o.path) && version == o.version end alias == eql? def name - File.basename(expand(path).to_s) + File.basename(expanded_path.to_s) end def install(spec) @@ -95,6 +95,7 @@ module Bundler def specs if has_app_cache? @path = app_cache_path + @expanded_path = nil # Invalidate end local_specs end @@ -105,6 +106,10 @@ module Bundler private + def expanded_path + @expanded_path ||= expand(path) + end + def expand(somepath) somepath.expand_path(Bundler.root) rescue ArgumentError => e @@ -123,7 +128,6 @@ module Bundler def load_spec_files index = Index.new - expanded_path = expand(path) if File.directory?(expanded_path) Dir["#{expanded_path}/#{@glob}"].each do |file| @@ -168,8 +172,7 @@ module Bundler end def generate_bin(spec, disable_extensions = false) - gem_dir = Pathname.new(spec.full_gem_path) - gem_file = nil + gem_dir = Pathname.new(spec.full_gem_path) # Some gem authors put absolute paths in their gemspec # and we have to save them from themselves @@ -183,8 +186,6 @@ module Bundler end.compact SharedHelpers.chdir(gem_dir) do - gem_file = Bundler.rubygems.build_gem gem_dir, spec - installer = Path::Installer.new(spec, :env_shebang => false) run_hooks(:pre_install, installer) installer.build_extensions unless disable_extensions @@ -204,10 +205,6 @@ module Bundler end Bundler.ui.warn "The validation message from Rubygems was:\n #{e.message}" - ensure - if gem_dir && gem_file - FileUtils.rm_rf(gem_dir.join gem_file) - end end def run_hooks(type, installer) diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 99cea1fa05..0053688f38 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -5,8 +5,10 @@ require 'rubygems/spec_fetcher' module Bundler class Source class Rubygems < Source - # threshold for switching back to the modern index instead of fetching every spec - API_REQUEST_LIMIT = 100 + # Use the API when installing less than X gems + API_REQUEST_LIMIT = 500 + # Ask for X gems per API request + API_REQUEST_SIZE = 50 attr_reader :remotes, :caches @@ -97,49 +99,50 @@ module Bundler spec.__swap__(s) end - path = cached_gem(spec) - if Bundler.requires_sudo? - install_path = Bundler.tmp(spec.full_name) - bin_path = install_path.join("bin") - else - install_path = Bundler.rubygems.gem_dir - bin_path = Bundler.system_bindir - end + unless Bundler.settings[:no_install] + path = cached_gem(spec) + if Bundler.requires_sudo? + install_path = Bundler.tmp(spec.full_name) + bin_path = install_path.join("bin") + else + install_path = Bundler.rubygems.gem_dir + bin_path = Bundler.system_bindir + end - installed_spec = nil - Bundler.rubygems.preserve_paths do - installed_spec = Bundler::GemInstaller.new(path, - :install_dir => install_path.to_s, - :bin_dir => bin_path.to_s, - :ignore_dependencies => true, - :wrappers => true, - :env_shebang => true - ).install - end + installed_spec = nil + Bundler.rubygems.preserve_paths do + installed_spec = Bundler::GemInstaller.new(path, + :install_dir => install_path.to_s, + :bin_dir => bin_path.to_s, + :ignore_dependencies => true, + :wrappers => true, + :env_shebang => true + ).install + end - # SUDO HAX - if Bundler.requires_sudo? - Bundler.rubygems.repository_subdirectories.each do |name| - src = File.join(install_path, name, "*") - dst = File.join(Bundler.rubygems.gem_dir, name) - if name == "extensions" && Dir.glob(src).any? - src = File.join(src, "*/*") - ext_src = Dir.glob(src).first - ext_src.gsub!(src[0..-6], '') - dst = File.dirname(File.join(dst, ext_src)) + # SUDO HAX + if Bundler.requires_sudo? + Bundler.rubygems.repository_subdirectories.each do |name| + src = File.join(install_path, name, "*") + dst = File.join(Bundler.rubygems.gem_dir, name) + if name == "extensions" && Dir.glob(src).any? + src = File.join(src, "*/*") + ext_src = Dir.glob(src).first + ext_src.gsub!(src[0..-6], '') + dst = File.dirname(File.join(dst, ext_src)) + end + Bundler.mkdir_p dst + Bundler.sudo "cp -R #{src} #{dst}" if Dir[src].any? end - Bundler.mkdir_p dst - Bundler.sudo "cp -R #{src} #{dst}" if Dir[src].any? - end - spec.executables.each do |exe| - Bundler.mkdir_p Bundler.system_bindir - Bundler.sudo "cp -R #{install_path}/bin/#{exe} #{Bundler.system_bindir}/" + spec.executables.each do |exe| + Bundler.mkdir_p Bundler.system_bindir + Bundler.sudo "cp -R #{install_path}/bin/#{exe} #{Bundler.system_bindir}/" + end end + installed_spec.loaded_from = loaded_from(spec) end - - spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec" - installed_spec.loaded_from = spec.loaded_from + spec.loaded_from = loaded_from(spec) ["Installing #{version_message(spec)}", spec.post_install_message] ensure if install_path && Bundler.requires_sudo? @@ -157,6 +160,9 @@ module Bundler return if File.dirname(cached_path) == Bundler.app_cache.to_s Bundler.ui.info " * #{File.basename(cached_path)}" FileUtils.cp(cached_path, Bundler.app_cache(custom_path)) + rescue Errno::EACCES => e + Bundler.ui.debug(e) + raise InstallError, e.message end def cached_built_in_gem(spec) @@ -190,6 +196,12 @@ module Bundler end end + def fetchers + @fetchers ||= remotes.map do |uri| + Bundler::Fetcher.new(uri) + end + end + protected def credless_remotes @@ -199,12 +211,16 @@ module Bundler private def source_uris_for_spec(spec) - specs.search_all(spec.name).inject([]) do |uris, spec| - uris << spec.source_uri.without_credentials if spec.source_uri + specs.search_all(spec.name).inject([]) do |uris, s| + uris << s.source_uri.without_credentials if s.source_uri uris end end + def loaded_from(spec) + "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec" + end + def cached_gem(spec) cached_gem = cached_path(spec) unless cached_gem @@ -281,12 +297,6 @@ module Bundler idx end - def fetchers - @fetchers ||= remotes.map do |url| - Bundler::Fetcher.new(url) - end - end - def api_fetchers fetchers.select{|f| f.use_api } end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 49a976ba6a..49efbf7a8e 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -74,6 +74,10 @@ module Bundler all_sources.each(&:remote!) end + def rubygems_primary_remotes + @rubygems_aggregate.remotes + end + private def add_source_to_list(source, list) diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt new file mode 100644 index 0000000000..c5393d1403 --- /dev/null +++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt @@ -0,0 +1,13 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) diff --git a/lib/bundler/templates/newgem/LICENSE.txt.tt b/lib/bundler/templates/newgem/LICENSE.txt.tt index a9f52e6bbb..8fef84cd53 100644 --- a/lib/bundler/templates/newgem/LICENSE.txt.tt +++ b/lib/bundler/templates/newgem/LICENSE.txt.tt @@ -1,22 +1,21 @@ -Copyright (c) <%=Time.now.year%> <%=config[:author]%> +The MIT License (MIT) -MIT License +Copyright (c) <%=Time.now.year%> <%=config[:author]%> -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index 8a65988cae..45d901738f 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -1,6 +1,8 @@ # <%=config[:constant_name]%> -TODO: Write a gem description +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/<%=config[:namespaced_path]%>`. To experiment with that code, run `bin/console` for an interactive prompt. + +TODO: Delete this and the text above, and describe your gem ## Installation @@ -22,6 +24,12 @@ Or install it yourself as: TODO: Write usage instructions here +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.<% if config[:bin] %> Run `bundle exec <%= config[:name] %>` to use the code located in this directory, ignoring other installed copies of this gem.<% end %> + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + ## Contributing 1. Fork it ( https://github.com/[my-github-username]/<%=config[:name]%>/fork ) diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index f4fc3c6ee9..d48487418c 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -18,6 +18,8 @@ task :default => :spec <% if config[:ext] -%> require "rake/extensiontask" +task :build => :compile + Rake::ExtensionTask.new("<%=config[:underscored_name]%>") do |ext| ext.lib_dir = "lib/<%=config[:namespaced_path]%>" end diff --git a/lib/bundler/templates/newgem/bin/console.tt b/lib/bundler/templates/newgem/bin/console.tt new file mode 100644 index 0000000000..f402bd639e --- /dev/null +++ b/lib/bundler/templates/newgem/bin/console.tt @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "<%= config[:namespaced_path] %>" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start diff --git a/lib/bundler/templates/newgem/bin/newgem.tt b/lib/bundler/templates/newgem/bin/newgem.tt deleted file mode 100644 index a005298815..0000000000 --- a/lib/bundler/templates/newgem/bin/newgem.tt +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby - -require '<%= config[:namespaced_path] %>' diff --git a/lib/bundler/templates/newgem/bin/setup.tt b/lib/bundler/templates/newgem/bin/setup.tt new file mode 100644 index 0000000000..b65ed50ff1 --- /dev/null +++ b/lib/bundler/templates/newgem/bin/setup.tt @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail +IFS=$'\n\t' + +bundle install + +# Do any other automated setup that you need to do here diff --git a/lib/bundler/templates/newgem/exe/newgem.tt b/lib/bundler/templates/newgem/exe/newgem.tt new file mode 100644 index 0000000000..a8339bb79f --- /dev/null +++ b/lib/bundler/templates/newgem/exe/newgem.tt @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby + +require "<%= config[:namespaced_path] %>" diff --git a/lib/bundler/templates/newgem/gitignore.tt b/lib/bundler/templates/newgem/gitignore.tt index ae3fdc2989..ebff7ac53c 100644 --- a/lib/bundler/templates/newgem/gitignore.tt +++ b/lib/bundler/templates/newgem/gitignore.tt @@ -7,8 +7,10 @@ /pkg/ /spec/reports/ /tmp/ +<%- if config[:ext] -%> *.bundle *.so *.o *.a mkmf.log +<%- end -%> diff --git a/lib/bundler/templates/newgem/lib/newgem/version.rb.tt b/lib/bundler/templates/newgem/lib/newgem/version.rb.tt index fe9b5fc558..5874085d61 100644 --- a/lib/bundler/templates/newgem/lib/newgem/version.rb.tt +++ b/lib/bundler/templates/newgem/lib/newgem/version.rb.tt @@ -1,7 +1,7 @@ <%- config[:constant_array].each_with_index do |c,i| -%> <%= ' '*i %>module <%= c %> <%- end -%> -<%= ' '*config[:constant_array].size %>VERSION = "0.0.1" +<%= ' '*config[:constant_array].size %>VERSION = "0.1.0" <%- (config[:constant_array].size-1).downto(0) do |i| -%> <%= ' '*i %>end <%- end -%> diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index 74d253ec37..f20698bf94 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -8,25 +8,32 @@ Gem::Specification.new do |spec| spec.version = <%=config[:constant_name]%>::VERSION spec.authors = [<%=config[:author].inspect%>] spec.email = [<%=config[:email].inspect%>] -<% if config[:ext] -%> - spec.extensions = ["ext/<%=config[:underscored_name]%>/extconf.rb"] -<% end -%> - spec.summary = %q{TODO: Write a short summary. Required.} - spec.description = %q{TODO: Write a longer description. Optional.} - spec.homepage = "" + + spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.} + spec.description = %q{TODO: Write a longer description or delete this line.} + spec.homepage = "TODO: Put your gem's website or public repo URL here." +<%- if config[:mit] -%> spec.license = "MIT" +<%- end -%> - spec.files = `git ls-files -z`.split("\x0") - spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] +<%- if config[:ext] -%> + spec.extensions = ["ext/<%=config[:underscored_name]%>/extconf.rb"] +<%- end -%> + + if spec.respond_to?(:metadata) + spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com' to prevent pushes to rubygems.org, or delete to allow pushes to any server." + end spec.add_development_dependency "bundler", "~> <%= Bundler::VERSION.split(".")[0..1].join(".") %>" spec.add_development_dependency "rake", "~> 10.0" -<% if config[:ext] -%> +<%- if config[:ext] -%> spec.add_development_dependency "rake-compiler" -<% end -%> -<% if config[:test] -%> +<%- end -%> +<%- if config[:test] && config[:test] != "false" -%> spec.add_development_dependency "<%=config[:test]%>" -<% end -%> +<%- end -%> end diff --git a/lib/bundler/templates/newgem/test/test_newgem.rb.tt b/lib/bundler/templates/newgem/test/test_newgem.rb.tt index 34cc473672..d50f7da243 100644 --- a/lib/bundler/templates/newgem/test/test_newgem.rb.tt +++ b/lib/bundler/templates/newgem/test/test_newgem.rb.tt @@ -1,6 +1,6 @@ require 'minitest_helper' -class Test<%= config[:constant_name] %> < MiniTest::Unit::TestCase +class Test<%= config[:constant_name] %> < Minitest::Test def test_that_it_has_a_version_number refute_nil ::<%= config[:constant_name] %>::VERSION end diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb index c7054e4e0a..6299c5e9d2 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -57,7 +57,7 @@ module Bundler def trace(e, newline = nil) return unless debug? - msg = ["#{e.class}: #{e.message}", *e.backtrace].join("\n") + msg = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n ")}" tell_me(msg, nil, newline) end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index ca77771043..4b2ee8ef86 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -2,5 +2,5 @@ module Bundler # We're doing this because we might write tests that deal # with other versions of bundler and we are unsure how to # handle this better. - VERSION = "1.7.15" unless defined?(::Bundler::VERSION) + VERSION = "1.8.8" unless defined?(::Bundler::VERSION) end diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb new file mode 100644 index 0000000000..4961588377 --- /dev/null +++ b/lib/bundler/worker.rb @@ -0,0 +1,73 @@ +require 'thread' + +module Bundler + class Worker + POISON = Object.new + + class WrappedException < StandardError + attr_reader :exception + def initialize(exn) + @exception = exn + end + end + + # Creates a worker pool of specified size + # + # @param size [Integer] Size of pool + # @param func [Proc] job to run in inside the worker pool + def initialize(size, func) + @request_queue = Queue.new + @response_queue = Queue.new + @func = func + @threads = size.times.map { |i| Thread.start { process_queue(i) } } + trap("INT") { abort_threads } + end + + # Enqueue a request to be executed in the worker pool + # + # @param obj [String] mostly it is name of spec that should be downloaded + def enq(obj) + @request_queue.enq obj + end + + # Retrieves results of job function being executed in worker pool + def deq + result = @response_queue.deq + raise result.exception if result.is_a?(WrappedException) + result + end + + def stop + stop_threads + end + + private + + def process_queue(i) + loop do + obj = @request_queue.deq + break if obj.equal? POISON + @response_queue.enq apply_func(obj, i) + end + end + + def apply_func(obj, i) + @func.call(obj, i) + rescue Exception => e + WrappedException.new(e) + end + + # Stop the worker threads by sending a poison object down the request queue + # so as worker threads after retrieving it, shut themselves down + def stop_threads + @threads.each { @request_queue.enq POISON } + @threads.each { |thread| thread.join } + end + + def abort_threads + @threads.each {|i| i.exit } + exit 1 + end + + end +end diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn index d158f5b02d..8bbc5140e1 100644 --- a/man/bundle-config.ronn +++ b/man/bundle-config.ronn @@ -70,6 +70,10 @@ The canonical form of this configuration is `"without"`. To convert the canonica form to the environment variable form, capitalize it, and prepend `BUNDLE_`. The environment variable form of `"without"` is `BUNDLE_WITHOUT`. +Any periods in the configuration keys must be replaced with two underscores when +setting it via environment variables. The configuration key `local.rack` becomes +the environment variable `BUNDLE_LOCAL__RACK`. + ## LIST OF AVAILABLE KEYS The following is a list of all configuration keys and their purpose. You can @@ -77,7 +81,7 @@ learn more about their operation in [bundle install(1)][bundle-install]. * `path` (`BUNDLE_PATH`): The location on disk to install gems. Defaults to `$GEM_HOME` in development - and `vendor/bundler` when `--deployment` is used + and `vendor/bundle` when `--deployment` is used * `frozen` (`BUNDLE_FROZEN`): Disallow changes to the `Gemfile`. Defaults to `true` when `--deployment` is used. @@ -86,6 +90,12 @@ learn more about their operation in [bundle install(1)][bundle-install]. * `bin` (`BUNDLE_BIN`): Install executables from gems in the bundle to the specified directory. Defaults to `false`. +* `gemfile` (`BUNDLE_GEMFILE`): + The name of the file that bundler should use as the `Gemfile`. This location + of this file also sets the root of the project, which is used to resolve + relative paths in the `Gemfile`, among other things. By default, bundler + will search up from the current working directory until it finds a + `Gemfile`. * `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. @@ -100,18 +110,6 @@ You can set them globally either via environment variables or `bundle config`, whichever is preferable for your setup. If you use both, environment variables will take preference over global settings. -An additional setting is available only as an environment variable: - -* `BUNDLE_GEMFILE`: - The name of the file that bundler should use as the `Gemfile`. This location - of this file also sets the root of the project, which is used to resolve - relative paths in the `Gemfile`, among other things. By default, bundler - will search up from the current working directory until it finds a - `Gemfile`. - -Bundler will ignore any `BUNDLE_GEMFILE` entries in local or global -configuration files. - ## LOCAL GIT REPOS Bundler also allows you to work against a git repository locally @@ -162,9 +160,13 @@ For example, to use a mirror of rubygems.org hosted at Bundler allows you to configure credentials for any gem source, which allows you to avoid putting secrets into your Gemfile. - bundle config SOURCE_URL USERNAME:PASSWORD + bundle config SOURCE_HOSTNAME USERNAME:PASSWORD For example, to save the credentials of user `claudette` for the gem source at `gems.longerous.com`, you would run: - bundle config https://gems.longerous.com/ claudette:s00pers3krit + bundle config gems.longerous.com claudette:s00pers3krit + +Or you can set the credentials as an environment variable like this: + + export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" diff --git a/man/bundle-install.ronn b/man/bundle-install.ronn index a4448333f8..e9706b51bd 100644 --- a/man/bundle-install.ronn +++ b/man/bundle-install.ronn @@ -3,128 +3,137 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile ## SYNOPSIS -`bundle install` [--gemfile=GEMFILE] - [--path PATH] [--system] - [--without=GROUP1[ GROUP2...]] - [--local] [--deployment] - [--binstubs[=DIRECTORY]] - [--standalone[=GROUP1[ GROUP2...]]] - [--trust-policy=POLICY] - [--jobs=SIZE] - [--retry=TRIES] - [--no-cache] - [--quiet] +`bundle install` [--binstubs[=DIRECTORY]] [--clean] [--full-index] + [--gemfile=GEMFILE] + [--jobs=NUMBER] + [--local] [--deployment] + [--no-cache] [--no-prune] + [--path PATH] [--system] + [--quiet] + [--retry=NUMBER] [--shebang] + [--standalone[=GROUP[ GROUP...]]] + [--trust-policy=POLICY] + [--without=GROUP[ GROUP...]] ## DESCRIPTION Install the gems specified in your Gemfile(5). If this is the first time you run bundle install (and a `Gemfile.lock` does not exist), -bundler will fetch all remote sources, resolve dependencies and +Bundler will fetch all remote sources, resolve dependencies and install all needed gems. If a `Gemfile.lock` does exist, and you have not updated your Gemfile(5), -bundler will fetch all remote sources, but use the dependencies +Bundler will fetch all remote sources, but use the dependencies specified in the `Gemfile.lock` instead of resolving dependencies. If a `Gemfile.lock` does exist, and you have updated your Gemfile(5), -bundler will use the dependencies in the `Gemfile.lock` for all gems +Bundler will use the dependencies in the `Gemfile.lock` for all gems that you did not update, but will re-resolve the dependencies of gems that you did update. You can find more information about this update process below under [CONSERVATIVE UPDATING][]. ## OPTIONS -* `--gemfile=<gemfile>`: - The location of the Gemfile(5) that bundler should use. This defaults - to a gemfile in the current working directory. In general, bundler - will assume that the location of the Gemfile(5) is also the project - root, and will look for the `Gemfile.lock` and `vendor/cache` relative - to it. +* `--binstubs[=<directory>]`: + Creates a directory (defaults to `~/bin`) and place any executables from the + gem there. These executables run in Bundler's context. If used, you might add + this directory to your environment's `PATH` variable. For instance, if the + `rails` gem comes with a `rails` executable, this flag will create a + `bin/rails` executable that ensures that all referred dependencies will be + resolved using the bundled gems. -* `--path=<path>`: - The location to install the gems in the bundle to. This defaults to - Rubygems' gem home, which is also the default location where `gem - install` installs gems. This means that, by default, gems installed - without a `--path` setting will show up in `gem list`. This setting is - a [remembered option][REMEMBERED OPTIONS]. +* `--clean`: + On finishing the installation Bundler is going to remove any gems not present + in the current Gemfile(5). Don't worry, gems currently in use will not be + removed. -* `--system`: - Installs the gems in the bundle to the system location. This - overrides any previous [remembered][REMEMBERED OPTIONS] use of - `--path`. +* `--full-index`: + Bundler will not call Rubygems' API endpoint (default) but download and cache + a (currently big) index file of all gems. Performance can be improved for + large bundles that seldomly change by enabling this option. -* `--without=<list>`: - A space-separated list of groups to skip installing. This is a - [remembered option][REMEMBERED OPTIONS]. +* `--gemfile=<gemfile>`: + The location of the Gemfile(5) which Bundler should use. This defaults + to a Gemfile(5) in the current working directory. In general, Bundler + will assume that the location of the Gemfile(5) is also the project's + root and will try to find `Gemfile.lock` and `vendor/cache` relative + to this location. + +* `--jobs=[<number>]`: + Install gems by starting <number> of workers parallely. * `--local`: - Do not attempt to connect to `rubygems.org`, instead using just - the gems already present in Rubygems' cache or in `vendor/cache`. - Note that if a more appropriate platform-specific gem exists on - `rubygems.org`, it will not be found. + Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the + gems already present in Rubygems' cache or in `vendor/cache`. Note that if a + appropriate platform-specific gem exists on `rubygems.org` it will not be + found. * `--deployment`: - Switches bundler's defaults into [deployment mode][DEPLOYMENT MODE]. - Do not use this flag on development machines. + In [deployment mode][DEPLOYMENT MODE], Bundler will 'roll-out' the bundle for + `production` use. Please check carefully if you want to have this option + enabled in `development` or `test` environments. -* `--binstubs[=<directory>]`: - Create a directory (defaults to `bin`) containing an executable - that runs in the context of the bundle. For instance, if the - `rails` gem comes with a `rails` executable, this flag will create - a `bin/rails` executable that ensures that all dependencies used - come from the bundled gems. +* `--system`: + Installs the gems specified in the bundle to the system's Rubygems location. + This overrides any previous [remembered][REMEMBERED OPTIONS] use of `--path`. -* `--shebang ruby-install-name`: - Uses the ruby executable (usually `ruby`) provided to execute the scripts created - with --binstubs. For instance, if you use --binstubs with `--shebang jruby`, - all executables will be created to use jruby instead. +* `--no-cache`: + Do not update the cache in `vendor/cache` with the newly bundled gems. This + does not remove any gems in the cache but keeps the newly bundled gems from + being cached during the install. -* `--standalone[=<list>]`: - Make a bundle that can work without Ruby Gems or Bundler at runtime. - It takes a space separated list of groups to install. It creates a - `bundle` directory and installs the bundle there. It also generates - a `bundle/bundler/setup.rb` file to replace Bundler's own setup. +* `--no-prune`: + Don't remove stale gems from the cache when the installation finishes. -* `--trust-policy=[<policy>]`: - Apply the Rubygems security policy named <policy>, where policy is one of - HighSecurity, MediumSecurity, LowSecurity, AlmostNoSecurity, or NoSecurity. - For more detail, see the Rubygems signing documentation, linked below in - [SEE ALSO][]. +* `--path=<path>`: + The location to install the specified gems to. This defaults to Rubygems' + setting. Bundler shares this location with Rubygems, `gem install ...` will + have gem installed there, too. Therefore, gems installed without a + `--path ...` setting will show up by calling `gem list`. Accodingly, gems + installed to other locations will not get listed. This setting is a + [remembered option][REMEMBERED OPTIONS]. -* `--jobs=[<size>]`: - Install gems parallely by starting <size> number of parallel workers. +* `--quiet`: + Do not print progress information to the standard output. Instead, Bundler + will exit using a status code (`$?`). -* `--retry[<tries]`: - Retries failed network or git requests <tries> times. +* `--retry=[<number>]`: + Retry failed network or git requests for <number> times. -* `--no-cache`: - Do not update the cache in `vendor/cache` with the newly bundled gems. This - does not remove any existing cached gems, only stops the newly bundled gems - from being cached during the install. +* `--shebang=<ruby-executable>`: + Uses the specified ruby executable (usually `ruby`) to execute the scripts + created with `--binstubs`. In addition, if you use `--binstubs` together with + `--shebang jruby` these executables will be changed to execute `jruby` + instead. -* `--quiet`: - Do not print progress information to stdout. Instead, communicate the - success of the install operation via exit status code. +* `--standalone[=<list>]`: + Makes a bundle that can work without depending on Rubygems or Bundler at + runtime. A space separated list of groups to install has to be specified. + Bundler creates a directory named `bundle` and installs the bundle there. It + also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup + in the manner required. -* `--clean`: - Run bundle clean automatically after install. +* `--trust-policy=[<policy>]`: + Apply the Rubygems security policy <policy>, where policy is one of + `HighSecurity`, `MediumSecurity`, `LowSecurity`, `AlmostNoSecurity`, or + `NoSecurity`. For more details, please see the Rubygems signing documentation + linked below in [SEE ALSO][]. -* `--full-index`: - Use the rubygems modern index instead of the API endpoint. +* `--without=<list>`: + A space-separated list of groups referencing gems to skip during installation. + This is a [remembered option][REMEMBERED OPTIONS]. -* `--no-prune`: - Don't remove stale gems from the cache. ## DEPLOYMENT MODE Bundler's defaults are optimized for development. To switch to defaults optimized for deployment, use the `--deployment` flag. Do not activate deployment mode on development machines, as it -will cause in an error when the Gemfile is modified. +will cause an error when the Gemfile(5) is modified. 1. A `Gemfile.lock` is required. @@ -161,10 +170,10 @@ will cause in an error when the Gemfile is modified. ## SUDO USAGE -By default, bundler installs gems to the same location as `gem install`. +By default, Bundler installs gems to the same location as `gem install`. In some cases, that location may not be writable by your Unix user. In -that case, bundler will stage everything in a temporary directory, +that case, Bundler will stage everything in a temporary directory, then ask you for your `sudo` password in order to copy the gems into their system location. @@ -185,7 +194,7 @@ the current user. Therefore, git gems are downloaded and installed into `~/.bundle` rather than $GEM_HOME or $BUNDLE_PATH. As a result, you should run `bundle install` as the current user, -and bundler will ask for your password if it is needed to put the +and Bundler will ask for your password if it is needed to put the gems into their final location. ## INSTALLING GROUPS @@ -193,7 +202,7 @@ gems into their final location. By default, `bundle install` will install all gems in all groups in your Gemfile(5), except those declared for a different platform. -However, you can explicitly tell bundler to skip installing +However, you can explicitly tell Bundler to skip installing certain groups with the `--without` option. This option takes a space-separated list of groups. @@ -214,21 +223,21 @@ third-party code being used in different environments.` For a simple illustration, consider the following Gemfile(5): - source "https://rubygems.org" + source 'https://rubygems.org' - gem "sinatra" + gem 'sinatra' group :production do - gem "rack-perftools-profiler" + gem 'rack-perftools-profiler' end -In this case, `sinatra` depends on any version of Rack (`>= 1.0`, while +In this case, `sinatra` depends on any version of Rack (`>= 1.0`), while `rack-perftools-profiler` depends on 1.x (`~> 1.0`). When you run `bundle install --without production` in development, we look at the dependencies of `rack-perftools-profiler` as well. That way, you do not spend all your time developing against Rack 2.0, using new -APIs unavailable in Rack 1.x, only to have bundler switch to Rack 1.2 +APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2 when the `production` group _is_ used. This should not cause any problems in practice, because we do not @@ -310,10 +319,10 @@ same versions of all dependencies as it used before the update. Let's take a look at an example. Here's your original Gemfile(5): - source "https://rubygems.org" + source 'https://rubygems.org' - gem "actionpack", "2.3.8" - gem "activemerchant" + gem 'actionpack', '2.3.8' + gem 'activemerchant' In this case, both `actionpack` and `activemerchant` depend on `activesupport`. The `actionpack` gem depends on `activesupport 2.3.8` @@ -326,10 +335,10 @@ gems in your Gemfile(5). Next, you modify your Gemfile(5) to: - source "https://rubygems.org" + source 'https://rubygems.org' - gem "actionpack", "3.0.0.rc" - gem "activemerchant" + gem 'actionpack', '3.0.0.rc' + gem 'activemerchant' The `actionpack 3.0.0.rc` gem has a number of new dependencies, and updates the `activesupport` dependency to `= 3.0.0.rc` and @@ -351,7 +360,7 @@ you would not expect it to suddenly stop working after updating dependency of actionpack requires updating one of its dependencies. Even though `activemerchant` declares a very loose dependency -that theoretically matches `activesupport 3.0.0.rc`, bundler treats +that theoretically matches `activesupport 3.0.0.rc`, Bundler treats gems in your Gemfile(5) that have not changed as an atomic unit together with their dependencies. In this case, the `activemerchant` dependency is treated as `activemerchant 1.7.1 + activesupport 2.3.8`, diff --git a/man/bundle-package.ronn b/man/bundle-package.ronn index 610f820487..8a6b439cd5 100644 --- a/man/bundle-package.ronn +++ b/man/bundle-package.ronn @@ -17,6 +17,13 @@ Since Bundler 1.2, the `bundle package` command can also package `:git` and `:path` dependencies besides .gem files. This needs to be explicitly enabled via the `--all` option. Once used, the `--all` option will be remembered. +## SUPPORT FOR MULTIPLE PLATFORMS + +When using gems that have different packages for different platforms, Bundler +1.8 and newer support caching of gems for other platforms in `vendor/cache`. +This needs to be enabled via the `--all-platforms` option. This setting will be +remembered in your local bundler configuration. + ## REMOTE FETCHING By default, if you simply run [bundle install(1)][bundle-install] after running diff --git a/man/bundle-update.ronn b/man/bundle-update.ronn index 3ac60c5715..b9900e3b50 100644 --- a/man/bundle-update.ronn +++ b/man/bundle-update.ronn @@ -3,7 +3,7 @@ bundle-update(1) -- Update your gems to the latest available versions ## SYNOPSIS -`bundle update` <*gems> [--source=NAME] [--local] +`bundle update` <*gems> [--group=NAME] [--source=NAME] [--local] ## DESCRIPTION @@ -17,6 +17,12 @@ gem. ## OPTIONS +* `--group=<name>`: + Only update the gems in the specified group. For instance, you can update all gems + in the development group with `bundle update --group development`. You can also + call `bundle update rails --group test` to update the rails gem and all gems in + the test group, for example. + * `--source=<name>`: The name of a `:git` or `:path` source used in the Gemfile(5). For instance, with a `:git` source of `http://github.com/rails/rails.git`, @@ -41,37 +47,40 @@ Consider the following Gemfile(5): When you run [bundle install(1)][bundle-install] the first time, bundler will resolve all of the dependencies, all the way down, and install what you need: - Fetching source index for https://rubygems.org/ - Installing rake (10.0.2) - Installing abstract (1.0.0) - Installing activesupport (3.0.0.rc) - Installing builder (2.1.2) - Installing i18n (0.4.1) - Installing activemodel (3.0.0.rc) - Installing erubis (2.6.6) - Installing rack (1.2.1) - Installing rack-mount (0.6.9) - Installing rack-test (0.5.4) - Installing tzinfo (0.3.22) - Installing actionpack (3.0.0.rc) - Installing mime-types (1.16) - Installing polyglot (0.3.1) - Installing treetop (1.4.8) - Installing mail (2.2.5) - Installing actionmailer (3.0.0.rc) - Installing arel (0.4.0) - Installing activerecord (3.0.0.rc) - Installing activeresource (3.0.0.rc) - Installing bundler (1.0.0.rc.3) - Installing nokogiri (1.4.3.1) with native extensions - Installing thor (0.14.0) - Installing railties (3.0.0.rc) - Installing rails (3.0.0.rc) - - Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed. + Fetching gem metadata from https://rubygems.org/......... + Resolving dependencies... + Installing builder 2.1.2 + Installing abstract 1.0.0 + Installing rack 1.2.8 + Using bundler 1.7.6 + Installing rake 10.4.0 + Installing polyglot 0.3.5 + Installing mime-types 1.25.1 + Installing i18n 0.4.2 + Installing mini_portile 0.6.1 + Installing tzinfo 0.3.42 + Installing rack-mount 0.6.14 + Installing rack-test 0.5.7 + Installing treetop 1.4.15 + Installing thor 0.14.6 + Installing activesupport 3.0.0.rc + Installing erubis 2.6.6 + Installing activemodel 3.0.0.rc + Installing arel 0.4.0 + Installing mail 2.2.20 + Installing activeresource 3.0.0.rc + Installing actionpack 3.0.0.rc + Installing activerecord 3.0.0.rc + Installing actionmailer 3.0.0.rc + Installing railties 3.0.0.rc + Installing rails 3.0.0.rc + Installing nokogiri 1.6.5 + + Bundle complete! 2 Gemfile dependencies, 26 gems total. + Use `bundle show [gemname]` to see where a bundled gem is installed. As you can see, even though you have just two gems in the Gemfile(5), your application -actually needs 25 different gems in order to run. Bundler remembers the exact versions +actually needs 26 different gems in order to run. Bundler remembers the exact versions it installed in `Gemfile.lock`. The next time you run [bundle install(1)][bundle-install], bundler skips the dependency resolution and installs the same gems as it installed last time. diff --git a/man/bundle.ronn b/man/bundle.ronn index 1167b0b5ec..7b69c6735c 100644 --- a/man/bundle.ronn +++ b/man/bundle.ronn @@ -82,6 +82,12 @@ We divide `bundle` subcommands into primary commands and utilities. * `bundle clean(1)`: Cleans up unused gems in your bundler directory +## PLUGINS + +When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, +Bundler will try to find an executable on your path named `bundler-<command>` +and execute it, passing down any extra arguments to it. + ## OBSOLETE These commands are obsolete and should no longer be used diff --git a/man/gemfile.5.ronn b/man/gemfile.5.ronn index 88d6f0ffa7..876c927380 100644 --- a/man/gemfile.5.ronn +++ b/man/gemfile.5.ronn @@ -40,7 +40,7 @@ the username and password for any sources that need it. The command must be run once on each computer that will install the Gemfile, but this keeps the credentials from being stored in plain text in version control. - bundle config https://gems.example.com/ user:password + bundle config gems.example.com user:password For some sources, like a company Gemfury account, it may be easier to simply include the credentials in the Gemfile as part of the source URL. @@ -254,16 +254,26 @@ gem warning described above in ### GIT (:git) If necessary, you can specify that a gem is located at a particular -git repository. The repository can be public (`http://github.com/rails/rails.git`) -or private (`git@github.com:rails/rails.git`). If the repository is private, -the user that you use to run `bundle install` `MUST` have the appropriate -keys available in their `$HOME/.ssh`. +git repository using the `:git` parameter. The repository can be accessed via +several protocols: + + * `HTTP(S)`: + gem "rails", :git => "https://github.com/rails/rails.git" + * `SSH`: + gem "rails", :git => "git@github.com:rails/rails.git" + * `git`: + gem "rails", :git => "git://github.com/rails/rails.git" -Git repositories are specified using the `:git` parameter. The `group`, -`platforms`, and `require` options are available and behave exactly the same -as they would for a normal gem. +If using SSH, the user that you use to run `bundle install` `MUST` have the +appropriate keys available in their `$HOME/.ssh`. - gem "rails", :git => "git://github.com/rails/rails.git" +`NOTE`: `http://` and `git://` URLs should be avoided if at all possible. These +protocols are unauthenticated, so a man-in-the-middle attacker can deliver +malicious code and compromise your system. HTTPS and SSH are strongly +preferred. + +The `group`, `platforms`, and `require` options are available and behave +exactly the same as they would for a normal gem. A git repository `SHOULD` have at least one file, at the root of the directory containing the gem, with the extension `.gemspec`. This file @@ -280,7 +290,7 @@ to, a version specifier, if provided, means that the git repository is only valid if the `.gemspec` specifies a version matching the version specifier. If not, bundler will print a warning. - gem "rails", "2.3.8", :git => "git://github.com/rails/rails.git" + gem "rails", "2.3.8", :git => "https://github.com/rails/rails.git" # bundle install will fail, because the .gemspec in the rails # repository's master branch specifies version 3.0.0 @@ -315,8 +325,25 @@ and then installs the resulting gem. The `gem build` command, which comes standard with Rubygems, evaluates the `.gemspec` in the context of the directory in which it is located. +### GIT SOURCE (:git_source) + +A custom git source can be defined via the `git_source` method. Provide the source's name +as an argument, and a block which receives a single argument and interpolates it into a +string to return the full repo address: + + git_source(:stash){ |repo_name| "https://stash.corp.acme.pl/#{repo_name}.git" } + gem 'rails', :stash => 'forks/rails' + +In addition, if you wish to choose a specific branch: + + gem "rails", :stash => "forks/rails", :branch => "branch_name" + ### GITHUB (:github) +`NOTE`: This shorthand should be avoided until Bundler 2.0, since it +currently expands to an insecure `git://` URL. This allows a +man-in-the-middle attacker to compromise your system. + If the git repository you want to use is hosted on GitHub and is public, you can use the :github shorthand to specify just the github username and repository name (without the trailing ".git"), separated by a slash. If both the username and repository name are the @@ -329,9 +356,36 @@ Are both equivalent to gem "rails", :git => "git://github.com/rails/rails.git" -In addition, if you wish to choose a specific branch: +Since the `github` method is a specialization of `git_source`, it accepts a `:branch` named argument. + +### GIST (:gist) + +If the git repository you want to use is hosted as a Github Gist and is public, you can use +the :gist shorthand to specify just the gist identifier (without the trailing ".git"). + + gem "the_hatch", :gist => "4815162342" - gem "rails", :github => "rails/rails", :branch => "branch_name" +Is equivalent to: + + gem "the_hatch", :git => "https://gist.github.com/4815162342.git" + +Since the `gist` method is a specialization of `git_source`, it accepts a `:branch` named argument. + +### BITBUCKET (:bitbucket) + +If the git repository you want to use is hosted on Bitbucket and is public, you can use the +:bitbucket shorthand to specify just the bitbucket username and repository name (without the +trailing ".git"), separated by a slash. If both the username and repository name are the +same, you can omit one. + + gem "rails", :bitbucket => "rails/rails" + gem "rails", :bitbucket => "rails" + +Are both equivalent to + + gem "rails", :git => "https://rails@bitbucket.org/rails/rails.git" + +Since the `bitbucket` method is a specialization of `git_source`, it accepts a `:branch` named argument. ### PATH (:path) @@ -349,6 +403,13 @@ gems specified as paths. gem "rails", :path => "vendor/rails" +If you would like to use multiple local gems directly from the filesystem, you can set a global `path` option to the path containing the gem's files. This will automatically load gemspec files from subdirectories. + + path 'components' do + gem 'admin_ui' + gem 'public_ui' + end + ## BLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS The `:source`, `:git`, `:path`, `:group`, and `:platforms` options may be @@ -359,7 +420,7 @@ applied to a group of gems by using block form. gem "another_internal_gem" end - git "git://github.com/rails/rails.git" do + git "https://github.com/rails/rails.git" do gem "activesupport" gem "actionpack" end diff --git a/spec/bundler/anonymizable_uri_spec.rb b/spec/bundler/anonymizable_uri_spec.rb index e5d9faeb84..c444ea04d7 100644 --- a/spec/bundler/anonymizable_uri_spec.rb +++ b/spec/bundler/anonymizable_uri_spec.rb @@ -2,14 +2,22 @@ require 'spec_helper' require 'bundler/anonymizable_uri' describe Bundler::AnonymizableURI do - let(:anonymizable_uri) { Bundler::AnonymizableURI.new(original_uri) } + def auri(uri, auth = nil) + Bundler::AnonymizableURI.new(uri, auth) + end describe "#without_credentials" do context "when the original URI has no credentials" do let(:original_uri) { URI('https://rubygems.org') } it "returns the original URI" do - expect(anonymizable_uri.without_credentials).to eq(original_uri) + expect(auri(original_uri).without_credentials).to eq(original_uri) + end + + it "applies given credentials" do + with_auth = original_uri.dup + with_auth.userinfo = "user:pass" + expect(auri(original_uri, "user:pass").original_uri).to eq(with_auth) end end @@ -17,7 +25,11 @@ describe Bundler::AnonymizableURI do let(:original_uri) { URI("https://username:password@gems.example.com") } it "returns the URI without username and password" do - expect(anonymizable_uri.without_credentials).to eq(URI("https://gems.example.com")) + expect(auri(original_uri).without_credentials).to eq(URI("https://gems.example.com")) + end + + it "does not apply given credentials" do + expect(auri(original_uri, "other:stuff").original_uri).to eq(original_uri) end end @@ -25,7 +37,7 @@ describe Bundler::AnonymizableURI do let(:original_uri) { URI("https://SeCrEt-ToKeN@gem.fury.io/me/") } it "returns the URI without username and password" do - expect(anonymizable_uri.without_credentials).to eq(URI("https://gem.fury.io/me/")) + expect(auri(original_uri).without_credentials).to eq(URI("https://gem.fury.io/me/")) end end end diff --git a/spec/bundler/bundler_spec.rb b/spec/bundler/bundler_spec.rb index 013dc29271..fa226002cc 100644 --- a/spec/bundler/bundler_spec.rb +++ b/spec/bundler/bundler_spec.rb @@ -44,14 +44,13 @@ describe Bundler do end end - context "with correct YAML file" do + context "with correct YAML file", :if => defined?(Encoding) do it "can load a gemspec with unicode characters with default ruby encoding" do # spec_helper forces the external encoding to UTF-8 but that's not the - # ruby default. - if defined?(Encoding) - encoding = Encoding.default_external - Encoding.default_external = "ASCII" - end + # default until Ruby 2.0 + verbose, $VERBOSE = $VERBOSE, false + encoding, Encoding.default_external = Encoding.default_external, "ASCII" + $VERBOSE = verbose File.open(app_gemspec_path, "wb") do |file| file.puts <<-GEMSPEC.gsub(/^\s+/, '') @@ -64,7 +63,9 @@ describe Bundler do expect(subject.author).to eq("André the Giant") - Encoding.default_external = encoding if defined?(Encoding) + verbose, $VERBOSE = $VERBOSE, false + Encoding.default_external = encoding + $VERBOSE = verbose end end diff --git a/spec/bundler/cli_spec.rb b/spec/bundler/cli_spec.rb index ef4ec79656..7917726efe 100644 --- a/spec/bundler/cli_spec.rb +++ b/spec/bundler/cli_spec.rb @@ -5,12 +5,25 @@ describe "bundle executable" do let(:source_uri) { "http://localgemserver.test" } it "returns non-zero exit status when passed unrecognized options" do - bundle '--invalid_argument', :exitstatus => true - expect(exitstatus).to_not be_zero + bundle '--invalid_argument' + expect(exitstatus).to_not be_zero if exitstatus end it "returns non-zero exit status when passed unrecognized task" do - bundle 'unrecognized-tast', :exitstatus => true - expect(exitstatus).to_not be_zero + bundle 'unrecognized-tast' + expect(exitstatus).to_not be_zero if exitstatus + end + + it "looks for a binary and executes it if it's named bundler-<task>" do + File.open(tmp('bundler-testtasks'), 'w', 0755) do |f| + f.puts "#!/usr/bin/env ruby\nputs 'Hello, world'\n" + end + + with_path_as(tmp) do + bundle 'testtasks' + end + + expect(exitstatus).to be_zero if exitstatus + expect(out).to eq('Hello, world') end end diff --git a/spec/bundler/dsl_spec.rb b/spec/bundler/dsl_spec.rb index 03db5d2033..e3ff2306e9 100644 --- a/spec/bundler/dsl_spec.rb +++ b/spec/bundler/dsl_spec.rb @@ -6,7 +6,7 @@ describe Bundler::Dsl do allow(Bundler::Source::Rubygems).to receive(:new){ @rubygems } end - describe "#register_host" do + describe "#git_source" do it "registers custom hosts" do subject.git_source(:example){ |repo_name| "git@git.example.com:#{repo_name}.git" } subject.git_source(:foobar){ |repo_name| "git@foobar.com:#{repo_name}.git" } @@ -49,6 +49,18 @@ describe Bundler::Dsl do github_uri = "git://github.com/rails/rails.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end + + it "converts :bitbucket to :git" do + subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails") + bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/flatlab-rails.git" + expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri) + end + + it "converts 'mcorp' to 'mcorp/mcorp'" do + subject.gem("not-really-a-gem", :bitbucket => "mcorp") + bitbucket_uri = "https://mcorp@bitbucket.org/mcorp/mcorp.git" + expect(subject.dependencies.first.source.uri).to eq(bitbucket_uri) + end end end @@ -72,6 +84,95 @@ describe Bundler::Dsl do end end + describe "#gem" do + [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :mri, :mri_18, :mri_19, + :mri_20, :mri_21, :jruby, :rbx].each do |platform| + it "allows #{platform} as a valid platform" do + subject.gem("foo", :platform => platform) + end + end + + it "rejects invalid platforms" do + expect { subject.gem("foo", :platform => :bogus) }. + to raise_error(Bundler::GemfileError, /is not a valid platform/) + end + + it "rejects with a leading space in the name" do + expect { subject.gem(" foo") }. + to raise_error(Bundler::GemfileError, /' foo' is not a valid gem name because it contains whitespace/) + end + + it "rejects with a trailing space in the name" do + expect { subject.gem("foo ") }. + to raise_error(Bundler::GemfileError, /'foo ' is not a valid gem name because it contains whitespace/) + end + + it "rejects with a space in the gem name" do + expect { subject.gem("fo o") }. + to raise_error(Bundler::GemfileError, /'fo o' is not a valid gem name because it contains whitespace/) + end + + it "rejects with a tab in the gem name" do + expect { subject.gem("fo\to") }. + to raise_error(Bundler::GemfileError, /'fo\to' is not a valid gem name because it contains whitespace/) + end + + it "rejects with a newline in the gem name" do + expect { subject.gem("fo\no") }. + to raise_error(Bundler::GemfileError, /'fo\no' is not a valid gem name because it contains whitespace/) + end + + it "rejects with a carriage return in the gem name" do + expect { subject.gem("fo\ro") }. + to raise_error(Bundler::GemfileError, /'fo\ro' is not a valid gem name because it contains whitespace/) + end + + it "rejects with a form feed in the gem name" do + expect { subject.gem("fo\fo") }. + to raise_error(Bundler::GemfileError, /'fo\fo' is not a valid gem name because it contains whitespace/) + end + + it "rejects symbols as gem name" do + expect { subject.gem(:foo) }. + to raise_error(Bundler::GemfileError, /You need to specify gem names as Strings. Use 'gem "foo"' instead/) + end + end + + context "can bundle groups of gems with" do + # git "https://github.com/rails/rails.git" do + # gem "railties" + # gem "action_pack" + # gem "active_model" + # end + describe "#git" do + it "from a single repo" do + rails_gems = ["railties", "action_pack", "active_model"] + subject.git "https://github.com/rails/rails.git" do + rails_gems.each { |rails_gem| subject.send :gem, rails_gem } + end + expect(subject.dependencies.map(&:name)).to match_array rails_gems + end + end + + # github 'spree' do + # gem 'spree_core' + # gem 'spree_api' + # gem 'spree_backend' + # end + describe "#github" do + it "from github" do + spree_gems = ["spree_core", "spree_api", "spree_backend"] + subject.github "spree" do + spree_gems.each { |spree_gem| subject.send :gem, spree_gem } + end + + subject.dependencies.each do |d| + expect(d.source.uri).to eq("git://github.com/spree/spree.git") + end + end + end + end + describe "syntax errors" do it "will raise a Bundler::GemfileError" do gemfile "gem 'foo', :path => /unquoted/string/syntax/error" diff --git a/spec/bundler/fetcher_spec.rb b/spec/bundler/fetcher_spec.rb new file mode 100644 index 0000000000..4640f63519 --- /dev/null +++ b/spec/bundler/fetcher_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Bundler::Fetcher do + before do + allow(Bundler).to receive(:root){ Pathname.new("root") } + end + + describe "#user_agent" do + it "builds user_agent with current ruby version and Bundler settings" do + allow(Bundler.settings).to receive(:all).and_return(["foo", "bar"]) + expect(described_class.user_agent).to match(/bundler\/(\d.)/) + expect(described_class.user_agent).to match(/rubygems\/(\d.)/) + expect(described_class.user_agent).to match(/ruby\/(\d.)/) + expect(described_class.user_agent).to match(/options\/foo,bar/) + end + end +end diff --git a/spec/bundler/gem_helper_spec.rb b/spec/bundler/gem_helper_spec.rb index d3df2dc768..2b0f3d97aa 100644 --- a/spec/bundler/gem_helper_spec.rb +++ b/spec/bundler/gem_helper_spec.rb @@ -3,7 +3,7 @@ require 'rake' require 'bundler/gem_helper' describe Bundler::GemHelper do - let(:app_name) { "test" } + let(:app_name) { "lorem__ipsum" } let(:app_path) { bundled_app app_name } let(:app_gemspec_path) { app_path.join("#{app_name}.gemspec") } @@ -40,10 +40,10 @@ describe Bundler::GemHelper do it "handles namespaces and converts them to CamelCase" do bundle "gem #{app_name}-foo_bar" - app_path = bundled_app "#{app_name}-foo_bar" + underscore_path = bundled_app "#{app_name}-foo_bar" - lib = app_path.join("lib/#{app_name}/foo_bar.rb").read - expect(lib).to include("module #{app_name.capitalize}") + lib = underscore_path.join("lib/#{app_name}/foo_bar.rb").read + expect(lib).to include("module LoremIpsum") expect(lib).to include("module FooBar") end end @@ -59,13 +59,14 @@ describe Bundler::GemHelper do end subject! { Bundler::GemHelper.new(app_path) } - let(:app_version) { "0.0.1" } + let(:app_version) { "0.1.0" } let(:app_gem_dir) { app_path.join("pkg") } let(:app_gem_path) { app_gem_dir.join("#{app_name}-#{app_version}.gem") } let(:app_gemspec_content) { File.read(app_gemspec_path) } before(:each) do content = app_gemspec_content.gsub("TODO: ", "") + content.sub!(/homepage\s+= ".*"/, 'homepage = ""') File.open(app_gemspec_path, "w") { |file| file << content } end @@ -85,7 +86,8 @@ describe Bundler::GemHelper do end context "defines Rake tasks" do - let(:task_names) { %w[build install release] } + let(:task_names) { %w[build install release + release:guard_clean release:source_control_push release:rubygem_push] } context "before installation" do it "raises an error with appropriate message" do @@ -157,7 +159,18 @@ describe Bundler::GemHelper do end end - describe "#release_gem" do + describe "rake release" do + let!(:rake_application) { Rake.application } + + before(:each) do + Rake.application = Rake::Application.new + subject.install + end + + after(:each) do + Rake.application = rake_application + end + before do Dir.chdir(app_path) do `git init` @@ -169,13 +182,13 @@ describe Bundler::GemHelper do context "fails" do it "when there are unstaged files" do - expect { subject.release_gem }. + expect { Rake.application["release"].invoke }. to raise_error("There are files that need to be committed first.") end it "when there are uncommitted files" do Dir.chdir(app_path) { `git add .` } - expect { subject.release_gem }. + expect { Rake.application["release"].invoke }. to raise_error("There are files that need to be committed first.") end @@ -185,7 +198,7 @@ describe Bundler::GemHelper do allow(Bundler.ui).to receive(:error) Dir.chdir(app_path) { `git commit -a -m "initial commit"` } - expect { subject.release_gem }.to raise_error + expect { Rake.application["release"].invoke }.to raise_error end end @@ -206,7 +219,7 @@ describe Bundler::GemHelper do Dir.chdir(app_path) { sys_exec("git push -u origin master", true) } - subject.release_gem + Rake.application["release"].invoke end it "even if tag already exists" do @@ -218,7 +231,7 @@ describe Bundler::GemHelper do `git tag -a -m \"Version #{app_version}\" v#{app_version}` end - subject.release_gem + Rake.application["release"].invoke end end end diff --git a/spec/bundler/settings_spec.rb b/spec/bundler/settings_spec.rb index 44f40ce3d0..56ee87da6e 100644 --- a/spec/bundler/settings_spec.rb +++ b/spec/bundler/settings_spec.rb @@ -10,4 +10,44 @@ describe Bundler::Settings do end end end + + describe "URI normalization" do + let(:settings) { described_class.new(bundled_app) } + + it "normalizes HTTP URIs in credentials configuration" do + settings["http://gemserver.example.org"] = "username:password" + expect(settings.all).to include("http://gemserver.example.org/") + end + + it "normalizes HTTPS URIs in credentials configuration" do + settings["https://gemserver.example.org"] = "username:password" + expect(settings.all).to include("https://gemserver.example.org/") + end + + it "normalizes HTTP URIs in mirror configuration" do + settings["mirror.http://rubygems.org"] = "http://rubygems-mirror.org" + expect(settings.all).to include("mirror.http://rubygems.org/") + end + + it "normalizes HTTPS URIs in mirror configuration" do + settings["mirror.https://rubygems.org"] = "http://rubygems-mirror.org" + expect(settings.all).to include("mirror.https://rubygems.org/") + end + + it "does not normalize other config keys that happen to contain 'http'" do + settings["local.httparty"] = home("httparty") + expect(settings.all).to include("local.httparty") + end + + it "does not normalize other config keys that happen to contain 'https'" do + settings["local.httpsmarty"] = home("httpsmarty") + expect(settings.all).to include("local.httpsmarty") + end + + it "reads older keys without trailing slashes" do + settings["mirror.https://rubygems.org"] = "http://rubygems-mirror.org" + expect(settings.gem_mirrors).to eq(URI("https://rubygems.org/") => URI("http://rubygems-mirror.org/")) + end + + end end diff --git a/spec/bundler/source/rubygems_spec.rb b/spec/bundler/source/rubygems_spec.rb index 68fc6c6ed4..6bcde3d328 100644 --- a/spec/bundler/source/rubygems_spec.rb +++ b/spec/bundler/source/rubygems_spec.rb @@ -22,4 +22,5 @@ describe Bundler::Source::Rubygems do end end end + end diff --git a/spec/bundler/source_list_spec.rb b/spec/bundler/source_list_spec.rb index 4acaa9da5d..f5a8575ae8 100644 --- a/spec/bundler/source_list_spec.rb +++ b/spec/bundler/source_list_spec.rb @@ -117,19 +117,6 @@ describe Bundler::SourceList do expect(source_list.all_sources).to include rubygems_aggregate end - it "returns path sources before git sources before rubygems sources before the aggregate" do - source_list.add_git_source('uri' => 'git://host/path.git') - source_list.add_rubygems_source('remotes' => ['https://rubygems.org']) - source_list.add_path_source('path' => '/path/to/gem') - - expect(source_list.all_sources).to eq [ - Bundler::Source::Path.new('path' => '/path/to/gem'), - Bundler::Source::Git.new('uri' => 'git://host/path.git'), - Bundler::Source::Rubygems.new('remotes' => ['https://rubygems.org']), - rubygems_aggregate, - ] - end - it "returns sources of the same type in the reverse order that they were added" do source_list.add_git_source('uri' => 'git://third-git.org/path.git') source_list.add_rubygems_source('remotes' => ['https://fifth-rubygems.org']) diff --git a/spec/cache/cache_path_spec.rb b/spec/cache/cache_path_spec.rb new file mode 100644 index 0000000000..c0e73ca588 --- /dev/null +++ b/spec/cache/cache_path_spec.rb @@ -0,0 +1,33 @@ +require "spec_helper" + +describe "bundle package" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + context "with --cache-path" do + it "caches gems at given path" do + bundle :package, "cache-path" => "vendor/cache-foo" + expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist + end + end + + context "with config cache_path" do + it "caches gems at given path" do + bundle "config cache_path vendor/cache-foo" + bundle :package + expect(bundled_app("vendor/cache-foo/rack-1.0.0.gem")).to exist + end + end + + context "when given an absolute path" do + it "exits with non-zero status" do + bundle :package, "cache-path" => "/tmp/cache-foo" + expect(out).to match(/must be relative/) + expect(exitstatus).to eq(15) if exitstatus + end + end +end diff --git a/spec/cache/platform_spec.rb b/spec/cache/platform_spec.rb index 0701d5d4fb..6b73f90f05 100644 --- a/spec/cache/platform_spec.rb +++ b/spec/cache/platform_spec.rb @@ -5,17 +5,13 @@ describe "bundle cache with multiple platforms" do gemfile <<-G source "file://#{gem_repo1}" - platforms :ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22 do + platforms :mri, :rbx do gem "rack", "1.0.0" end platforms :jruby do gem "activesupport", "2.3.5" end - - platforms :mri, :mri_18, :mri_19, :mri_20, :mri_21, :mri_22 do - gem "activerecord", "2.3.2" - end G lockfile <<-G @@ -24,7 +20,6 @@ describe "bundle cache with multiple platforms" do specs: rack (1.0.0) activesupport (2.3.5) - activerecord (2.3.2) PLATFORMS ruby @@ -33,25 +28,26 @@ describe "bundle cache with multiple platforms" do DEPENDENCIES rack (1.0.0) activesupport (2.3.5) - activerecord (2.3.2) G - cache_gems "rack-1.0.0", "activesupport-2.3.5", "activerecord-2.3.2" + cache_gems "rack-1.0.0", "activesupport-2.3.5" end - it "ensures that bundle install does not delete gems for other platforms" do + it "ensures that a succesful bundle install does not delete gems for other platforms" do bundle "install" + expect(exitstatus).to eq 0 if exitstatus + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist - expect(bundled_app("vendor/cache/activerecord-2.3.2.gem")).to exist end - it "ensures that bundle update does not delete gems for other platforms" do + it "ensures that a succesful bundle update does not delete gems for other platforms" do bundle "update" + expect(exitstatus).to eq 0 if exitstatus + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/activesupport-2.3.5.gem")).to exist - expect(bundled_app("vendor/cache/activerecord-2.3.2.gem")).to exist end end diff --git a/spec/commands/binstubs_spec.rb b/spec/commands/binstubs_spec.rb index e307539f76..284913a354 100644 --- a/spec/commands/binstubs_spec.rb +++ b/spec/commands/binstubs_spec.rb @@ -45,8 +45,8 @@ describe "bundle binstubs <gem>" do gem "rack" G - bundle "binstubs", :exitstatus => true - expect(exitstatus).to eq(1) + bundle "binstubs" + expect(exitstatus).to eq(1) if exitstatus expect(out).to eq("`bundle binstubs` needs at least one gem to run.") end @@ -111,9 +111,9 @@ describe "bundle binstubs <gem>" do source "file://#{gem_repo1}" G - bundle "binstubs doesnt_exist", :exitstatus => true + bundle "binstubs doesnt_exist" - expect(exitstatus).to eq(7) + expect(exitstatus).to eq(7) if exitstatus expect(out).to eq("Could not find gem 'doesnt_exist'.") end end @@ -216,4 +216,29 @@ describe "bundle binstubs <gem>" do expect(out).to include('no executables for the gem with_development_dependency') end end + + context "when BUNDLE_INSTALL is specified" do + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "config auto_install 1" + bundle "binstubs rack" + expect(out).to include('Installing rack 1.0.0') + should_be_installed "rack 1.0.0" + end + + it "does nothing when already up to date" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "config auto_install 1" + bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => 1 } + expect(out).not_to include('Installing rack 1.0.0') + end + end end diff --git a/spec/commands/check_spec.rb b/spec/commands/check_spec.rb index 248b5c2eed..6d047597d5 100644 --- a/spec/commands/check_spec.rb +++ b/spec/commands/check_spec.rb @@ -7,8 +7,8 @@ describe "bundle check" do gem "rails" G - bundle :check, :exitstatus => true - expect(@exitstatus).to eq(0) + bundle :check + expect(exitstatus).to eq(0) if exitstatus expect(out).to eq("The Gemfile's dependencies are satisfied") end @@ -67,8 +67,8 @@ describe "bundle check" do gem "rails" G - bundle :check, :exitstatus => true - expect(@exitstatus).to be > 0 + bundle :check + expect(exitstatus).to be > 0 if exitstatus expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.") end @@ -101,8 +101,8 @@ describe "bundle check" do G bundle "install --without foo" - bundle "check", :exitstatus => true - expect(@exitstatus).to eq(0) + bundle "check" + expect(exitstatus).to eq(0) if exitstatus expect(out).to include("The Gemfile's dependencies are satisfied") end @@ -119,9 +119,9 @@ describe "bundle check" do gem "rack" G - bundle "check", :exitstatus => true + bundle "check" expect(out).to include("* rack (1.0.0)") - expect(@exitstatus).to eq(1) + expect(exitstatus).to eq(1) if exitstatus end it "ignores missing gems restricted to other platforms" do @@ -187,14 +187,14 @@ describe "bundle check" do end it "outputs an error when the default Gemfile is not found" do - bundle :check, :exitstatus => true - expect(@exitstatus).to eq(10) + bundle :check + expect(exitstatus).to eq(10) if exitstatus expect(out).to include("Could not locate Gemfile") end it "does not output fatal error message" do - bundle :check, :exitstatus => true - expect(@exitstatus).to eq(10) + bundle :check + expect(exitstatus).to eq(10) if exitstatus expect(out).not_to include("Unfortunately, a fatal error has occurred. ") end @@ -224,8 +224,8 @@ describe "bundle check" do bundle "install --deployment" FileUtils.rm(bundled_app("Gemfile.lock")) - bundle :check, :exitstatus => true - expect(exitstatus).not_to eq(0) + bundle :check + expect(exitstatus).not_to eq(0) if exitstatus end context "--path" do @@ -240,15 +240,15 @@ describe "bundle check" do end it "returns success" do - bundle "check --path vendor/bundle", :exitstatus => true - expect(@exitstatus).to eq(0) + bundle "check --path vendor/bundle" + expect(exitstatus).to eq(0) if exitstatus expect(out).to eq("The Gemfile's dependencies are satisfied") end it "should write to .bundle/config" do - bundle "check --path vendor/bundle", :exitstatus => true - bundle "check", :exitstatus => true - expect(@exitstatus).to eq(0) + bundle "check --path vendor/bundle" + bundle "check" + expect(exitstatus).to eq(0) if exitstatus end end @@ -263,8 +263,8 @@ describe "bundle check" do it "returns success when the Gemfile is satisfied" do bundle :install - bundle :check, :exitstatus => true - expect(@exitstatus).to eq(0) + bundle :check + expect(exitstatus).to eq(0) if exitstatus expect(out).to eq("The Gemfile's dependencies are satisfied") end diff --git a/spec/commands/clean_spec.rb b/spec/commands/clean_spec.rb index cee1312a5f..529c245912 100644 --- a/spec/commands/clean_spec.rb +++ b/spec/commands/clean_spec.rb @@ -270,8 +270,8 @@ describe "bundle clean" do bundle "install --path vendor/bundle --without development" - bundle :clean, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :clean + expect(exitstatus).to eq(0) if exitstatus end it "displays an error when used without --path" do @@ -281,10 +281,10 @@ describe "bundle clean" do gem "rack", "1.0.0" G - bundle :clean, :exitstatus => true + bundle :clean - expect(exitstatus).to eq(1) - expect(out).to eq("Can only use bundle clean when --path is set or --force is set") + expect(exitstatus).to eq(1) if exitstatus + expect(out).to include("--force") end # handling bundle clean upgrade path from the pre's @@ -505,9 +505,9 @@ describe "bundle clean" do bundle "clean --force" - sys_status "foo" + sys_exec "foo" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus expect(out).to eq("1.0") end @@ -527,9 +527,9 @@ describe "bundle clean" do G bundle "install --path vendor/bundle" - bundle :clean, :exitstatus => true + bundle :clean - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end it "doesn't remove gems in dry-run mode" do @@ -589,4 +589,28 @@ describe "bundle clean" do expect(vendored_gems("bin/rackup")).to exist end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle "install --path vendor/bundle --no-clean" + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "weakling" + G + + bundle "config auto_install 1" + bundle :clean + expect(out).to include('Installing weakling 0.0.3') + should_have_gems 'thin-1.0', 'rack-1.0.0', 'weakling-0.0.3' + should_not_have_gems 'foo-1.0' + end end diff --git a/spec/commands/config_spec.rb b/spec/commands/config_spec.rb index aad00fa469..cd1f23e4a0 100644 --- a/spec/commands/config_spec.rb +++ b/spec/commands/config_spec.rb @@ -177,6 +177,13 @@ describe ".bundle/config" do run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end" expect(out).to eq("false") end + + it "can set properties with periods via the environment" do + ENV["BUNDLE_FOO__BAR"] = "baz" + + run "puts Bundler.settings['foo.bar']" + expect(out).to eq("baz") + end end describe "gem mirrors" do @@ -195,6 +202,10 @@ E describe "quoting" do before(:each) { gemfile "# no gems" } + let(:long_string) do + "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \ + "--with-xslt-dir=/usr/pkg" + end it "saves quotes" do bundle "config foo something\\'" @@ -225,7 +236,6 @@ E end it "doesn't duplicate quotes around long wrapped values" do - long_string = "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib --with-xslt-dir=/usr/pkg" bundle "config foo #{long_string}" run "puts Bundler.settings[:foo]" @@ -241,7 +251,12 @@ E describe "very long lines" do before(:each) { bundle :install } let(:long_string) do - "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib --with-xslt-dir=/usr/pkg" + "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \ + "--with-xslt-dir=/usr/pkg" + end + let(:long_string_without_special_characters) do + "here is quite a long string that will wrap to a second line but will not be " \ + "surrounded by quotes" end let(:long_string_without_special_characters) do "here is quite a long string that will wrap to a second line but will not be surrounded by quotes" @@ -259,5 +274,23 @@ E expect(out).to match(long_string_without_special_characters) end end +end + +describe "setting gemfile via config" do + context "when only the non-default Gemfile exists" do + it "persists the gemfile location to .bundle/config" do + File.open(bundled_app("NotGemfile"), "w") do |f| + f.write <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + end + bundle "config --local gemfile #{bundled_app("NotGemfile")}" + expect(File.exist?(".bundle/config")).to eq(true) + + bundle "config" + expect(out).to include("NotGemfile") + end + end end diff --git a/spec/commands/console_spec.rb b/spec/commands/console_spec.rb index 75d6796c9d..f26fbcdba5 100644 --- a/spec/commands/console_spec.rb +++ b/spec/commands/console_spec.rb @@ -36,10 +36,9 @@ describe "bundle console" do input.puts("__callee__") input.puts("exit") end - expect(out).to include("irb") + expect(out).to include("IRB") end - it "doesn't load any other groups" do bundle "console" do |input| input.puts("puts ACTIVESUPPORT") @@ -73,4 +72,23 @@ describe "bundle console" do expect(out).to include("NameError") end end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + gem "foo" + G + + bundle "config auto_install 1" + bundle :console do |input| + input.puts("puts 'hello'") + input.puts("exit") + end + expect(out).to include("Installing foo 1.0") + expect(out).to include("hello") + should_be_installed "foo 1.0" + end end diff --git a/spec/commands/exec_spec.rb b/spec/commands/exec_spec.rb index 52b93565de..835b0795f0 100644 --- a/spec/commands/exec_spec.rb +++ b/spec/commands/exec_spec.rb @@ -189,8 +189,8 @@ describe "bundle exec" do gem "rack" G - bundle "exec foobarbaz", :exitstatus => true - expect(exitstatus).to eq(127) + bundle "exec foobarbaz" + expect(exitstatus).to eq(127) if exitstatus expect(out).to include("bundler: command not found: foobarbaz") expect(out).to include("Install missing gem executables with `bundle install`") end @@ -201,8 +201,8 @@ describe "bundle exec" do G bundle "exec touch foo" - bundle "exec ./foo", :exitstatus => true - expect(exitstatus).to eq(126) + bundle "exec ./foo" + expect(exitstatus).to eq(126) if exitstatus expect(out).to include("bundler: not executable: ./foo") end @@ -211,8 +211,8 @@ describe "bundle exec" do gem "rack" G - bundle "exec", :exitstatus => true - expect(exitstatus).to eq(128) + bundle "exec" + expect(exitstatus).to eq(128) if exitstatus expect(out).to include("bundler: exec needs a command to run") end @@ -306,4 +306,16 @@ describe "bundle exec" do end end end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + gem "foo" + G + + bundle "config auto_install 1" + bundle "exec rackup" + expect(out).to include("Installing foo 1.0") + end end diff --git a/spec/commands/licenses_spec.rb b/spec/commands/licenses_spec.rb index c8d5ff739e..e2aba5a717 100644 --- a/spec/commands/licenses_spec.rb +++ b/spec/commands/licenses_spec.rb @@ -15,4 +15,17 @@ describe "bundle licenses" do expect(out).to include("actionpack: Unknown") expect(out).to include("with_license: MIT") end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "with_license" + gem "foo" + G + + bundle "config auto_install 1" + bundle :licenses + expect(out).to include("Installing foo 1.0") + end end diff --git a/spec/commands/newgem_spec.rb b/spec/commands/newgem_spec.rb index 024ca0cabb..fc2f113e8a 100644 --- a/spec/commands/newgem_spec.rb +++ b/spec/commands/newgem_spec.rb @@ -1,6 +1,7 @@ require "spec_helper" describe "bundle gem" do + def reset! super global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false" @@ -15,7 +16,7 @@ describe "bundle gem" do after do `git config --global user.name "#{@git_name}"` - `git config --global user.email #{@git_email}` + `git config --global user.email "#{@git_email}"` end shared_examples_for "git config is present" do @@ -30,7 +31,7 @@ describe "bundle gem" do end end - shared_examples_for "git config is absent" do |hoge| + shared_examples_for "git config is absent" do it "sets gemspec author to default message if git user.name is not set or empty" do expect(generated_gem.gemspec.authors.first).to eq("TODO: Write your name") end @@ -40,6 +41,72 @@ describe "bundle gem" do end end + it "generates a valid gemspec" do + system_gems ["rake-10.0.2"] + + in_app_root + bundle "gem newgem --bin" + + process_file(bundled_app('newgem', "newgem.gemspec")) do |line| + next line unless line =~ /TODO/ + # Simulate replacing TODOs with real values + case line + when /spec\.metadata\['allowed_push_host'\]/, /spec\.homepage/ + line.gsub(/\=.*$/, "= 'http://example.org'") + when /spec\.summary/ + line.gsub(/\=.*$/, "= %q{A short summary of my new gem.}") + when /spec\.description/ + line.gsub(/\=.*$/, "= %q{A longer description of my new gem.}") + else + line + end + end + + Dir.chdir(bundled_app('newgem')) do + bundle "exec rake build" + end + + expect(exitstatus).to be_zero if exitstatus + expect(out).not_to include("ERROR") + expect(err).not_to include("ERROR") + end + + context "gem naming with relative paths" do + before do + reset! + in_app_root + end + + it "resolves ." do + create_temporary_dir('tmp') + + bundle 'gem .' + + expect(bundled_app("tmp/lib/tmp.rb")).to exist + end + + it "resolves .." do + create_temporary_dir('temp/empty_dir') + + bundle 'gem ..' + + expect(bundled_app("temp/lib/temp.rb")).to exist + end + + it "resolves relative directory" do + create_temporary_dir('tmp/empty/tmp') + + bundle 'gem ../../empty' + + expect(bundled_app("tmp/empty/lib/empty.rb")).to exist + end + + def create_temporary_dir(dir) + FileUtils.mkdir_p(dir) + Dir.chdir(dir) + end + end + context "gem naming with underscore" do let(:gem_name) { 'test_gem' } @@ -53,15 +120,15 @@ describe "bundle gem" do it "generates a gem skeleton" do expect(bundled_app("test_gem/test_gem.gemspec")).to exist - expect(bundled_app("test_gem/LICENSE.txt")).to exist expect(bundled_app("test_gem/Gemfile")).to exist expect(bundled_app("test_gem/Rakefile")).to exist expect(bundled_app("test_gem/lib/test_gem.rb")).to exist expect(bundled_app("test_gem/lib/test_gem/version.rb")).to exist + expect(bundled_app("test_gem/.gitignore")).to exist end - it "starts with version 0.0.1" do - expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/VERSION = "0.0.1"/) + it "starts with version 0.1.0" do + expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/VERSION = "0.1.0"/) end it "does not nest constants" do @@ -83,8 +150,9 @@ describe "bundle gem" do it_should_behave_like "git config is absent" end - it "sets gemspec license to MIT by default" do - expect(generated_gem.gemspec.license).to eq("MIT") + it "sets gemspec metadata['allowed_push_host']", :rubygems => "2.0" do + expect(generated_gem.gemspec.metadata['allowed_push_host']). + to match("delete to allow pushes to any server") end it "requires the version file" do @@ -117,11 +185,11 @@ describe "bundle gem" do end it "builds bin skeleton" do - expect(bundled_app("test_gem/bin/test_gem")).to exist + expect(bundled_app("test_gem/exe/test_gem")).to exist end it "requires 'test-gem'" do - expect(bundled_app("test_gem/bin/test_gem").read).to match(/require 'test_gem'/) + expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/) end end @@ -163,6 +231,35 @@ describe "bundle gem" do end end + context "gem.test setting set to rspec" do + before do + reset! + in_app_root + bundle "config gem.test rspec" + bundle "gem #{gem_name}" + end + + it "builds spec skeleton" do + expect(bundled_app("test_gem/.rspec")).to exist + expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist + expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist + end + end + + context "gem.test setting set to rspec and --test is set to minitest" do + before do + reset! + in_app_root + bundle "config gem.test rspec" + bundle "gem #{gem_name} --test=minitest" + end + + it "builds spec skeleton" do + expect(bundled_app("test_gem/test/test_test_gem.rb")).to exist + expect(bundled_app("test_gem/test/minitest_helper.rb")).to exist + end + end + context "--test parameter set to minitest" do before do reset! @@ -216,6 +313,47 @@ describe "bundle gem" do end end + context "with --mit option" do + let(:gem_name) { 'test-gem' } + + before do + bundle "gem #{gem_name} --mit" + # reset gemspec cache for each test because of commit 3d4163a + Bundler.clear_gemspec_cache + end + + it "generates a gem skeleton with MIT license" do + expect(bundled_app("test-gem/test-gem.gemspec")).to exist + expect(bundled_app("test-gem/LICENSE.txt")).to exist + expect(bundled_app("test-gem/Gemfile")).to exist + expect(bundled_app("test-gem/Rakefile")).to exist + expect(bundled_app("test-gem/lib/test/gem.rb")).to exist + expect(bundled_app("test-gem/lib/test/gem/version.rb")).to exist + + skel = Bundler::GemHelper.new(bundled_app(gem_name).to_s) + expect(skel.gemspec.license).to eq("MIT") + end + end + + context "with --coc option" do + let(:gem_name) { 'test-gem' } + + before do + bundle "gem #{gem_name} --coc" + # reset gemspec cache for each test because of commit 3d4163a + Bundler.clear_gemspec_cache + end + + it "generates a gem skeleton with Code of Conduct" do + expect(bundled_app("test-gem/test-gem.gemspec")).to exist + expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to exist + expect(bundled_app("test-gem/Gemfile")).to exist + expect(bundled_app("test-gem/Rakefile")).to exist + expect(bundled_app("test-gem/lib/test/gem.rb")).to exist + expect(bundled_app("test-gem/lib/test/gem/version.rb")).to exist + end + end + context "gem naming with dashed" do let(:gem_name) { 'test-gem' } @@ -229,15 +367,15 @@ describe "bundle gem" do it "generates a gem skeleton" do expect(bundled_app("test-gem/test-gem.gemspec")).to exist - expect(bundled_app("test-gem/LICENSE.txt")).to exist + # expect(bundled_app("test-gem/LICENSE.txt")).to exist expect(bundled_app("test-gem/Gemfile")).to exist expect(bundled_app("test-gem/Rakefile")).to exist expect(bundled_app("test-gem/lib/test/gem.rb")).to exist expect(bundled_app("test-gem/lib/test/gem/version.rb")).to exist end - it "starts with version 0.0.1" do - expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/VERSION = "0.0.1"/) + it "starts with version 0.1.0" do + expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/VERSION = "0.1.0"/) end it "nests constants so they work" do @@ -259,8 +397,9 @@ describe "bundle gem" do it_should_behave_like "git config is absent" end - it "sets gemspec license to MIT by default" do - expect(generated_gem.gemspec.license).to eq("MIT") + it "sets gemspec metadata['allowed_push_host']", :rubygems => "2.0" do + expect(generated_gem.gemspec.metadata['allowed_push_host']). + to match("delete to allow pushes to any server") end it "requires the version file" do @@ -293,11 +432,11 @@ describe "bundle gem" do end it "builds bin skeleton" do - expect(bundled_app("test-gem/bin/test-gem")).to exist + expect(bundled_app("test-gem/exe/test-gem")).to exist end it "requires 'test/gem'" do - expect(bundled_app("test-gem/bin/test-gem").read).to match(/require 'test\/gem'/) + expect(bundled_app("test-gem/exe/test-gem").read).to match(/require "test\/gem"/) end end @@ -423,6 +562,62 @@ describe "bundle gem" do it "includes rake-compiler" do expect(bundled_app("test_gem/test_gem.gemspec").read).to include('spec.add_development_dependency "rake-compiler"') end + + it "depends on compile task for build" do + rakefile = strip_whitespace <<-RAKEFILE + require "bundler/gem_tasks" + + require "rake/extensiontask" + + task :build => :compile + + Rake::ExtensionTask.new("test_gem") do |ext| + ext.lib_dir = "lib/test_gem" + end + RAKEFILE + + expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile) + end end end + + context "on first run" do + before do + in_app_root + end + + it "asks about test framework" do + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false" + + bundle "gem foobar" do |input| + input.puts "rspec" + end + + expect(bundled_app("foobar/spec/spec_helper.rb")).to exist + end + + it "asks about MIT license" do + global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false" + + bundle :config + + bundle "gem foobar" do |input| + input.puts "yes" + end + + expect(bundled_app("foobar/LICENSE.txt")).to exist + end + + it "asks about CoC" do + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false" + + + bundle "gem foobar" do |input| + input.puts "yes" + end + + expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist + end + end + end diff --git a/spec/commands/open_spec.rb b/spec/commands/open_spec.rb index e3a66ec1de..67ee40cfe5 100644 --- a/spec/commands/open_spec.rb +++ b/spec/commands/open_spec.rb @@ -65,4 +65,16 @@ describe "bundle open" do expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/) end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "foo" + G + + bundle "config auto_install 1" + bundle "open rails", :env => {"EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => ""} + expect(out).to include("Installing foo 1.0") + end end diff --git a/spec/commands/outdated_spec.rb b/spec/commands/outdated_spec.rb index da17a2eee8..4d8e6d5ce1 100644 --- a/spec/commands/outdated_spec.rb +++ b/spec/commands/outdated_spec.rb @@ -42,15 +42,15 @@ describe "bundle outdated" do update_git "foo", :path => lib_path("foo") end - bundle "outdated", :exitstatus => true + bundle "outdated" - expect(exitstatus).to_not be_zero + expect(exitstatus).to_not be_zero if exitstatus end it "returns success exit status if no outdated gems present" do - bundle "outdated", :exitstatus => true + bundle "outdated" - expect(exitstatus).to be_zero + expect(exitstatus).to be_zero if exitstatus end end @@ -149,8 +149,20 @@ describe "bundle outdated" do end it "returns non-zero exit code" do - bundle "outdated invalid_gem_name", :exitstatus => true - expect(exitstatus).to_not be_zero + bundle "outdated invalid_gem_name" + expect(exitstatus).to_not be_zero if exitstatus end end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + gem "foo" + G + + bundle "config auto_install 1" + bundle :outdated + expect(out).to include("Installing foo 1.0") + end end diff --git a/spec/commands/package_spec.rb b/spec/commands/package_spec.rb index e433b92094..d87ebc56bf 100644 --- a/spec/commands/package_spec.rb +++ b/spec/commands/package_spec.rb @@ -28,6 +28,32 @@ describe "bundle package" do expect(bundled_app("test/vendor/cache/")).to exist end end + + context "with --no-install" do + it "puts the gems in vendor/cache but does not install them" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + D + + bundle "package --no-install" + + should_not_be_installed "rack 1.0.0" + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + end + end + + context "with --all-platforms" do + it "puts the gems in vendor/cache even for other rubies", :ruby => "2.1" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack', :platforms => :ruby_19 + D + + bundle "package --all-platforms" + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + end + end end describe "bundle install with gem sources" do diff --git a/spec/commands/show_spec.rb b/spec/commands/show_spec.rb index 01a2c2a50a..18105541c7 100644 --- a/spec/commands/show_spec.rb +++ b/spec/commands/show_spec.rb @@ -63,8 +63,10 @@ describe "bundle show" do it "prints summary of gems" do bundle "show --verbose" - expect(out).to include(' - This is just a fake gem for testing') - expect(out).to include(' - Ruby based make-like utility.') + expect(out).to include("* actionmailer (2.3.2)") + expect(out).to include("\tSummary: This is just a fake gem for testing") + expect(out).to include("\tHomepage: No website available.") + expect(out).to include("\tStatus: Up to date") end end @@ -122,4 +124,29 @@ describe "bundle show" do expect(err).to be_empty end end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "foo" + G + + bundle "config auto_install 1" + bundle :show + expect(out).to include("Installing foo 1.0") + end + + context "with an invalid regexp for gem name" do + it "does not find the gem" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + invalid_regexp = '[]' + + bundle "show #{invalid_regexp}" + expect(out).to include("Could not find gem '#{invalid_regexp}'.") + end + end end diff --git a/spec/install/deploy_spec.rb b/spec/install/deploy_spec.rb index 25f5c5b5de..26d8abe295 100644 --- a/spec/install/deploy_spec.rb +++ b/spec/install/deploy_spec.rb @@ -20,8 +20,8 @@ describe "install with --deployment or --frozen" do it "works after you try to deploy without a lock" do bundle "install --deployment" - bundle :install, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :install + expect(exitstatus).to eq(0) if exitstatus should_be_installed "rack 1.0" end @@ -42,15 +42,15 @@ describe "install with --deployment or --frozen" do end G bundle :install - bundle "install --deployment --without test", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install --deployment --without test" + expect(exitstatus).to eq(0) if exitstatus end it "works when you bundle exec bundle" do bundle :install bundle "install --deployment" - bundle "exec bundle check", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "exec bundle check" + expect(exitstatus).to eq(0) if exitstatus end it "works when using path gems from the same path and the version is specified" do @@ -62,9 +62,9 @@ describe "install with --deployment or --frozen" do G bundle :install - bundle "install --deployment", :exitstatus => true + bundle "install --deployment" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end it "works when there are credentials in the source URL" do @@ -74,9 +74,9 @@ describe "install with --deployment or --frozen" do gem "rack-obama", ">= 1.0" G - bundle "install --deployment", :exitstatus => true, :artifice => "endpoint_strict_basic_authentication" + bundle "install --deployment", :artifice => "endpoint_strict_basic_authentication" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end it "works with sources given by a block" do @@ -86,9 +86,9 @@ describe "install with --deployment or --frozen" do end G - bundle "install --deployment", :exitstatus => true + bundle "install --deployment" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus should_be_installed "rack 1.0" end @@ -98,13 +98,13 @@ describe "install with --deployment or --frozen" do end it "works with the --deployment flag if you didn't change anything" do - bundle "install --deployment", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install --deployment" + expect(exitstatus).to eq(0) if exitstatus end it "works with the --frozen flag if you didn't change anything" do - bundle "install --frozen", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install --frozen" + expect(exitstatus).to eq(0) if exitstatus end it "explodes with the --deployment flag if you make a change and don't check in the lockfile" do diff --git a/spec/install/gemfile/git_spec.rb b/spec/install/gemfile/git_spec.rb index 5d954c4966..537ae8554f 100644 --- a/spec/install/gemfile/git_spec.rb +++ b/spec/install/gemfile/git_spec.rb @@ -297,7 +297,7 @@ describe "bundle install with git sources" do bundle %|config local.rack #{lib_path('local-rack')}| bundle %|config disable_local_branch_check true| bundle :install - expect(out).to match(/Your bundle is complete!/) + expect(out).to match(/Bundle complete!/) end it "explodes on different branches on install" do @@ -655,8 +655,8 @@ describe "bundle install with git sources" do G bundle "install" - bundle "install", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install" + expect(exitstatus).to eq(0) if exitstatus end it "does not duplicate git gem sources" do @@ -759,8 +759,8 @@ describe "bundle install with git sources" do simulate_new_machine - bundle "install --deployment", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install --deployment" + expect(exitstatus).to eq(0) if exitstatus end end @@ -921,14 +921,14 @@ describe "bundle install with git sources" do ENV['GIT_DIR'] = 'bar' ENV['GIT_WORK_TREE'] = 'bar' - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" git "#{lib_path('xxxxxx-1.0')}" do gem 'xxxxxx' end G - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus expect(ENV['GIT_DIR']).to eq('bar') expect(ENV['GIT_WORK_TREE']).to eq('bar') end @@ -959,9 +959,9 @@ describe "bundle install with git sources" do bundle "package --all" simulate_new_machine - bundle "install", :env => {"PATH" => ""}, :exitstatus => true + bundle "install", :env => {"PATH" => ""} expect(out).to_not include("You need to install git to be able to use gems from git repositories.") - expect(exitstatus).to be_zero + expect(exitstatus).to be_zero if exitstatus end end end diff --git a/spec/install/gemfile/path_spec.rb b/spec/install/gemfile/path_spec.rb index f88b6aa2a3..07449201c2 100644 --- a/spec/install/gemfile/path_spec.rb +++ b/spec/install/gemfile/path_spec.rb @@ -78,8 +78,8 @@ describe "bundle install with explicit source paths" do gem 'foo', :path => File.expand_path("../foo-1.0", __FILE__) G - bundle "install --frozen", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install --frozen" + expect(exitstatus).to eq(0) if exitstatus end it "installs dependencies from the path even if a newer gem is available elsewhere" do @@ -199,11 +199,11 @@ describe "bundle install with explicit source paths" do s.write "bar.gemspec" end - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G gemspec :path => "#{lib_path("foo")}" G - expect(exitstatus).to eq(15) + expect(exitstatus).to eq(15) if exitstatus expect(out).to match(/There are multiple gemspecs/) end @@ -212,7 +212,7 @@ describe "bundle install with explicit source paths" do s.write "bar.gemspec" end - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G gemspec :path => "#{lib_path("foo")}", :name => "foo" G diff --git a/spec/install/gemfile_spec.rb b/spec/install/gemfile_spec.rb index 267a7f55db..bfe239396a 100644 --- a/spec/install/gemfile_spec.rb +++ b/spec/install/gemfile_spec.rb @@ -26,6 +26,32 @@ describe "bundle install" do end end + context "with gemfile set via config" do + before do + gemfile bundled_app("NotGemfile"), <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + bundle "config --local gemfile #{bundled_app("NotGemfile")}" + end + it "uses the gemfile to install" do + bundle "install" + bundle "show" + + expect(out).to include("rack (1.0.0)") + end + it "uses the gemfile while in a subdirectory" do + bundled_app("subdir").mkpath + Dir.chdir(bundled_app("subdir")) do + bundle "install" + bundle "show" + + expect(out).to include("rack (1.0.0)") + end + end + end + context "with deprecated features" do before :each do in_app_root diff --git a/spec/install/gems/dependency_api_spec.rb b/spec/install/gems/dependency_api_spec.rb index e17612a2d9..7f20039e62 100644 --- a/spec/install/gems/dependency_api_spec.rb +++ b/spec/install/gems/dependency_api_spec.rb @@ -1,7 +1,8 @@ require "spec_helper" describe "gemcutter's dependency API" do - let(:source_uri) { "http://localgemserver.test" } + let(:source_hostname) { "localgemserver.test" } + let(:source_uri) { "http://#{source_hostname}" } it "should use the API" do gemfile <<-G @@ -21,7 +22,7 @@ describe "gemcutter's dependency API" do G bundle :install, :artifice => "endpoint" - expect(out).to include("Could not find gem ' sinatra") + expect(out).to include("' sinatra' is not a valid gem name because it contains whitespace.") end it "should handle nested dependencies" do @@ -107,9 +108,9 @@ describe "gemcutter's dependency API" do G bundle "install", :artifice => "endpoint" - bundle "install --deployment", :artifice => "endpoint", :exitstatus => true + bundle "install --deployment", :artifice => "endpoint" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus should_be_installed("foo 1.0") end @@ -532,7 +533,16 @@ describe "gemcutter's dependency API" do G end - it "reads authentication details from bundle config" do + it "reads authentication details by host name from bundle config" do + bundle "config #{source_hostname} #{user}:#{password}" + + bundle :install, :artifice => "endpoint_strict_basic_authentication" + + expect(out).to include("Fetching gem metadata from #{source_uri}") + should_be_installed "rack 1.0.0" + end + + it "reads authentication details by full url from bundle config" do # The trailing slash is necessary here; Fetcher canonicalizes the URI. bundle "config #{source_uri}/ #{user}:#{password}" @@ -542,13 +552,20 @@ describe "gemcutter's dependency API" do should_be_installed "rack 1.0.0" end + it "should use the API" do + bundle "config #{source_hostname} #{user}:#{password}" + bundle :install, :artifice => "endpoint_strict_basic_authentication" + expect(out).to include("Fetching gem metadata from #{source_uri}") + should_be_installed "rack 1.0.0" + end + it "prefers auth supplied in the source uri" do gemfile <<-G source "#{basic_auth_source_uri}" gem "rack" G - bundle "config #{source_uri}/ otheruser:wrong" + bundle "config #{source_hostname} otheruser:wrong" bundle :install, :artifice => "endpoint_strict_basic_authentication" should_be_installed "rack 1.0.0" @@ -556,11 +573,11 @@ describe "gemcutter's dependency API" do it "shows instructions if auth is not provided for the source" do bundle :install, :artifice => "endpoint_strict_basic_authentication" - expect(out).to include("bundle config #{source_uri}/ username:password") + expect(out).to include("bundle config #{source_hostname} username:password") end it "fails if authentication has already been provided, but failed" do - bundle "config #{source_uri}/ #{user}:wrong" + bundle "config #{source_hostname} #{user}:wrong" bundle :install, :artifice => "endpoint_strict_basic_authentication" expect(out).to include("Bad username or password") @@ -643,9 +660,9 @@ describe "gemcutter's dependency API" do gem 'rack' G - bundle "install", :exitstatus => true, :artifice => "endpoint_marshal_fail" + bundle "install", :artifice => "endpoint_marshal_fail" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end end diff --git a/spec/install/gems/groups_spec.rb b/spec/install/gems/groups_spec.rb index afce362ad8..23af4aee65 100644 --- a/spec/install/gems/groups_spec.rb +++ b/spec/install/gems/groups_spec.rb @@ -41,14 +41,14 @@ describe "bundle install with groups" do end it "sets up everything if Bundler.setup is used with no groups" do - out = run("require 'rack'; puts RACK") - expect(out).to eq('1.0.0') + output = run("require 'rack'; puts RACK") + expect(output).to eq('1.0.0') - out = run("require 'activesupport'; puts ACTIVESUPPORT") - expect(out).to eq('2.3.5') + output = run("require 'activesupport'; puts ACTIVESUPPORT") + expect(output).to eq('2.3.5') - out = run("require 'thin'; puts THIN") - expect(out).to eq('1.0') + output = run("require 'thin'; puts THIN") + expect(output).to eq('1.0') end it "removes old groups when new groups are set up" do @@ -62,12 +62,12 @@ describe "bundle install with groups" do end it "sets up old groups when they have previously been removed" do - out = run <<-RUBY, :emo + output = run <<-RUBY, :emo Bundler.setup(:default) Bundler.setup(:default, :emo) require 'thin'; puts THIN RUBY - expect(out).to eq('1.0') + expect(output).to eq('1.0') end end diff --git a/spec/install/gems/platform_spec.rb b/spec/install/gems/platform_spec.rb index 71603e1036..1d52858011 100644 --- a/spec/install/gems/platform_spec.rb +++ b/spec/install/gems/platform_spec.rb @@ -173,10 +173,36 @@ describe "bundle install with platform conditionals" do end G - bundle :show, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :show + expect(exitstatus).to eq(0) if exitstatus end + it "does not attempt to install gems from :rbx when using --local" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + + gemfile <<-G + source "file://#{gem_repo1}" + gem "some_gem", platform: :rbx + G + + bundle "install --local" + expect(out).not_to match(/Could not find gem 'some_gem/) + end + + it "does not attempt to install gems from other rubies when using --local" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + other_ruby_version_tag = RUBY_VERSION =~ /^1\.8/ ? :ruby_19 : :ruby_18 + + gemfile <<-G + source "file://#{gem_repo1}" + gem "some_gem", platform: :#{other_ruby_version_tag} + G + + bundle "install --local" + expect(out).not_to match(/Could not find gem 'some_gem/) + end end describe "when a gem has no architecture" do diff --git a/spec/install/gems/simple_case_spec.rb b/spec/install/gems/simple_case_spec.rb index aac70db6cf..759de0884f 100644 --- a/spec/install/gems/simple_case_spec.rb +++ b/spec/install/gems/simple_case_spec.rb @@ -16,7 +16,8 @@ describe "bundle install with gem sources" do raise StandardError, "FAIL" G - expect(err).to match(/FAIL \(StandardError\)/) + expect(err).to eq "" + expect(out).to match(/StandardError: FAIL/) expect(bundled_app("Gemfile.lock")).not_to exist end @@ -95,11 +96,11 @@ describe "bundle install with gem sources" do end it "raises an appropriate error when gems are specified using symbols" do - status = install_gemfile(<<-G, :exitstatus => true) + install_gemfile(<<-G) source "file://#{gem_repo1}" gem :rack G - expect(status).to eq(4) + expect(exitstatus).to eq(4) if exitstatus end it "pulls in dependencies" do @@ -318,24 +319,24 @@ describe "bundle install with gem sources" do FileUtils.mkdir_p(bundled_app(".bundle")) FileUtils.touch(bundled_app(".bundle/config")) - install_gemfile(<<-G, :exitstatus => true) + install_gemfile(<<-G) source "file://#{gem_repo1}" gem 'foo' G - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end it "doesn't blow up when the global .bundle/config is empty" do FileUtils.mkdir_p("#{Bundler.rubygems.user_home}/.bundle") FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config") - install_gemfile(<<-G, :exitstatus => true) + install_gemfile(<<-G) source "file://#{gem_repo1}" gem 'foo' G - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end end @@ -356,9 +357,9 @@ describe "bundle install with gem sources" do file.puts gemfile end - bundle :install, :exitstatus => true + bundle :install - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end end diff --git a/spec/install/gems/sources_spec.rb b/spec/install/gems/sources_spec.rb index 86d23c4ed2..731f1402e5 100644 --- a/spec/install/gems/sources_spec.rb +++ b/spec/install/gems/sources_spec.rb @@ -15,7 +15,7 @@ describe "bundle install with gems on multiple sources" do end end - context "when the same version of the same gem is in multiple sources" do + context "with multiple toplevel sources" do let(:repo3_rack_version) { "1.0.0" } before do @@ -30,10 +30,18 @@ describe "bundle install with gems on multiple sources" do it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first" do bundle :install + expect(out).to include("Warning: this Gemfile contains multiple primary sources.") expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") expect(out).to include("Installed from: file:#{gem_repo1}") should_be_installed("rack-obama 1.0.0", "rack 1.0.0") end + + it "errors when disable_multisource is set" do + bundle "config disable_multisource true" + bundle :install + expect(out).to include("Each source after the first must include a block") + expect(exitstatus).to eq(14) if exitstatus + end end context "when different versions of the same gem are in multiple sources" do @@ -51,6 +59,7 @@ describe "bundle install with gems on multiple sources" do it "warns about ambiguous gems, but installs anyway" do bundle :install + expect(out).to include("Warning: this Gemfile contains multiple primary sources.") expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") expect(out).to include("Installed from: file:#{gem_repo1}") should_be_installed("rack-obama 1.0.0", "rack 1.0.0") @@ -91,9 +100,9 @@ describe "bundle install with gems on multiple sources" do expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist - bundle "install --deployment", :exitstatus => true + bundle "install --deployment" - expect(exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus should_be_installed("rack-obama 1.0.0", "rack 1.0.0") end end @@ -214,6 +223,7 @@ describe "bundle install with gems on multiple sources" do it "installs from the other source and warns about ambiguous gems" do bundle :install + expect(out).to include("Warning: this Gemfile contains multiple primary sources.") expect(out).to include("Warning: the gem 'rack' was found in multiple sources.") expect(out).to include("Installed from: file:#{gem_repo2}") should_be_installed("depends_on_rack 1.0.1", "rack 1.0.0") @@ -305,6 +315,26 @@ describe "bundle install with gems on multiple sources" do should_be_installed("rack 0.9.1") end end + + context "with a path gem in the same Gemfile" do + before do + build_lib "foo" + + gemfile <<-G + gem "rack", :source => "file://#{gem_repo1}" + gem "foo", :path => "#{lib_path('foo-1.0')}" + G + end + + it "does not unlock the non-path gem after install" do + bundle :install + + bundle %{exec ruby -e 'puts "OK"'} + + expect(out).to include("OK") + expect(exitstatus).to eq(0) if exitstatus + end + end end context "when an older version of the same gem also ships with Ruby" do diff --git a/spec/install/gems/standalone_spec.rb b/spec/install/gems/standalone_spec.rb index c666120ad4..08a20a2a23 100644 --- a/spec/install/gems/standalone_spec.rb +++ b/spec/install/gems/standalone_spec.rb @@ -186,9 +186,9 @@ describe "bundle install --standalone" do end it "should run without errors" do - bundle "install --standalone", :artifice => "endpoint", :exitstatus => true + bundle "install --standalone", :artifice => "endpoint" - expect(@exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end it "still makes the gems available to normal bundler" do diff --git a/spec/install/gems/win32_spec.rb b/spec/install/gems/win32_spec.rb index 6224220281..6d1ac85675 100644 --- a/spec/install/gems/win32_spec.rb +++ b/spec/install/gems/win32_spec.rb @@ -16,11 +16,11 @@ describe "bundle install with win32-generated lockfile" do f << " rack\r\n" end - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" gem "rack" G - expect(@exitstatus).to eq(0) + expect(exitstatus).to eq(0) if exitstatus end end diff --git a/spec/install/path_spec.rb b/spec/install/path_spec.rb index 2ddceb23d5..6c5e7e50ab 100644 --- a/spec/install/path_spec.rb +++ b/spec/install/path_spec.rb @@ -33,7 +33,7 @@ describe "bundle install" do it "prints a warning to let the user know what has happened with bundle --path vendor/bundle" do bundle "install --path vendor/bundle" - expect(out).to include("It was installed into ./vendor") + expect(out).to include("gems are installed into ./vendor") end it "disallows --path vendor/bundle --system" do diff --git a/spec/install/post_bundle_message_spec.rb b/spec/install/post_bundle_message_spec.rb index 6bd4e8575d..78133d9283 100644 --- a/spec/install/post_bundle_message_spec.rb +++ b/spec/install/post_bundle_message_spec.rb @@ -14,9 +14,10 @@ describe "post bundle message" do end let(:bundle_show_message) {"Use `bundle show [gemname]` to see where a bundled gem is installed."} - let(:bundle_deployment_message) {"It was installed into ./vendor"} - let(:bundle_complete_message) {"Your bundle is complete!"} - let(:bundle_updated_message) {"Your bundle is updated!"} + let(:bundle_deployment_message) {"Bundled gems are installed into ./vendor"} + let(:bundle_complete_message) {"Bundle complete!"} + let(:bundle_updated_message) {"Bundle updated!"} + let(:installed_gems_stats) {"4 Gemfile dependencies, 5 gems now installed."} describe "for fresh bundle install" do it "without any options" do @@ -24,6 +25,7 @@ describe "post bundle message" do expect(out).to include(bundle_show_message) expect(out).not_to include("Gems in the group") expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) end it "with --without one group" do @@ -31,6 +33,7 @@ describe "post bundle message" do expect(out).to include(bundle_show_message) expect(out).to include("Gems in the group emo were not installed") expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) end it "with --without two groups" do @@ -38,6 +41,7 @@ describe "post bundle message" do expect(out).to include(bundle_show_message) expect(out).to include("Gems in the groups emo and test were not installed") expect(out).to include(bundle_complete_message) + expect(out).to include("4 Gemfile dependencies, 3 gems now installed.") end it "with --without more groups" do @@ -45,6 +49,7 @@ describe "post bundle message" do expect(out).to include(bundle_show_message) expect(out).to include("Gems in the groups emo, obama and test were not installed") expect(out).to include(bundle_complete_message) + expect(out).to include("4 Gemfile dependencies, 2 gems now installed.") end describe "with --path and" do @@ -84,6 +89,7 @@ describe "post bundle message" do expect(out).to include(bundle_show_message) expect(out).to_not include("Gems in the groups") expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) end it "with --without one group" do @@ -92,6 +98,7 @@ describe "post bundle message" do expect(out).to include(bundle_show_message) expect(out).to include("Gems in the group emo were not installed") expect(out).to include(bundle_complete_message) + expect(out).to include(installed_gems_stats) end it "with --without two groups" do diff --git a/spec/install/security_policy_spec.rb b/spec/install/security_policy_spec.rb index 941e14de27..1f48cb9e5a 100644 --- a/spec/install/security_policy_spec.rb +++ b/spec/install/security_policy_spec.rb @@ -16,8 +16,8 @@ describe "policies with unsigned gems" do it "will work after you try to deploy without a lock" do bundle "install --deployment" - bundle :install, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :install + expect(exitstatus).to eq(0) if exitstatus should_be_installed "rack 1.0", "signed_gem 1.0" end @@ -38,8 +38,8 @@ describe "policies with unsigned gems" do end it "will succeed with no policy" do - bundle "install", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install" + expect(exitstatus).to eq(0) if exitstatus end end @@ -64,14 +64,14 @@ describe "policies with signed gems and no CA" do end it "will succeed with Low Security setting, low security accepts self signed gem" do - bundle "install --trust-policy=LowSecurity", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install --trust-policy=LowSecurity" + expect(exitstatus).to eq(0) if exitstatus should_be_installed "signed_gem 1.0" end it "will succeed with no policy" do - bundle "install", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "install" + expect(exitstatus).to eq(0) if exitstatus should_be_installed "signed_gem 1.0" end end diff --git a/spec/lock/lockfile_spec.rb b/spec/lock/lockfile_spec.rb index 588d64308c..364b97181a 100644 --- a/spec/lock/lockfile_spec.rb +++ b/spec/lock/lockfile_spec.rb @@ -75,6 +75,7 @@ describe "the lockfile format" do install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true) source "http://localgemserver.test/" + source "http://user:pass@othergemserver.test/" gem "rack-obama", ">= 1.0" G @@ -82,6 +83,7 @@ describe "the lockfile format" do lockfile_should_be <<-G GEM remote: http://localgemserver.test/ + remote: http://user:pass@othergemserver.test/ specs: rack (1.0.0) rack-obama (1.0) diff --git a/spec/other/bundle_ruby_spec.rb b/spec/other/bundle_ruby_spec.rb index 08d24ee71a..739c3d0ffb 100644 --- a/spec/other/bundle_ruby_spec.rb +++ b/spec/other/bundle_ruby_spec.rb @@ -63,7 +63,7 @@ describe "bundle_ruby" do G bundle_ruby :exitstatus => true - expect(exitstatus).not_to eq(0) + expect(exitstatus).not_to eq(0) if exitstatus bundle_ruby expect(out).to eq("Please define :engine_version") @@ -78,7 +78,7 @@ describe "bundle_ruby" do G bundle_ruby :exitstatus => true - expect(exitstatus).not_to eq(0) + expect(exitstatus).not_to eq(0) if exitstatus bundle_ruby expect(out).to eq("Please define :engine") @@ -93,7 +93,7 @@ describe "bundle_ruby" do G bundle_ruby :exitstatus => true - expect(exitstatus).not_to eq(0) + expect(exitstatus).not_to eq(0) if exitstatus bundle_ruby expect(out).to eq("ruby_version must match the :engine_version for MRI") diff --git a/spec/other/platform_spec.rb b/spec/other/platform_spec.rb index 619b5c7fb3..13b57c135e 100644 --- a/spec/other/platform_spec.rb +++ b/spec/other/platform_spec.rb @@ -151,9 +151,9 @@ G gem "foo" G - bundle "platform", :exitstatus => true + bundle "platform" - expect(exitstatus).not_to eq(0) + expect(exitstatus).not_to eq(0) if exitstatus end it "raises an error if engine_version is used but engine is not" do @@ -164,9 +164,9 @@ G gem "foo" G - bundle "platform", :exitstatus => true + bundle "platform" - expect(exitstatus).not_to eq(0) + expect(exitstatus).not_to eq(0) if exitstatus end it "raises an error if engine version doesn't match ruby version for MRI" do @@ -177,9 +177,9 @@ G gem "foo" G - bundle "platform", :exitstatus => true + bundle "platform" - expect(exitstatus).not_to eq(0) + expect(exitstatus).not_to eq(0) if exitstatus end it "should print if no ruby version is specified" do @@ -205,30 +205,28 @@ G let(:patchlevel_incorrect) { "#{ruby_version_correct}, :patchlevel => '#{not_local_patchlevel}'" } let(:patchlevel_fixnum) { "#{ruby_version_correct}, :patchlevel => #{RUBY_PATCHLEVEL}1" } - def should_be_ruby_version_incorrect(opts = {:exitstatus => true}) - expect(exitstatus).to eq(18) if opts[:exitstatus] + def should_be_ruby_version_incorrect + expect(exitstatus).to eq(18) if exitstatus expect(out).to be_include("Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version}") end - def should_be_engine_incorrect(opts = {:exitstatus => true}) - expect(exitstatus).to eq(18) if opts[:exitstatus] + def should_be_engine_incorrect + expect(exitstatus).to eq(18) if exitstatus expect(out).to be_include("Your Ruby engine is #{local_ruby_engine}, but your Gemfile specified #{not_local_tag}") end - def should_be_engine_version_incorrect(opts = {:exitstatus => true}) - expect(exitstatus).to eq(18) if opts[:exitstatus] + def should_be_engine_version_incorrect + expect(exitstatus).to eq(18) if exitstatus expect(out).to be_include("Your #{local_ruby_engine} version is #{local_engine_version}, but your Gemfile specified #{local_ruby_engine} #{not_local_engine_version}") end - def should_be_patchlevel_incorrect(opts = {:exitstatus => true}) - expect(exitstatus).to eq(18) if opts[:exitstatus] - + def should_be_patchlevel_incorrect + expect(exitstatus).to eq(18) if exitstatus expect(out).to be_include("Your Ruby patchlevel is #{RUBY_PATCHLEVEL}, but your Gemfile specified #{not_local_patchlevel}") end - def should_be_patchlevel_fixnum(opts = {:exitstatus => true}) - expect(exitstatus).to eq(18) if opts[:exitstatus] - + def should_be_patchlevel_fixnum + expect(exitstatus).to eq(18) if exitstatus expect(out).to be_include("The Ruby patchlevel in your Gemfile must be a string") end @@ -269,7 +267,7 @@ G end it "doesn't install when the ruby version doesn't match" do - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" gem "rack" @@ -281,7 +279,7 @@ G end it "doesn't install when engine doesn't match" do - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" gem "rack" @@ -294,7 +292,7 @@ G it "doesn't install when engine version doesn't match" do simulate_ruby_engine "jruby" do - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" gem "rack" @@ -307,7 +305,7 @@ G end it "doesn't install when patchlevel doesn't match" do - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" gem "rack" @@ -333,8 +331,8 @@ G #{ruby_version_correct} G - bundle :check, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :check + expect(exitstatus).to eq(0) if exitstatus expect(out).to eq("The Gemfile's dependencies are satisfied") end @@ -352,8 +350,8 @@ G #{ruby_version_correct_engineless} G - bundle :check, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :check + expect(exitstatus).to eq(0) if exitstatus expect(out).to eq("The Gemfile's dependencies are satisfied") end end @@ -371,7 +369,7 @@ G #{ruby_version_incorrect} G - bundle :check, :exitstatus => true + bundle :check should_be_ruby_version_incorrect end @@ -388,7 +386,7 @@ G #{engine_incorrect} G - bundle :check, :exitstatus => true + bundle :check should_be_engine_incorrect end @@ -406,13 +404,13 @@ G #{engine_version_incorrect} G - bundle :check, :exitstatus => true + bundle :check should_be_engine_version_incorrect end end it "fails when patchlevel doesn't match" do - install_gemfile <<-G, :exitstatus => true + install_gemfile <<-G source "file://#{gem_repo1}" gem "rack" G @@ -424,7 +422,7 @@ G #{patchlevel_incorrect} G - bundle :check, :exitstatus => true + bundle :check should_be_patchlevel_incorrect end end @@ -486,7 +484,7 @@ G build_gem "activesupport", "3.0" end - bundle :update, :exitstatus => true + bundle :update should_be_ruby_version_incorrect end @@ -502,7 +500,7 @@ G build_gem "activesupport", "3.0" end - bundle :update, :exitstatus => true + bundle :update should_be_engine_incorrect end @@ -519,13 +517,13 @@ G build_gem "activesupport", "3.0" end - bundle :update, :exitstatus => true + bundle :update should_be_engine_version_incorrect end end it "fails when patchlevel doesn't match" do - gemfile <<-G, :exitstatus => true + gemfile <<-G source "file://#{gem_repo1}" gem "rack" @@ -535,7 +533,7 @@ G build_gem "activesupport", "3.0" end - bundle :update, :exitstatus => true + bundle :update should_be_patchlevel_incorrect end end @@ -582,7 +580,7 @@ G #{ruby_version_incorrect} G - bundle "show rails", :exitstatus => true + bundle "show rails" should_be_ruby_version_incorrect end @@ -594,7 +592,7 @@ G #{engine_incorrect} G - bundle "show rails", :exitstatus => true + bundle "show rails" should_be_engine_incorrect end @@ -607,13 +605,13 @@ G #{engine_version_incorrect} G - bundle "show rails", :exitstatus => true + bundle "show rails" should_be_engine_version_incorrect end end it "fails when patchlevel doesn't match" do - gemfile <<-G, :exitstatus => true + gemfile <<-G source "file://#{gem_repo1}" gem "rack" @@ -623,7 +621,7 @@ G build_gem "activesupport", "3.0" end - bundle "show rails", :exitstatus => true + bundle "show rails" should_be_patchlevel_incorrect end end @@ -668,7 +666,7 @@ G #{ruby_version_incorrect} G - bundle :cache, :exitstatus => true + bundle :cache should_be_ruby_version_incorrect end @@ -679,7 +677,7 @@ G #{engine_incorrect} G - bundle :cache, :exitstatus => true + bundle :cache should_be_engine_incorrect end @@ -691,20 +689,20 @@ G #{engine_version_incorrect} G - bundle :cache, :exitstatus => true + bundle :cache should_be_engine_version_incorrect end end it "fails when patchlevel doesn't match" do - gemfile <<-G, :exitstatus => true + gemfile <<-G source "file://#{gem_repo1}" gem "rack" #{patchlevel_incorrect} G - bundle :cache, :exitstatus => true + bundle :cache should_be_patchlevel_incorrect end end @@ -749,7 +747,7 @@ G #{ruby_version_incorrect} G - bundle :pack, :exitstatus => true + bundle :pack should_be_ruby_version_incorrect end @@ -760,7 +758,7 @@ G #{engine_incorrect} G - bundle :pack, :exitstatus => true + bundle :pack should_be_engine_incorrect end @@ -772,26 +770,27 @@ G #{engine_version_incorrect} G - bundle :pack, :exitstatus => true + bundle :pack should_be_engine_version_incorrect end end it "fails when patchlevel doesn't match" do - gemfile <<-G, :exitstatus => true + gemfile <<-G source "file://#{gem_repo1}" gem "rack" #{patchlevel_incorrect} G - bundle :pack, :exitstatus => true + bundle :pack should_be_patchlevel_incorrect end end context "bundle exec" do before do + ENV["BUNDLER_FORCE_TTY"] = "true" system_gems "rack-1.0.0", "rack-0.9.1" end @@ -826,7 +825,7 @@ G #{ruby_version_incorrect} G - bundle "exec rackup", :exitstatus => true + bundle "exec rackup" should_be_ruby_version_incorrect end @@ -837,32 +836,32 @@ G #{engine_incorrect} G - bundle "exec rackup", :exitstatus => true + bundle "exec rackup" should_be_engine_incorrect end - it "fails when the engine version doesn't match" do - simulate_ruby_engine "jruby" do - gemfile <<-G - gem "rack", "0.9.1" - - #{engine_version_incorrect} - G - - bundle "exec rackup", :exitstatus => true - should_be_engine_version_incorrect - end - end + # it "fails when the engine version doesn't match" do + # simulate_ruby_engine "jruby" do + # gemfile <<-G + # gem "rack", "0.9.1" + # + # #{engine_version_incorrect} + # G + # + # bundle "exec rackup" + # should_be_engine_version_incorrect + # end + # end it "fails when patchlevel doesn't match" do - gemfile <<-G, :exitstatus => true + gemfile <<-G source "file://#{gem_repo1}" gem "rack" #{patchlevel_incorrect} G - bundle "exec rackup", :exitstatus => true + bundle "exec rackup" should_be_patchlevel_incorrect end end @@ -923,7 +922,7 @@ G #{ruby_version_incorrect} G - bundle "console", :exitstatus => true + bundle "console" should_be_ruby_version_incorrect end @@ -937,7 +936,7 @@ G #{engine_incorrect} G - bundle "console", :exitstatus => true + bundle "console" should_be_engine_incorrect end @@ -952,13 +951,13 @@ G #{engine_version_incorrect} G - bundle "console", :exitstatus => true + bundle "console" should_be_engine_version_incorrect end end it "fails when patchlevel doesn't match" do - gemfile <<-G, :exitstatus => true + gemfile <<-G source "file://#{gem_repo1}" gem "rack" gem "activesupport", :group => :test @@ -967,7 +966,7 @@ G #{patchlevel_incorrect} G - bundle "console", :exitstatus => true + bundle "console" should_be_patchlevel_incorrect end end @@ -979,6 +978,8 @@ G gem "yard" gem "rack", :group => :test G + + ENV['BUNDLER_FORCE_TTY'] = "true" end it "makes a Gemfile.lock if setup succeeds" do @@ -990,8 +991,6 @@ G #{ruby_version_correct} G - File.read(bundled_app("Gemfile.lock")) - FileUtils.rm(bundled_app("Gemfile.lock")) run "1" @@ -1008,8 +1007,6 @@ G #{ruby_version_correct_engineless} G - File.read(bundled_app("Gemfile.lock")) - FileUtils.rm(bundled_app("Gemfile.lock")) run "1" @@ -1026,23 +1023,15 @@ G #{ruby_version_incorrect} G - File.read(bundled_app("Gemfile.lock")) - FileUtils.rm(bundled_app("Gemfile.lock")) ruby <<-R require 'rubygems' - require 'bundler' - - begin - Bundler.setup - rescue Bundler::RubyVersionMismatch => e - puts e.message - end + require 'bundler/setup' R expect(bundled_app("Gemfile.lock")).not_to exist - should_be_ruby_version_incorrect(:exitstatus => false) + should_be_ruby_version_incorrect end it "fails when engine doesn't match" do @@ -1054,23 +1043,15 @@ G #{engine_incorrect} G - File.read(bundled_app("Gemfile.lock")) - FileUtils.rm(bundled_app("Gemfile.lock")) ruby <<-R require 'rubygems' - require 'bundler' - - begin - Bundler.setup - rescue Bundler::RubyVersionMismatch => e - puts e.message - end + require 'bundler/setup' R expect(bundled_app("Gemfile.lock")).not_to exist - should_be_engine_incorrect(:exitstatus => false) + should_be_engine_incorrect end it "fails when engine version doesn't match" do @@ -1083,23 +1064,15 @@ G #{engine_version_incorrect} G - File.read(bundled_app("Gemfile.lock")) - FileUtils.rm(bundled_app("Gemfile.lock")) ruby <<-R require 'rubygems' - require 'bundler' - - begin - Bundler.setup - rescue Bundler::RubyVersionMismatch => e - puts e.message - end + require 'bundler/setup' R expect(bundled_app("Gemfile.lock")).not_to exist - should_be_engine_version_incorrect(:exitstatus => false) + should_be_engine_version_incorrect end end @@ -1112,24 +1085,15 @@ G #{patchlevel_incorrect} G - puts File.read(bundled_app("Gemfile")) - File.read(bundled_app("Gemfile.lock")) - FileUtils.rm(bundled_app("Gemfile.lock")) ruby <<-R require 'rubygems' - require 'bundler' - - begin - Bundler.setup - rescue Bundler::RubyVersionMismatch => e - puts e.message - end + require 'bundler/setup' R expect(bundled_app("Gemfile.lock")).not_to exist - should_be_patchlevel_incorrect(:exitstatus => false) + should_be_patchlevel_incorrect end end @@ -1200,7 +1164,7 @@ G #{ruby_version_incorrect} G - bundle "outdated", :exitstatus => true + bundle "outdated" should_be_ruby_version_incorrect end @@ -1218,7 +1182,7 @@ G #{engine_incorrect} G - bundle "outdated", :exitstatus => true + bundle "outdated" should_be_engine_incorrect end @@ -1237,7 +1201,7 @@ G #{engine_version_incorrect} G - bundle "outdated", :exitstatus => true + bundle "outdated" should_be_engine_version_incorrect end end @@ -1257,7 +1221,7 @@ G #{patchlevel_incorrect} G - bundle "outdated", :exitstatus => true + bundle "outdated" should_be_patchlevel_incorrect end end @@ -1277,7 +1241,7 @@ G #{patchlevel_fixnum} G - bundle "outdated", :exitstatus => true + bundle "outdated" should_be_patchlevel_fixnum end end diff --git a/spec/quality_spec.rb b/spec/quality_spec.rb index 59a8520066..de3bdc6a57 100644 --- a/spec/quality_spec.rb +++ b/spec/quality_spec.rb @@ -1,14 +1,15 @@ require "spec_helper" -if defined?(Encoding) && Encoding.default_external != "UTF-8" - Encoding.default_external = "UTF-8" +if defined?(Encoding) && Encoding.default_external.name != "UTF-8" + # Poor man's ruby -E UTF-8, since it works on 1.8.7 + Encoding.default_external = Encoding.find("UTF-8") end describe "The library itself" do def check_for_spec_defs_with_single_quotes(filename) failing_lines = [] - File.readlines(filename).each_with_index do |line,number| + File.readlines(filename).each_with_index do |line, number| failing_lines << number + 1 if line =~ /^ *(describe|it|context) {1}'{1}/ end @@ -19,7 +20,7 @@ describe "The library itself" do def check_for_tab_characters(filename) failing_lines = [] - File.readlines(filename).each_with_index do |line,number| + File.readlines(filename).each_with_index do |line, number| failing_lines << number + 1 if line =~ /\t/ end @@ -30,7 +31,7 @@ describe "The library itself" do def check_for_extra_spaces(filename) failing_lines = [] - File.readlines(filename).each_with_index do |line,number| + File.readlines(filename).each_with_index do |line, number| next if line =~ /^\s+#.*\s+\n$/ next if %w(LICENCE.md).include?(line) failing_lines << number + 1 if line =~ /\s+\n$/ @@ -85,4 +86,18 @@ describe "The library itself" do system("rm bundler-#{Bundler::VERSION}.gem") end end + + it "does not contain any warnings" do + Dir.chdir(root.join("lib")) + exclusions = /bundler\/capistrano\.rb|bundler\/vlad\.rb|bundler\/gem_tasks\.rb|tmp\/rubygems/ + lib_files = `git ls-files -z -- **/*.rb`.split("\x0").reject{|f| f =~ exclusions } + sys_exec("ruby -w -I. ", :expect_err) do |input| + lib_files.each do |f| + input.puts "require '#{f.gsub(/\.rb$/, '')}'" + end + end + + expect(@err).to eq("") + expect(@out).to eq("") + end end diff --git a/spec/realworld/edgecases_spec.rb b/spec/realworld/edgecases_spec.rb index 38f8eaae72..15c7697564 100644 --- a/spec/realworld/edgecases_spec.rb +++ b/spec/realworld/edgecases_spec.rb @@ -206,7 +206,7 @@ describe "real world edgecases", :realworld => true do activesupport! L - bundle :install, :exitstatus => true - expect(exitstatus).to eq(0) + bundle :install + expect(exitstatus).to eq(0) if exitstatus end end diff --git a/spec/realworld/parallel_spec.rb b/spec/realworld/parallel_spec.rb index 9f65dbb705..d447786158 100644 --- a/spec/realworld/parallel_spec.rb +++ b/spec/realworld/parallel_spec.rb @@ -10,7 +10,12 @@ describe "parallel", :realworld => true do G bundle :install, :jobs => 4, :env => {"DEBUG" => "1"} - expect(out).to match(/[1-3]: /) + + if Bundler.rubygems.provides?(">= 2.1.0") + expect(out).to match(/[1-3]: /) + else + expect(out).to include("is not threadsafe") + end bundle "show activesupport" expect(out).to match(/activesupport/) @@ -30,7 +35,12 @@ describe "parallel", :realworld => true do G bundle :install, :jobs => 4, :env => {"DEBUG" => "1"} - expect(out).to match(/[1-3]: /) + + if Bundler.rubygems.provides?(">= 2.1.0") + expect(out).to match(/[1-3]: /) + else + expect(out).to include("is not threadsafe") + end bundle "show activesupport" expect(out).to match(/activesupport/) @@ -57,7 +67,12 @@ describe "parallel", :realworld => true do G bundle :update, :jobs => 4, :env => {"DEBUG" => "1"} - expect(out).to match(/[1-3]: /) + + if Bundler.rubygems.provides?(">= 2.1.0") + expect(out).to match(/[1-3]: /) + else + expect(out).to include("is not threadsafe") + end bundle "show activesupport" expect(out).to match(/activesupport-3\.2\.\d+/) @@ -68,4 +83,23 @@ describe "parallel", :realworld => true do bundle "config jobs" expect(out).to match(/: "4"/) end + + it "works with --standalone" do + gemfile <<-G, :standalone => true + source "https://rubygems.org" + gem "diff-lcs" + G + + bundle :install, :standalone => true, :jobs => 4 + + ruby <<-RUBY, :no_lib => true + $:.unshift File.expand_path("bundle") + require "bundler/setup" + + require "diff/lcs" + puts Diff::LCS + RUBY + + expect(out).to eq("Diff::LCS") + end end diff --git a/spec/runtime/load_spec.rb b/spec/runtime/load_spec.rb index cc2cebf8e5..358e3021e8 100644 --- a/spec/runtime/load_spec.rb +++ b/spec/runtime/load_spec.rb @@ -34,7 +34,23 @@ describe "Bundler.load" do Bundler.load }.not_to raise_error() end + end + + describe "with a gems.rb file" do + before(:each) do + create_file "gems.rb", <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + it "provides a list of the env dependencies" do + expect(Bundler.load.dependencies).to have_dep("rack", ">= 0") + end + + it "provides a list of the resolved gems" do + expect(Bundler.load.gems).to have_gem("rack-1.0.0", "bundler-#{Bundler::VERSION}") + end end describe "without a gemfile" do diff --git a/spec/runtime/require_spec.rb b/spec/runtime/require_spec.rb index e1c93897a5..5e5e9db22f 100644 --- a/spec/runtime/require_spec.rb +++ b/spec/runtime/require_spec.rb @@ -98,6 +98,28 @@ describe "Bundler.require" do expect(err).to eq("ZOMG LOAD ERROR") end + it "doesn't swallow the error when the library has an unrelated error" do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")" + end + + gemfile <<-G + path "#{lib_path}" + gem "loadfuuu" + G + + cmd = <<-RUBY + begin + Bundler.require + rescue LoadError => e + $stderr.puts "ZOMG LOAD ERROR: \#{e.message}" + end + RUBY + run(cmd, :expect_err => true) + + expect(err).to eq("ZOMG LOAD ERROR: cannot load such file -- load-bar") + end + describe "with namespaced gems" do before :each do build_lib "jquery-rails", "1.0.0" do |s| @@ -170,8 +192,9 @@ describe "Bundler.require" do it "doesn't swallow the error when the library has an unrelated error" do build_lib "load-fuuu", "1.0.0" do |s| - s.write "lib/load-fuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")" + s.write "lib/load/fuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")" end + lib_path('load-fuuu-1.0.0/lib/load-fuuu.rb').rmtree gemfile <<-G path "#{lib_path}" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e55a72bcf0..d14f3005cd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,12 +1,20 @@ $:.unshift File.expand_path('..', __FILE__) $:.unshift File.expand_path('../../lib', __FILE__) -# stdlib first + +require 'bundler/psyched_yaml' +require 'fileutils' require 'uri' require 'digest/sha1' -require 'fileutils' -require 'bundler/psyched_yaml' -require 'rubygems' -require 'rspec' + +begin + require 'rubygems' + spec = Gem::Specification.load("bundler.gemspec") + gem 'rspec', spec.dependencies.last.requirement.to_s + require 'rspec' +rescue LoadError + abort "Run rake spec:deps to install development dependencies" +end + require 'bundler' # Require the correct version of popen for the current platform @@ -58,29 +66,12 @@ RSpec.configure do |config| config.filter_run_excluding :realworld => true end - if RUBY_VERSION >= "1.9" - config.filter_run_excluding :ruby => "1.8" - else - config.filter_run_excluding :ruby => "1.9" - end - - if RUBY_VERSION >= "2.0" - config.filter_run_excluding :ruby => "1.8" - config.filter_run_excluding :ruby => "1.9" - else - config.filter_run_excluding :ruby => "2.0" - config.filter_run_excluding :ruby => "2.1" - end - - if Gem::VERSION < "2.2" - config.filter_run_excluding :rubygems => "2.2" - end - + config.filter_run_excluding :ruby => LessThanProc.with(RUBY_VERSION) + config.filter_run_excluding :rubygems => LessThanProc.with(Gem::VERSION) config.filter_run_excluding :rubygems_master => (ENV['RGV'] != "master") config.filter_run :focused => true unless ENV['CI'] config.run_all_when_everything_filtered = true - config.alias_example_to :fit, :focused => true original_wd = Dir.pwd original_path = ENV['PATH'] @@ -109,15 +100,15 @@ RSpec.configure do |config| Dir.chdir(original_wd) # Reset ENV - ENV['PATH'] = original_path - ENV['GEM_HOME'] = original_gem_home - ENV['GEM_PATH'] = original_gem_home - ENV['BUNDLE_PATH'] = nil - ENV['BUNDLE_GEMFILE'] = nil - ENV['BUNDLER_TEST'] = nil - ENV['BUNDLE_FROZEN'] = nil + ENV['PATH'] = original_path + ENV['GEM_HOME'] = original_gem_home + ENV['GEM_PATH'] = original_gem_home + ENV['BUNDLE_PATH'] = nil + ENV['BUNDLE_GEMFILE'] = nil + ENV['BUNDLE_FROZEN'] = nil + ENV['BUNDLE_APP_CONFIG'] = nil + ENV['BUNDLER_TEST'] = nil ENV['BUNDLER_SPEC_PLATFORM'] = nil ENV['BUNDLER_SPEC_VERSION'] = nil - ENV['BUNDLE_APP_CONFIG'] = nil end end diff --git a/spec/support/builders.rb b/spec/support/builders.rb index 1b2670b4ab..1a1c916779 100644 --- a/spec/support/builders.rb +++ b/spec/support/builders.rb @@ -636,7 +636,7 @@ module Spec end end - TEST_CERT = <<-CERT.gsub /^\s*/, '' + TEST_CERT = <<-CERT.gsub(/^\s*/, '') -----BEGIN CERTIFICATE----- MIIDMjCCAhqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAnMQwwCgYDVQQDDAN5b3Ux FzAVBgoJkiaJk/IsZAEZFgdleGFtcGxlMB4XDTE1MDIwODAwMTIyM1oXDTQyMDYy @@ -659,7 +659,7 @@ module Spec -----END CERTIFICATE----- CERT - TEST_PKEY = <<-PKEY.gsub /^\s*/, '' + TEST_PKEY = <<-PKEY.gsub(/^\s*/, '') -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA2W8V2k3jdzgMxL0mjTqbRruTdtDcdZDXKtiFkyLvsXUXvc2k GSdgcjMOS1CkafqGz/hAUlPibjM0QEXjtQuMdTmdMrmuORLeeIZhSO+HdkTNV6j3 diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index 7eb2ebd979..e5774f12a3 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -4,10 +4,10 @@ module Spec @in_p, @out_p, @err_p = nil, nil, nil Dir["#{tmp}/{gems/*,*}"].each do |dir| next if %(base remote1 gems rubygems).include?(File.basename(dir)) - unless ENV['BUNDLER_SUDO_TESTS'] - FileUtils.rm_rf(dir) - else + if ENV['BUNDLER_SUDO_TESTS'] `sudo rm -rf #{dir}` + else + FileUtils.rm_rf(dir) end end FileUtils.mkdir_p(tmp) @@ -57,7 +57,6 @@ module Spec def bundle(cmd, options = {}) expect_err = options.delete(:expect_err) - exitstatus = options.delete(:exitstatus) sudo = "sudo" if options.delete(:sudo) options["no-color"] = true unless options.key?("no-color") || %w(exec conf).include?(cmd.to_s[0..3]) @@ -68,23 +67,17 @@ module Spec requires << File.expand_path('../artifice/'+options.delete(:artifice)+'.rb', __FILE__) if options.key?(:artifice) requires_str = requires.map{|r| "-r#{r}"}.join(" ") - env = (options.delete(:env) || {}).map{|k,v| "#{k}='#{v}'"}.join(" ") - args = options.map do |k,v| + env = (options.delete(:env) || {}).map{|k, v| "#{k}='#{v}'"}.join(" ") + args = options.map do |k, v| v == true ? " --#{k}" : " --#{k} #{v}" if v end.join cmd = "#{env} #{sudo} #{Gem.ruby} -I#{lib} #{requires_str} #{bundle_bin} #{cmd}#{args}" - - if exitstatus - sys_status(cmd) - else - sys_exec(cmd, expect_err){|i| yield i if block_given? } - end + sys_exec(cmd, expect_err){|i| yield i if block_given? } end def bundle_ruby(options = {}) - expect_err = options.delete(:expect_err) - exitstatus = options.delete(:exitstatus) + expect_err = options.delete(:expect_err) options["no-color"] = true unless options.key?("no-color") bundle_bin = File.expand_path('../../../bin/bundle_ruby', __FILE__) @@ -94,19 +87,15 @@ module Spec requires << File.expand_path('../artifice/'+options.delete(:artifice)+'.rb', __FILE__) if options.key?(:artifice) requires_str = requires.map{|r| "-r#{r}"}.join(" ") - env = (options.delete(:env) || {}).map{|k,v| "#{k}='#{v}' "}.join + env = (options.delete(:env) || {}).map{|k, v| "#{k}='#{v}' "}.join cmd = "#{env}#{Gem.ruby} -I#{lib} #{requires_str} #{bundle_bin}" - if exitstatus - sys_status(cmd) - else - sys_exec(cmd, expect_err){|i| yield i if block_given? } - end + sys_exec(cmd, expect_err){|i| yield i if block_given? } end def ruby(ruby, options = {}) expect_err = options.delete(:expect_err) - env = (options.delete(:env) || {}).map{|k,v| "#{k}='#{v}' "}.join + env = (options.delete(:env) || {}).map{|k, v| "#{k}='#{v}' "}.join ruby.gsub!(/["`\$]/) {|m| "\\#{m}" } lib_option = options[:no_lib] ? "" : " -I#{lib}" sys_exec(%{#{env}#{Gem.ruby}#{lib_option} -e "#{ruby}"}, expect_err) @@ -133,7 +122,7 @@ module Spec end def sys_exec(cmd, expect_err = false) - Open3.popen3(cmd.to_s) do |stdin, stdout, stderr| + Open3.popen3(cmd.to_s) do |stdin, stdout, stderr, wait_thr| @in_p, @out_p, @err_p = stdin, stdout, stderr yield @in_p if block_given? @@ -141,18 +130,13 @@ module Spec @out = @out_p.read_available_bytes.strip @err = @err_p.read_available_bytes.strip + @exitstatus = wait_thr && wait_thr.value.exitstatus end puts @err unless expect_err || @err.empty? || !$show_err @out end - def sys_status(cmd) - @err = nil - @out = %x{#{cmd}}.strip - @exitstatus = $?.exitstatus - end - def config(config = nil, path = bundled_app('.bundle/config')) return YAML.load_file(path) unless config FileUtils.mkdir_p(File.dirname(path)) @@ -166,14 +150,6 @@ module Spec config(config, home(".bundle/config")) end - def gemfile(*args) - create_file("Gemfile", *args) - end - - def lockfile(*args) - create_file("Gemfile.lock", *args) - end - def create_file(*args) path = bundled_app(args.shift) path = args.shift if args.first.is_a?(Pathname) @@ -184,6 +160,14 @@ module Spec end end + def gemfile(*args) + create_file("Gemfile", *args) + end + + def lockfile(*args) + create_file("Gemfile.lock", *args) + end + def strip_whitespace(str) # Trim the leading spaces spaces = str[/\A\s+/, 0] || "" @@ -217,6 +201,14 @@ module Spec ENV['GEM_HOME'], ENV['GEM_PATH'] = gem_home, gem_path end + def with_path_as(path) + old_path = ENV['PATH'] + ENV['PATH'] = "#{path}:#{ENV['PATH']}" + yield + ensure + ENV['PATH'] = old_path + end + def break_git! FileUtils.mkdir_p(tmp("broken_path")) File.open(tmp("broken_path/git"), "w", 0755) do |f| @@ -357,5 +349,12 @@ module Spec ensure Dir[pattern].each(&chmod[0755, 0644]) end + + def process_file(pathname) + changed_lines = pathname.readlines.map do |line| + yield line + end + File.open(pathname, 'w') { |file| file.puts(changed_lines.join) } + end end end diff --git a/spec/support/less_than_proc.rb b/spec/support/less_than_proc.rb new file mode 100644 index 0000000000..af80d82c4e --- /dev/null +++ b/spec/support/less_than_proc.rb @@ -0,0 +1,14 @@ +class LessThanProc < Proc + attr_accessor :present + + def self.with(present) + pv = Gem::Version.new(present.dup) + lt = self.new { |required| pv < Gem::Version.new(required) } + lt.present = present + return lt + end + + def inspect + "\"=< #{present.to_s}\"" + end +end diff --git a/spec/update/gems_spec.rb b/spec/update/gems_spec.rb index 856e922642..ed503e3607 100644 --- a/spec/update/gems_spec.rb +++ b/spec/update/gems_spec.rb @@ -58,11 +58,11 @@ describe "bundle update" do describe "with an unknown dependency" do it "should inform the user" do - bundle "update halting-problem-solver", :expect_err=>true + bundle "update halting-problem-solver", :expect_err =>true expect(out).to include "Could not find gem 'halting-problem-solver'" end it "should suggest alternatives" do - bundle "update active-support", :expect_err=>true + bundle "update active-support", :expect_err =>true expect(out).to include "Did you mean activesupport?" end end @@ -194,8 +194,8 @@ describe "bundle update" do gem "activesupport" G - bundle "update nonexisting", :exitstatus => true + bundle "update nonexisting" expect(out).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.") - expect(@exitstatus).to eq(22) + expect(exitstatus).to eq(22) if exitstatus end end diff --git a/spec/update/git_spec.rb b/spec/update/git_spec.rb index 2452636c10..9f5e71b3e2 100644 --- a/spec/update/git_spec.rb +++ b/spec/update/git_spec.rb @@ -91,7 +91,7 @@ describe "bundle update" do expect(err).to be_empty expect(out).to include("Fetching #{lib_path}/foo_two") - expect(out).to include("Your bundle is complete!") + expect(out).to include("Bundle complete!") end @@ -113,8 +113,8 @@ describe "bundle update" do gem 'foo', :git => "#{@remote.path}", :tag => "fubar" G - bundle "update", :exitstatus => true - expect(exitstatus).to eq(0) + bundle "update" + expect(exitstatus).to eq(0) if exitstatus end describe "with submodules" do @@ -206,7 +206,7 @@ describe "bundle update" do bundle %|config local.rack #{lib_path('local-rack')}| bundle "update rack" - expect(out).to include("Your bundle is updated!") + expect(out).to include("Bundle updated!") end it "shows the previous version of the gem" do |