diff options
author | Colby Swandale <colby@taplaboratories.com> | 2017-03-29 18:52:18 +1100 |
---|---|---|
committer | Colby Swandale <colby@taplaboratories.com> | 2017-03-29 18:52:18 +1100 |
commit | fe3046b0b422d0ff453149f4445026072648d6d6 (patch) | |
tree | 12a0923433fbe5d880abe23fd0c089c072031213 | |
parent | 9f358b82190dee44567726c5352c24109f3550ad (diff) | |
parent | 22db10fe2c1ff89468de2b3b2ce85a21902bf576 (diff) | |
download | bundler-fe3046b0b422d0ff453149f4445026072648d6d6.tar.gz |
Merge remote-tracking branch 'upstream/master' into bundler-config-parseable-flag
* upstream/master: (522 commits)
add tests
fix EOL
Require socket in the file that needs it
use Gem::Version.correct? instead of catching exceptions
remove rails 2.3 from ISSUES guide
quote empty PATH
turns out linux needs env vars to be quoted (even when empty)
run specs without git in the env
add specs to test gem command when git is not installed (note: this won't actually work at the moment)
dont create .gitignore as well if git is not installed in the gem command
dont init new git repo unless git is installed on gem command
fix spacing between paragraphs in generated READMEs for new gems
Account for differing behavior in hash#select between 1.8.7 and 1.9.3+
.map -> .each, as we are not using return value
Only uninstall plugins that we attempted to install.
Documentation at the correct place
Double quotes and small documentation
--key parameter can be set via bundle config gem.push_key
Add a spec for nested bundle exec with a gem that shadows a default gem
Allow installing a gem from an arbitrary repo in the specs
...
342 files changed, 7103 insertions, 2005 deletions
diff --git a/.gitignore b/.gitignore index bd5d29dc7b..88730f82cd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ # output from ronn /lib/bundler/man/ +man/* +!man/*.ronn # rspec failure tracking .rspec_status diff --git a/.rubocop.yml b/.rubocop.yml index b027d4cf54..ded01cbb21 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,7 +15,7 @@ Lint/AssignmentInCondition: Enabled: false Lint/EndAlignment: - AlignWith: variable + EnforcedStyleAlignWith: variable AutoCorrect: true Lint/UnusedMethodArgument: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3a23005c2b..4917f5af50 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,17 +1,16 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2016-07-27 12:41:39 -0500 using RuboCop version 0.41.2. +# on 2017-03-11 17:10:16 -0500 using RuboCop version 0.47.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 4 -Lint/Eval: +# Offense count: 2 +Lint/EmptyWhen: Exclude: - - 'lib/bundler.rb' - - 'lib/bundler/endpoint_specification.rb' - - 'spec/support/streams.rb' + - 'lib/bundler/friendly_errors.rb' + - 'spec/support/builders.rb' # Offense count: 4 Lint/HandleExceptions: @@ -41,6 +40,7 @@ Lint/RescueException: - 'lib/bundler/worker.rb' # Offense count: 1 +# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/bundler/fetcher.rb' @@ -51,18 +51,18 @@ Lint/UselessAssignment: - 'lib/bundler/index.rb' - 'lib/bundler/installer.rb' -# Offense count: 1686 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes. +# Offense count: 442 +# Configuration parameters: CountComments, ExcludedMethods. +Metrics/BlockLength: + Max: 980 + +# Offense count: 1966 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 207 -# Offense count: 3 -# Configuration parameters: CountKeywordArgs. -Metrics/ParameterLists: - Max: 6 - -# Offense count: 6 +# Offense count: 7 # Cop supports --auto-correct. Performance/RedundantBlockCall: Exclude: @@ -79,41 +79,61 @@ Performance/RedundantMatch: - 'lib/bundler/definition.rb' - 'lib/bundler/lockfile_parser.rb' -# Offense count: 6 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: MaxKeyValuePairs. Performance/RedundantMerge: Exclude: - 'lib/bundler/cli/gem.rb' - - 'spec/support/helpers.rb' + +# Offense count: 4 +Security/Eval: + Exclude: + - 'lib/bundler.rb' + - 'lib/bundler/endpoint_specification.rb' + - 'spec/support/streams.rb' + +# Offense count: 6 +Security/MarshalLoad: + Exclude: + - 'lib/bundler.rb' + - 'spec/install/gems/resolving_spec.rb' + - 'spec/support/artifice/compact_index.rb' + - 'spec/support/artifice/endpoint.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Security/YAMLLoad: + Exclude: + - 'spec/bundler/yaml_serializer_spec.rb' + - 'spec/commands/inject_spec.rb' # Offense count: 1 Style/AccessorMethodName: Exclude: - 'lib/bundler/source/git.rb' -# Offense count: 3 +# Offense count: 4 Style/CaseEquality: Exclude: - 'lib/bundler/dsl.rb' - 'lib/bundler/match_platform.rb' - 'lib/bundler/rubygems_ext.rb' -# Offense count: 23 +# Offense count: 24 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Enabled: false -# Offense count: 10 +# Offense count: 9 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'lib/bundler/cli.rb' - 'lib/bundler/cli/gem.rb' - - 'lib/bundler/cli/lock.rb' - 'lib/bundler/cli/platform.rb' - 'lib/bundler/dsl.rb' - 'lib/bundler/lazy_specification.rb' @@ -122,10 +142,23 @@ Style/ConditionalAssignment: - 'lib/bundler/source/git.rb' - 'lib/bundler/source/rubygems.rb' -# Offense count: 138 +# Offense count: 154 Style/Documentation: Enabled: false +# Offense count: 17 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: compact, expanded +Style/EmptyMethod: + Exclude: + - 'exe/bundle_ruby' + - 'lib/bundler/cli.rb' + - 'lib/bundler/plugin/api/source.rb' + - 'lib/bundler/rubygems_integration.rb' + - 'lib/bundler/ui/silent.rb' + - 'spec/support/artifice/fail.rb' + # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. @@ -133,13 +166,31 @@ Style/ExtraSpacing: Exclude: - 'lib/bundler/cli.rb' -# Offense count: 4 +# Offense count: 2 # Configuration parameters: AllowedVariables. Style/GlobalVars: Exclude: - 'lib/bundler/cli.rb' - 'spec/spec_helper.rb' - - 'spec/support/helpers.rb' + +# Offense count: 18 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Exclude: + - 'lib/bundler/cli/cache.rb' + - 'lib/bundler/cli/clean.rb' + - 'lib/bundler/cli/install.rb' + - 'lib/bundler/cli/outdated.rb' + - 'lib/bundler/cli/package.rb' + - 'lib/bundler/definition.rb' + - 'lib/bundler/dsl.rb' + - 'lib/bundler/installer.rb' + - 'lib/bundler/lockfile_parser.rb' + - 'lib/bundler/runtime.rb' + - 'lib/bundler/source/path/installer.rb' + - 'lib/bundler/source_list.rb' + - 'spec/commands/newgem_spec.rb' + - 'spec/support/sometimes.rb' # Offense count: 1 Style/IfInsideElse: @@ -151,13 +202,23 @@ Style/IfUnlessModifierOfIfUnless: Exclude: - 'spec/support/helpers.rb' -# Offense count: 4 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_brackets Style/IndentArray: EnforcedStyle: consistent +# Offense count: 6 +Style/MethodMissing: + Exclude: + - 'lib/bundler/dep_proxy.rb' + - 'lib/bundler/dsl.rb' + - 'lib/bundler/lazy_specification.rb' + - 'lib/bundler/plugin/dsl.rb' + - 'lib/bundler/remote_specification.rb' + - 'spec/support/builders.rb' + # Offense count: 2 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: module_function, extend_self @@ -166,6 +227,17 @@ Style/ModuleFunction: - 'lib/bundler/shared_helpers.rb' - 'spec/support/path.rb' +# Offense count: 11 +# Cop supports --auto-correct. +Style/MultilineIfModifier: + Exclude: + - 'bin/with_rubygems' + - 'lib/bundler/installer.rb' + - 'lib/bundler/psyched_yaml.rb' + - 'lib/bundler/rubygems_ext.rb' + - 'lib/bundler/runtime.rb' + - 'lib/bundler/source/rubygems.rb' + # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. @@ -183,6 +255,18 @@ Style/NestedParenthesizedCalls: - 'spec/commands/lock_spec.rb' - 'spec/runtime/setup_spec.rb' +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/bundler/gem_helper.rb' + - 'lib/bundler/mirror.rb' + - 'lib/bundler/source/git/git_proxy.rb' + - 'lib/bundler/source/path.rb' + # Offense count: 9 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ @@ -198,12 +282,27 @@ Style/PredicateName: - 'lib/bundler/source/git/git_proxy.rb' - 'lib/bundler/source/path.rb' -# Offense count: 25 +# Offense count: 24 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, exploded Style/RaiseArgs: - Enabled: false + Exclude: + - 'lib/bundler/cli/install.rb' + - 'lib/bundler/deployment.rb' + - 'lib/bundler/fetcher/downloader.rb' + - 'lib/bundler/fetcher/index.rb' + - 'lib/bundler/installer/standalone.rb' + - 'lib/bundler/rubygems_integration.rb' + - 'lib/bundler/shared_helpers.rb' + - 'lib/bundler/source/git/git_proxy.rb' + - 'lib/bundler/source/rubygems/remote.rb' + - 'spec/bundler/endpoint_specification_spec.rb' + - 'spec/bundler/fetcher/dependency_spec.rb' + - 'spec/bundler/fetcher/downloader_spec.rb' + - 'spec/bundler/fetcher/index_spec.rb' + - 'spec/bundler/rubygems_integration_spec.rb' + - 'spec/bundler/shared_helpers_spec.rb' # Offense count: 2 # Cop supports --auto-correct. @@ -219,14 +318,31 @@ Style/SpaceAroundOperators: Exclude: - 'lib/bundler/retry.rb' -# Offense count: 10 +# Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. -# SupportedStyles: comma, consistent_comma, no_comma +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBraces: space, no_space +Style/SpaceInsideBlockBraces: + Exclude: + - 'lib/bundler/installer/parallel_installer.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex +Style/TernaryParentheses: + Exclude: + - 'lib/bundler/cli/common.rb' + - 'lib/bundler/gem_version_promoter.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma Style/TrailingCommaInLiteral: Exclude: - 'lib/bundler/cli/gem.rb' - - 'lib/bundler/dependency.rb' - 'lib/bundler/fetcher.rb' - 'lib/bundler/gem_helpers.rb' - 'lib/bundler/graph.rb' @@ -234,7 +350,7 @@ Style/TrailingCommaInLiteral: - 'lib/bundler/similarity_detector.rb' - 'spec/support/artifice/endpoint.rb' -# Offense count: 18 +# Offense count: 7 # Cop supports --auto-correct. Style/UnneededInterpolation: Exclude: @@ -243,6 +359,3 @@ Style/UnneededInterpolation: - 'spec/bundler/shared_helpers_spec.rb' - 'spec/cache/git_spec.rb' - 'spec/commands/exec_spec.rb' - - 'spec/support/artifice/endpoint.rb' - - 'spec/support/artifice/endpoint_500.rb' - - 'spec/support/fakeweb/windows.rb' diff --git a/.travis.yml b/.travis.yml index 88ae318a94..a06deba886 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,6 @@ branches: - /.+-stable$/ notifications: - email: - # andre - - secure: "bCcvqJT7YrBawtkXXwHhT+jOFth7r2Qv/30PkkbhQxk6Jb3xambjCOJ3U6vJ\ngYmiL50exi5lUp3oc3SEbHN5t2CrZqOZDQ6o7P8EAmB5c0oH2RrYaFOkI5Gt\nul/jGH/96A9sj0aMwG7JfdMSfhqj1DUKAm2PnnbXPL853VfmT24=" - # terence - - secure: "MQ8eA5Jb8YzEpAo58DRGfVJklAPcEbAulpBZnTxp0am6ldneDtJHbQk21w6R\nj5GsDHlzr/lMp/GHIimtUZ7rLohfND8fj/W7fs1Dkd4eN02/ERt98x3pHlqv\nvZgSnZ39uVYv+OcphraE24QaRaGWLhWZAMYQTVe/Yz50NyG8g1U=" slack: on_success: change on_failure: always @@ -30,9 +25,10 @@ addons: secure: "TrzIv116JLGUxm6PAUskCYrv8KTDguncKROVwbnjVPKTGDAgoDderd8JUdDEXrKoZ9qGLD2TPYKExt9/QDl71E+qHdWnVqWv4HKCUk2P9z/VLKzHuggOUBkCXiJUhjywUieCJhI3N92bfq2EjSBbu2/OFHqWOjLQ+QCooTEBjv8=" rvm: - - 2.3.1 - - 2.2 - - 2.1 + - 2.4.0 + - 2.3.3 + - 2.2.6 + - 2.1.10 - 2.0.0 - 1.9.3 - 1.8.7 @@ -43,18 +39,19 @@ env: # We need to know if changes to rubygems will break bundler on release - RGV=master # Test the latest rubygems release with all of our supported rubies - - RGV=v2.6.6 + - RGV=v2.6.8 matrix: include: + # Ruby 2.4, Rubygems 2.6.8 and up # Ruby 2.3, Rubygems 2.5.1 and up - - rvm: 2.2 + - rvm: 2.2.6 env: RGV=v2.5.2 # Ruby 2.2, Rubygems 2.4.5 and up - - rvm: 2.2 + - rvm: 2.2.6 env: RGV=v2.4.8 # Ruby 2.1, Rubygems 2.2.2 and up - - rvm: 2.1 + - rvm: 2.1.10 env: RGV=v2.2.5 # Ruby 2.0.0, Rubygems 2.0.0 and up - rvm: 2.0.0 @@ -79,11 +76,11 @@ matrix: - rvm: 1.9.3 env: RGV=v1.5.3 - # ALLOWED FAILURES # Ruby 1.8.7, Rubygems 1.3.6 and up - # since the great Travis image outage, frequent random segfaults :'( - rvm: 1.8.7 env: RGV=v2.2.5 + # ALLOWED FAILURES + # since the great Travis image outage, frequent random segfaults :'( - rvm: 1.8.7 env: RGV=v2.0.14 - rvm: 1.8.7 @@ -100,13 +97,26 @@ matrix: env: RGV=v1.3.7 - rvm: 1.8.7 env: RGV=v1.3.6 - # For no apparent reason, this often goes over the Travis limit - - rvm: 1.8.7 - env: RGV=v2.1.11 # Ruby-head (we want to know how we're doing, but not fail the build) - rvm: ruby-head env: RGV=master allow_failures: - rvm: 1.8.7 + env: RGV=v2.0.14 + - rvm: 1.8.7 + env: RGV=v1.8.29 + - rvm: 1.8.7 + env: RGV=v1.7.2 + - rvm: 1.8.7 + env: RGV=v1.6.2 + - rvm: 1.8.7 + env: RGV=v1.5.3 + - rvm: 1.8.7 + env: RGV=v1.4.2 + - rvm: 1.8.7 + env: RGV=v1.3.7 + - rvm: 1.8.7 + env: RGV=v1.3.6 - rvm: ruby-head + env: RGV=master diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c7f8ba80..55d9ea5ab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,195 @@ +## 1.14.6 (2017-03-03) + +Bugfixes: + + - avoid undefined constant `Bundler::Plugin::API::Source` exception (#5409, @segiddins) + - avoid incorrect warnings about needing to enable `specific_platform` (@segiddins) + - fail gracefully when the compact index does not send an ETag (#5463, @olleolleolle) + - ensure `bundle outdated --local` shows all outdated gems (#5430, @denniss) + - fix a case where ruby version requirements could lead to incorrect resolver conflicts (#5425, @segiddins) + +## 1.14.5 (2017-02-22) + +Bugfixes: + + - avoid loading all unused gemspecs during `bundle exec` on RubyGems 2.3+ (@segiddins) + - improve resolver performance when dependencies have zero or one total possibilities ignoring requirements (#5444, #5457, @segiddins) + - enable compact index when OpenSSL FIPS mode is enabled but not active (#5433, @wjordan) + - use github username instead of git name for the github url in `bundle gem` (#5438, @danielpclark) + - avoid a TypeError on RubyGems 2.6.8 when no build settings are set for native extensions (@okkez) + - fail gracefully when the dependency api is missing runtime dependencies for a gem (@segiddins) + - handle when a platform-specific gem has more dependencies than the ruby platform version (#5339, #5426, @segiddins) + - allow running bundler on a machine with no home directory where the temporary directory is not writable (#5371, @segiddins) + - avoid gem version conflicts on openssl using Ruby 2.5 (#5235, @rhenium) + - fail when installing in frozen mode and the dependencies for `gemspec` gems have changed without the lockfile being updated (#5264, @segiddins) + +## 1.14.4 (2017-02-12) + +Bugfixes: + + - fail gracefully when attempting to overwrite an existing directory with `bundle gem` (#5358, @nodo) + - fix a resolver bug that would cause bundler to report conflicts that it could resolve (#5359, #5362, @segiddins) + - set native extension build arguments for git gems (#5401, @segiddins) + - fix the suggested `bundle lock` command printed when a dependency is unused on any platform (@5t111111) + - ensure the version passed to `ruby` in the Gemfile is valid during Gemfile parsing (#5380, @segiddins) + - show `bundle inject` usage when too many arguments are passed (#5384, @Shekharrajak) + - stop `bundle show --outdated` from implicitly running `bundle update` (#5375, @colby-swandale) + - allow the temporary home directory fallback to work for multiple users (@svoop) + +## 1.14.3 (2017-01-24) + +Bugfixes: + + - fix the resolver attempting to activate ruby-platform gems when the bundle is only for other platforms (#5349, #5356, @segiddins) + - avoid re-resolving a locked gemfile that uses `gemspec` and includes development dependencies (#5349, @segiddins) + +## 1.14.2 (2017-01-22) + +Bugfixes: + + - fix using `force_ruby_platform` on windows (#5344, @segiddins) + - fix an incorrect version conflict error when using `gemspec` on multiple platforms (#5340, @segiddins) + +## 1.14.1 (2017-01-21) + +Bugfixes: + + - work around a ruby 2.2.2 bug that caused a stack consistency error during installation (#5342, @segiddins) + +## 1.14.0 (2017-01-20) + +Bugfixes: + + - ensure `Settings::Mirror` is autoloaded under the `Settings` namespace + (#5238, @segiddins) + - fix `bundler/inline` when `BUNDLE_GEMFILE=""` (#5079, @segiddins) + +## 1.14.0.pre.2 (2017-01-11) + +Bugfixes: + + - allow not selecting a gem when running `bundle open` (#5301, @segiddins) + - support installing gems from git branches that contain shell metacharacters (#5295, @segiddins) + - fix a resolver error that could leave dependencies unresolved (#5294, @segiddins) + - fix a stack overflow error when invoking commands (#5296, @segiddins) + +## 1.14.0.pre.1 (2016-12-29) + +Features: + + - `bundle doctor` first runs `bundle check` (@segiddins) + - the bundler trampoline is automatically enabled when the target version is greater than bundler 2 (@segiddins) + - gem checksums returned by rubygems.org are validated when installing gems (#4464, @segiddins) + - use the git username as a github username when running `bundle gem` (@JuanitoFatas) + - show more context when the resolver conflicts on required ruby and rubygems versions (@segiddins) + - improve platform support by allowing bundler to pick the best platform match during dependency resolution, enabled with the `specific_platform` setting (#4295, #4896, @segiddins) + - always prompt the user for a password when using `sudo` (#3006, @segiddins) + - support running without a home directory (#4778, @segiddins) + - print a warning when the gemfile uses a platform conditional that will exclude the gem from all lockfile platforms (@segiddins) + - add the `force_ruby_platform` setting to force bundler to install ruby-platform gems, even on other platforms (#4813, @segiddins) + - add conservative update options to `bundle lock` (#4912, @chrismo) + - improve `bundle outdated` output to group gems by group (@ryanfox1985) + - add conservative update options to `bundle update` (#5065, #5076, @chrismo) + - print the output of `bundle env` as github-flavored markdown, making it easier to preserve formatting when copy-pasting into a new issue (@segiddins) + - configure the persistence file when using `bundle gem` with `rspec` (@segiddins) + - add support for the `ruby_25` gemfile filter (@amatsuda) + - when installing with a lockfile that is missing dependencies, allow installation to proceed (but without parallelism) (@segiddins) + +Performance: + + - improve `require "bundler"` performance by ~5x (@segiddins) + - allow install gems in parallel when running on rubygems 2+ + +Bugfixes: + + - config files with CRLF line endings can be read (#4435, @segiddins) + - `bundle lock` activates gems for the current platform even if they were activated under a different platform for a separate dependency (#4896, @segiddins) + - running `bundle env` in a directory without a gemfile no longer crashes (@segiddins) + - fail gracefully when attempting to use a source with an unknown URI scheme (#4953, @segiddins) + - store paths in the lockfile relative to the root gemfile directory when using `eval_gemfile` (#4966, @segiddins) + - `bundle lock` will not update without the `--update` flag (#4957, @segiddins) + - the `console` binstub generated by `bundle gem` will load `.irbrc` files (@mattbrictson) + - print friendly filesystem access errors in the new index (@segiddins) + - print a helpful error when running out of memory on jruby (#4673, @segiddins) + - load all rubygems plugins when installing gems (#2824, @segiddins) + - `bundle clean --dry-run` prints the list of gems without the `--force` option when no path is set (#5027, @hmistry) + - local installs no longer print "this gem may have been yanked" (#5022, @hmistry) + - avoid leaking `which` output when running `bundle doctor` (@colby-swandale) + - print a warning when attempting to `bundle exec` an empty program (#5084, @bronzdoc) + - ensure `bundle outdated` lists all outdated gems (#4979, @chrismo) + - fail gracefully when attempting to `bundle gem` with an invalid constant name (#5185, @segiddins) + - allow `bundler/inline` to work in a directory that contains a gemfile (#5117, @colby-swandale) + - ensure that the new index is thread-safe, allowing installation on rbx (#5142, @segiddins) + - remove deprecated `rspec` syntax in `bundle gem` output (@gearnode) + - fail gracefully when any system error is encountered when touching the filesystem (#5134, @segiddins) + - fix compatibility with a machine running with FIPS mode enabled (#4989, @segiddins) + - fix `bundle lock --add-platform ruby` (#5230, @segiddins) + - print gem post-install messages when running `bundle update` (@smathy) + - ensure errors due to a retries are all separated by a newline (@segiddins) + - print out the bundle path in gem not found errors (#4854, @diegosteiner) + - fail gracefully when creating threads fails (@segiddins) + - avoid downloading metadata for gems that are only development dependencies (@Paxa) + +## 1.13.7 (2016-12-25) + +Features: + + - add support for the `ruby_24` gemfile filter (#5281, @amatsuda) + +## 1.13.6 (2016-10-22) + +Bugfixes: + + - make the `gem` method public again, fixing a regression in 1.13.4 (#5102, @segiddins) + +## 1.13.5 (2016-10-15) + +Bugfixes: + + - Ensure a locked pre-release spec can always be re-resolved (#5089, @segiddins) + +## 1.13.4 (2016-10-11) + +Bugfixes: + + - stop printing warning when compact index versions file is rewritten (#5064, @indirect) + - fix `parent directory is world writable but not sticky` error on install (#5043, @indirect) + - fix for `uninitialized constant Bundler::Plugin::API::Source` error (#5010, @hsbt, @aycabta) + - make `update` options for major, minor, and patch updates consistent (#4934, @chrismo) + +## 1.13.3 (2016-10-10) + +Bugfixes: + + - add support for weak etags to the new index (@segiddins) + +## 1.13.2 (2016-09-30) + +Bugfixes: + + - allow `Settings` to be initialized without a root directory (@m1k3) + - allow specifying ruby engines in the gemfile as a symbol (#4919, @JuanitoFatas) + - avoid an exception when using `bundler/deployment` with Vlad (@srbaker) + - ensure redefined methods have the same visibility as the one they're replacing, fixing `Kernel.require` failing on JRuby (#4975, @segiddins) + - ensure that Bundler won't complain about a corrupt lockfile when no lockfile exists when using `gemspec` in the Gemfile (#5006, @segiddins) + - fail gracefully when parsing the metadata for a gemspec from the compact index fails (@segiddins) + - fix system gems not being copied to --path on bundle install (e.g. --deployment) (#4974, @chrismo) + +Performance: + + - avoid parsing the lockfile twice when evaluating gemfiles (@segiddins) + +## 1.13.1 (2016-09-13) + +Bugfixes: + + - ensure that `Gem::Source` is available, fixing several exceptions (#4944, @dekellum) + - ensure that dependency resolution works when multiple gems have the same dependency (#4961, @segiddins) + +## 1.13.0 (2016-09-05) + +This space deliberately left blank. + ## 1.13.0.rc.2 (2016-08-21) Features: @@ -11,7 +203,7 @@ Features: - print gem installation errors after other install output (#4834, @segiddins) - add `lock --remove-platform` flag to remove platforms from the lock (#4877, @segiddins) - add `only_update_to_newer_versions` setting to prevent downgrades during `update` (@segiddins) - - expanded expirimental plugin support to include hooks and sources (@asutoshpalai) + - expanded experimental plugin support to include hooks and sources (@asutoshpalai) Bugfixes: @@ -89,6 +281,11 @@ Bugfixes: - allow running `bundle install --deployment` after `bundle package --all` with path gems (#2175, @allenzhao) - add support for patchlevels in ruby versions in the gemfile and gemspecs (#4593, @chalkos) +## 1.12.6 (2016-10-10) + +Bugfixes: + - add support for weak etags to the new index (@segiddins) + ## 1.12.5 (2016-05-25) Bugfixes: @@ -1208,13 +1405,13 @@ Bugfixes: Features: - - compatibile with Ruby 2.0.0-preview2 - - compatibile with Rubygems 2.0.0.preview2 (@drbrain, @evanphx) + - compatible with Ruby 2.0.0-preview2 + - compatible with Rubygems 2.0.0.preview2 (@drbrain, @evanphx) - ruby 2.0 added to the `:ruby19` ABI-compatible platform - lazy load YAML, allowing Psych to be specified in the Gemfile - significant performance improvements (@cheald, #2181) - `inject` command for scripted Gemfile additions (Engine Yard) - - :github option uses slashless arguements as repo owner (@rking) + - :github option uses slashless arguments as repo owner (@rking) - `open` suggests gem names for typos (@jdelStrother) - `update` reports non-existent gems (@jdelStrother) - `gem` option --test can generate rspec stubs (@MafcoCinco) @@ -1638,7 +1835,7 @@ Features: - Add bundle clean. Removes unused gems from --path directory - Initial Gemcutter Endpoint API work, BAI Fetching source index - Added bundle install --standalone - - Ignore Gemfile.lock when buliding new gems + - Ignore Gemfile.lock when building new gems - Make it possible to override a .gemspec dependency's source in the Gemfile @@ -1689,7 +1886,7 @@ Bugfixes: Features: - - Compatability with Rubygems 1.8.10 installer changes + - Compatibility with Rubygems 1.8.10 installer changes - Report gem installation failures clearly (@rwilcox, #1380) - Useful error for cap and vlad on first deploy (@nexmat, @kirs) @@ -2439,7 +2636,7 @@ Bugfixes: - make the tests platform agnostic so we can confirm that they're green on JRuby - fixes for Ruby 1.9 -## 0.9.5 (Feburary 12, 2010) +## 0.9.5 (February 12, 2010) Features: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8349280ddd..8dc0548add 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,36 +1,17 @@ -# Contributing +# Contributing to Bundler -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. - -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 or suggesting new features](https://github.com/bundler/bundler/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) or [CodeClimate](https://codeclimate.com/github/bundler/bundler) - - by [writing code](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) (no patch is too small! fix typos or bad whitespace) - - by backfilling [unit tests](https://github.com/bundler/bundler/tree/master/spec/bundler) for modules that lack [coverage](https://codeclimate.com/github/bundler/bundler/coverage) - -If you need help getting started, check out the [DEVELOPMENT.md](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) file for steps that will get you up and running. +Welcome to Bundler! We are so happy that you're here. We know it can be daunting to joining a new open source project, so here's a quick overview of what you can expect from this documentation. -Thanks for helping us make Bundler better. - -# Roadmap - -If you're interested in reading some of the potential ideas or plans for Bundler, see the official Bundler [Trello Board](https://trello.com/b/DioUaKgx/bundler). - -# Troubleshooting +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. -If you're having a problem, please see [ISSUES](https://github.com/bundler/bundler/blob/master/ISSUES.md) for troubleshooting steps and a guide for how to submit a ticket that will help us solve the problem you are having as quickly as possible. +## Quick start -# Requesting Features +Some guides you might find useful: -Feel free to discuss your ideas or feature requests on any of the lists or channels listed below or [create an issue](https://github.com/bundler/bundler/issues/new) with the `feature-request` label. Feature-wise, we consider Bundler stable, so the core team does not tend to work on feature suggestions. Pull requests are welcome though! +* [Submitting pull requests](doc/development/PULL_REQUESTS.md) +* [Filing an issue](doc/contributing/ISSUES.md) +* [Bug triage](doc/contributing/BUG_TRIAGE.md) -# Discussing Bundler +## Comprehensive guides -If you'd like to discuss features, ask questions, or just engage in general Bundler-focused discussion, please see the [#bundler](irc://irc.freenode.net/#bundler) IRC channel on Freenode, and the [Bundler mailing list](http://groups.google.com/group/ruby-bundler) on Google Groups. +Not finding what you're looking for? More comprehensive guides are [available here](doc/README.md). diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index 8964b75c3d..0000000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,148 +0,0 @@ -Great to have you here! Here are a few ways you can help out with [Bundler](http://github.com/bundler/bundler). - -# 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/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 - -If you’re interested in contributing to Bundler, that’s awesome! We’d love your help. - -If you have any questions after reading this page, please feel free to contact either [@indirect](https://github.com/indirect), [@segiddins](https://github.com/segiddins), or [@RochesterinNYC](https://github.com/RochesterinNYC). They are all happy to provide help working through your first bug fix or thinking through the problem you’re trying to resolve. - -## How you can help - -We track [small bugs and features](https://github.com/bundler/bundler/issues?labels=small) so that anyone who wants to help can start with something that's not too overwhelming. We also keep a [list of things anyone can help with, any time](https://github.com/bundler/bundler/blob/master/CONTRIBUTING.md#contributing). If nothing on those lists looks good, talk to us, and we'll figure out what you can help with. We can absolutely use your help, no matter what level of programming skill you have at the moment. - -# Development setup - -Bundler doesn't use a Gemfile to list development dependencies, because when we tried it we couldn't tell if we were awake or it was just another level of dreams. To work on Bundler, you'll probably want to do a couple of things. - - 1. Install `groff-base` and `graphviz` packages using your package manager, e.g for ubuntu - - $ sudo apt-get install graphviz groff-base -y - - and for OS X (with brew installed) - - $ brew install graphviz homebrew/dupes/groff - - 2. Install Bundler's development dependencies - - $ bin/rake spec:deps - - 3. Run the test suite, to make sure things are working - - $ bin/rake spec - - 4. Set up a shell alias to run Bundler from your clone, e.g. a Bash alias: - - $ alias dbundle='BUNDLE_DISABLE_POSTIT=1 ruby -I /path/to/bundler/lib /path/to/bundler/exe/bundle' - - The `BUNDLE_DISABLE_POSTIT` environment variable ensures that the version of Bundler in `/path/to/bundler/lib` will be used. Without that environment setting, Bundler will automatically download, install, and run the version of Bundler listed in `Gemfile.lock`. With that set up, you can test changes you've made to Bundler by running `dbundle`, without interfering with the regular `bundle` command. - -# Submitting Pull Requests - -Before you submit a pull request, please remember to do the following: - -- Make sure the code formatting and styling adheres to the guidelines. We use Rubocop for this. Lack of formatting adherence will result in automatic Travis build failures. - - $ bin/rubocop -a - -- Please run the test suite: - - $ bin/rspec - -- If you are unable to run the entire test suite, please run the unit test suite and at least the integration specs related to the command or domain of Bundler that your code changes relate to. - -- Ex. For a pull request that changes something with `bundle update`, you might run: - - $ bin/rspec spec/bundler - $ bin/rspec spec/commands/update_spec.rb - -- Please ensure that the commit messages included in the pull request __do not__ have the following: - - `@tag` Github user or team references (ex. `@indirect` or `@bundler/core`) - - `#id` references to issues or pull requests (ex. `#43` or `bundler/bundler-site#12`) - - If you want to use these mechanisms, please instead include them in the pull request description. This prevents multiple notifications or references being created on commit rebases or pull request/branch force pushes. - -- Additionally, do not use `[ci skip]` or `[skip ci]` mechanisms in your pull request titles/descriptions or commit messages. Every potential commit and pull request should run through Bundler's CI system. This applies to all changes/commits (ex. even a change to just documentation or the removal of a comment). - -# Bug triage - -Triage is the work of processing tickets that have been opened into actionable issues, feature requests, or bug reports. That includes verifying bugs, categorizing the ticket, and ensuring there's enough information to reproduce the bug for anyone who wants to try to fix it. - -We've created an [issues guide](https://github.com/bundler/bundler/blob/master/ISSUES.md) to walk Bundler users through the process of troubleshooting issues and reporting bugs. - -If you'd like to help, awesome! You can [report a new bug](https://github.com/bundler/bundler/issues/new) or browse our [existing open tickets](https://github.com/bundler/bundler/issues). - -Not every ticket will point to a bug in Bundler's code, but open tickets usually mean that there is something we could improve to help that user. Sometimes that means writing additional documentation, sometimes that means making error messages clearer, and sometimes that means explaining to a user that they need to install git to use git gems. - -When you're looking at a ticket, here are the main questions to ask: - - * Can I reproduce this bug myself? - * Are the steps to reproduce clearly stated in the ticket? - * Which versions of Bundler (1.1.x, 1.2.x, git, etc.) manifest this bug? - * Which operating systems (OS X, Windows, Ubuntu, CentOS, etc.) manifest this bug? - * Which rubies (MRI, JRuby, Rubinius, etc.) and which versions (1.8.7, 1.9.3, etc.) have this bug? - -If you can't reproduce an issue, chances are good that the bug has been fixed (hurrah!). That's a good time to post to the ticket explaining what you did and how it worked. - -If you can reproduce an issue, you're well on your way to fixing it. :) Fixing issues is similar to adding new features: - - 1. Discuss the fix on the existing issue. Coordinating with everyone else saves duplicate work and serves as a great way to get suggestions and ideas if you need any. - 2. Base your commits on the correct branch. Bugfixes for 1.x versions of Bundler should be based on the matching 1-x-stable branch. - 3. Commit the code and at least one test covering your changes to a named 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 “Bugfixes” heading. - 5. Send us a [pull request](https://help.github.com/articles/using-pull-requests) from your bugfix branch. - -Finally, the ticket may be a duplicate of another older ticket. If you notice a ticket is a duplicate, simply comment on the ticket noting the original ticket’s number. For example, you could say “This is a duplicate of issue #42, and can be closed”. - - -# Adding New Features - -If you would like to add a new feature to Bundler, please follow these steps: - - 1. [Create an issue](https://github.com/bundler/bundler/issues/new) with the [`feature-request` label](https://github.com/bundler/bundler/labels/type:%20feature-request) 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. Send us a [pull request](https://help.github.com/articles/using-pull-requests) from your feature branch. - -If you don't hear back immediately, don’t get discouraged! We all have day jobs, but we respond to most tickets within a day or two. - - -# Beta testing - -Early releases require heavy testing, especially across various system setups. We :heart: testers, and are big fans of anyone who can run `gem install bundler --pre` and try out upcoming releases in their development and staging environments. - -There may not always be prereleases or beta versions of Bundler. The Bundler team will tweet from the [@bundlerio account](http://twitter.com/bundlerio) when a prerelease or beta version becomes available. You are also always welcome to try checking out master and building a gem yourself if you want to try out the latest changes. - - -# Translations - -We don't currently have any translations, but please reach out to us if you would like to help get this going. - - -# Documentation - -Code needs explanation, and sometimes those who know the code well have trouble explaining it to someone just getting into it. Because of that, we welcome documentation suggestions and patches from everyone, especially if they are brand new to using Bundler. - -Bundler has two main sources of documentation: the built-in help (including usage information and man pages) and the [Bundler documentation site](http://bundler.io). - -If you’d like to submit a patch to the man pages, follow the steps for submitting a pull request above. All of the man pages are located in the `man` directory. Just use the “Documentation” heading when you describe what you did in the changelog. - -If you have a suggestion or proposed change for [bundler.io](http://bundler.io), please open an issue or send a pull request to the [bundler-site](https://github.com/bundler/bundler-site) repository. - - -# Community - -Community is an important part of all we do. If you’d like to be part of the Bundler community, you can jump right in and start helping make Bundler better for everyone who uses it. - -It would be tremendously helpful to have more people answering questions about Bundler (and often simply about [Rubygems](https://github.com/rubygems/rubygems) or Ruby itself) in our [issue tracker](https://github.com/bundler/bundler/issues) or on [Stack Overflow](http://stackoverflow.com/questions/tagged/bundler). - -Additional documentation and explanation is always helpful, too. If you have any suggestions for the Bundler website [bundler.io](http://bundler.io), we would absolutely love it if you opened an issue or pull request on the [bundler-site](https://github.com/bundler/bundler-site) repository. - -Finally, sharing your experiences and discoveries by writing them up is a valuable way to help others who have similar problems or experiences in the future. You can write a blog post, create an example and commit it to Github, take screenshots, or make videos. - -Publishing examples of how Bundler is used helps 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. - -Finally, 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. @@ -11,8 +11,15 @@ It does this by managing the gems that the application depends on. Given a list ### Installation and usage +To install: + ``` gem install bundler +``` + +Bundler is most commonly used to manage your application's dependencies. To use it for this: + +``` bundle init echo 'gem "rspec"' >> Gemfile bundle install @@ -23,17 +30,24 @@ See [bundler.io](http://bundler.io) for the full documentation. ### Troubleshooting -For help with common problems, see [ISSUES](https://github.com/bundler/bundler/blob/master/ISSUES.md). +For help with common problems, see [TROUBLESHOOTING](doc/TROUBLESHOOTING.md). + +Still stuck? Try [filing an issue](doc/contributing/ISSUES.md). + +### Supporting + +<a href="https://rubytogether.org/"><img src="https://rubytogether.org/images/rubies.svg" width=200></a><br/> +Bundler is maintained by <a href="https://rubytogether.org/">Ruby Together</a>, a grassroots initiative committed to supporting the critical Ruby infrastructure you rely on. Contribute today <a href="https://rubytogether.org/developers">as an individual</a> or even better, <a href="https://rubytogether.org/companies">as a company</a>, and ensure that Bundler, RubyGems, and other shared tooling is around for years to come. ### Other questions -To see what has changed in recent versions of Bundler, see the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md). +To see what has changed in recent versions of Bundler, see the [CHANGELOG](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). +To get in touch with the Bundler core team and other Bundler users, please see [getting help](doc/contributing/GETTING_HELP.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). +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 [our documentation section](doc/README.md). ### Code of Conduct @@ -39,7 +39,7 @@ namespace :spec do deps = Hash[BUNDLER_SPEC.development_dependencies.map do |d| [d.name, d.requirement.to_s] end] - deps["rubocop"] ||= "= 0.41.2" if RUBY_VERSION >= "1.9.3" # can't go in the gemspec because of the ruby version requirement + deps["rubocop"] ||= "= 0.47.1" if RUBY_VERSION >= "2.0.0" # can't go in the gemspec because of the ruby version requirement # JRuby can't build ronn or rdiscount, so we skip that if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" @@ -47,10 +47,10 @@ namespace :spec do deps.delete("rdiscount") end - deps.sort_by {|name, _| name }.each do |name, version| - sh %(#{Gem.ruby} -S gem list -i "^#{name}$" -v "#{version}" || ) + - %(#{Gem.ruby} -S gem install #{name} -v "#{version}" --no-ri --no-rdoc) - end + gem_install_command = "install --no-ri --no-rdoc --conservative " + deps.sort_by {|name, _| name }.map do |name, version| + "'#{name}:#{version}'" + end.join(" ") + sh %(#{Gem.ruby} -S gem #{gem_install_command}) # Download and install gems used inside tests $LOAD_PATH.unshift("./spec") @@ -74,7 +74,7 @@ namespace :spec do $LOAD_PATH.unshift("./spec") require "support/rubygems_ext" - Spec::Rubygems::DEPS["codeclimate-test-reporter"] = nil if RUBY_VERSION >= "2.2.0" + Spec::Rubygems::DEPS["codeclimate-test-reporter"] = "~> 0.6.0" if RUBY_VERSION >= "2.2.0" # Install the other gem deps, etc Rake::Task["spec:deps"].invoke @@ -91,9 +91,9 @@ begin RSpec::Core::RakeTask.new task :spec => "man:build" - if RUBY_VERSION >= "1.9.3" + if RUBY_VERSION >= "2.0.0" # can't go in the gemspec because of the ruby version requirement - gem "rubocop", "= 0.41.2" + gem "rubocop", "= 0.47.1" require "rubocop/rake_task" RuboCop::RakeTask.new end @@ -127,7 +127,7 @@ begin rubyopt = ENV["RUBYOPT"] # When editing this list, also edit .travis.yml! branches = %w(master) - releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.5.2 v2.6.6) + releases = %w(v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.29 v2.0.14 v2.1.11 v2.2.5 v2.4.8 v2.5.2 v2.6.8) (branches + releases).each do |rg| desc "Run specs with Rubygems #{rg}" RSpec::Core::RakeTask.new(rg) do |t| @@ -190,7 +190,7 @@ begin task :travis do rg = ENV["RGV"] || raise("Rubygems version is required on Travis!") - if RUBY_VERSION > "1.9.3" + if RUBY_VERSION >= "2.0.0" puts "\n\e[1;33m[Travis CI] Running bundler linter\e[m\n\n" Rake::Task["rubocop"].invoke end @@ -238,14 +238,15 @@ begin require "ronn" namespace :man do - directory "lib/bundler/man" + directory "man" sources = Dir["man/*.ronn"].map {|f| File.basename(f, ".ronn") } sources.map do |basename| ronn = "man/#{basename}.ronn" - roff = "lib/bundler/man/#{basename}" + manual_section = ".1" unless basename =~ /.*(\d+)\Z/ + roff = "man/#{basename}#{manual_section}" - file roff => ["lib/bundler/man", ronn] do + file roff => ["man", ronn] do sh "#{Gem.ruby} -S ronn --roff --pipe #{ronn} > #{roff}" end @@ -257,9 +258,8 @@ begin end task :clean do - leftovers = Dir["lib/bundler/man/*"].reject do |f| - basename = File.basename(f).sub(/\.(txt|ronn)/, "") - sources.include?(basename) + leftovers = Dir["man/*"].reject do |f| + File.extname(f) == ".ronn" || f == "man/index.txt" end rm leftovers if leftovers.any? end @@ -285,13 +285,6 @@ end begin require "automatiek" - Automatiek::RakeTask.new("compact_index_client") do |lib| - lib.download = { :github => "https://github.com/bundler/compact_index_client" } - lib.namespace = "CompactIndexClient" - lib.prefix = "Bundler" - lib.vendor_lib = "lib/bundler/vendor/compact_index_client" - end - Automatiek::RakeTask.new("molinillo") do |lib| lib.download = { :github => "https://github.com/CocoaPods/Molinillo" } lib.namespace = "Molinillo" @@ -312,11 +305,29 @@ begin lib.prefix = "BundlerVendoredPostIt" lib.vendor_lib = "lib/bundler/vendor/postit" end + + Automatiek::RakeTask.new("net-http-persistent") do |lib| + lib.download = { :github => "https://github.com/drbrain/net-http-persistent" } + lib.namespace = "Net::HTTP::Persistent" + lib.prefix = "Bundler::Persistent" + lib.vendor_lib = "lib/bundler/vendor/net-http-persistent" + + mixin = Module.new do + def namespace_files + super + require_target = vendor_lib.sub(%r{^(.+?/)?lib/}, "") << "/lib" + relative_files = files.map {|f| Pathname.new(f).relative_path_from(Pathname.new(vendor_lib) / "lib").sub_ext("").to_s } + process_files(/require (['"])(#{Regexp.union(relative_files)})/, "require \\1#{require_target}/\\2") + end + end + lib.send(:extend, mixin) + end rescue LoadError namespace :vendor do task(:molinillo) { abort "Install the automatiek gem to be able to vendor gems." } task(:thor) { abort "Install the automatiek gem to be able to vendor gems." } task(:postit) { abort "Install the automatiek gem to be able to vendor gems." } + task("net-http-persistent") { abort "Install the automatiek gem to be able to vendor gems." } end end @@ -331,3 +342,5 @@ task :build => ["man:build"] task :release => ["man:require", "man:build"] task :default => :spec + +Dir["task/*.{rb,rake}"].each(&method(:load)) diff --git a/bin/rubocop b/bin/rubocop index 78641c589f..c7d45e23fb 100755 --- a/bin/rubocop +++ b/bin/rubocop @@ -10,7 +10,7 @@ bundler_spec.dependencies.each do |dep| gem dep.name, dep.requirement end -gem "rubocop", "= 0.41.2" +gem "rubocop", "= 0.47.1" Gem.finish_resolve if Gem.respond_to?(:finish_resolve) diff --git a/bundler.gemspec b/bundler.gemspec index f67c538fd6..ef848e9e8d 100644 --- a/bundler.gemspec +++ b/bundler.gemspec @@ -7,8 +7,12 @@ require "bundler/version" Gem::Specification.new do |s| s.name = "bundler" s.version = Bundler::VERSION - s.licenses = ["MIT"] - s.authors = ["André Arko", "Samuel Giddins"] + s.license = "MIT" + s.authors = [ + "André Arko", "Samuel Giddins", "Chris Morris", "James Wen", "Tim Moore", + "André Medeiros", "Jessica Lynn Suttles", "Terence Lee", "Carl Lerche", + "Yehuda Katz" + ] s.email = ["team@bundler.io"] s.homepage = "http://bundler.io" s.summary = "The best way to manage your application's dependencies" @@ -27,7 +31,7 @@ Gem::Specification.new do |s| 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.files += Dir.glob("man/**/*") s.bindir = "exe" s.executables = %w(bundle bundler) diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000000..534dd941a7 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,30 @@ +# Docs: Contributing and developing Bundler + +_If you're looking for documentation on how to use Bundler: visit [bundler.io](http://bundler.io/), or run `bundle help` from the command line. You may also be interested in [troubleshooting common issues](TROUBLESHOOTING.md) found when using Bundler._ + +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. + +If you'd like to help make Bundler better, you totally rock! Thanks for helping us make Bundler better. + +## Contributing + +* [Overview & getting started](contributing/README.md) +* [How you can help: your first contributions!](contributing/HOW_YOU_CAN_HELP.md) +* [Bug triage](contributing/BUG_TRIAGE.md) +* [Getting help](contributing/GETTING_HELP.md) +* [Filing issues](contributing/ISSUES.md) +* [Community](contributing/COMMUNITY.md) + +## Development + +* [Overview](development/README.md) +* [Development setup](development/SETUP.md) +* [Submitting pull requests](development/PULL_REQUESTS.md) +* [Adding new features](development/NEW_FEATURES.md) +* [Releasing Bundler](development/RELEASING.md) + +## Documentation + +* [Overview](documentation/README.md) +* [Writing docs for man pages](documentation/WRITING.md) +* [Documentation vision](documentation/VISION.md) diff --git a/doc/TROUBLESHOOTING.md b/doc/TROUBLESHOOTING.md new file mode 100644 index 0000000000..e9187c50cd --- /dev/null +++ b/doc/TROUBLESHOOTING.md @@ -0,0 +1,64 @@ +# Troubleshooting common issues + +Stuck using Bundler? Browse these common issues before [filing a new issue](contributing/ISSUES.md). + +## Permission denied when installing bundler + +Certain operating systems such as MacOS and Ubuntu have versions of Ruby that require elevated privileges to install gems. + + ERROR: While executing gem ... (Gem::FilePermissionError) + You don't have write permissions for the /Library/Ruby/Gems/2.0.0 directory. + +There are multiple ways to solve this issue. You can install bundler with elevated privilges using `sudo` or `su`. + + sudo gem install bundler + +If you cannot elevated your privileges or do not want to globally install Bundler, you can use the `--user-install` option. + + gem install bundler --user-install + +This will install Bundler into your home directory. Note that you will need to append `~/.gem/ruby/<ruby version>/bin` to your `$PATH` variable to use `bundle`. + +## Heroku errors + +Please open a ticket with [Heroku](https://www.heroku.com) if you're having trouble deploying. They have a professional support team who can help you resolve Heroku issues far better than the Bundler team can. If the problem that you are having turns out to be a bug in Bundler itself, [Heroku support](https://www.heroku.com/support) can get the exact details to us. + +## Other problems + +First, figure out exactly what it is that you're trying to do (see [XY Problem](http://xyproblem.info/)). Then, go to the [Bundler documentation website](http://bundler.io) and see if we have instructions on how to do that. + +Second, check [the compatibility +list](http://bundler.io/compatibility.html), and make sure that the version of Bundler that you are using works with the versions of Ruby and Rubygems that you are using. To see your versions: + + # Bundler version + bundle -v + + # Ruby version + ruby -v + + # Rubygems version + gem -v + +If these instructions don't work, or you can't find any appropriate instructions, you can try these troubleshooting steps: + + # Remove user-specific gems and git repos + rm -rf ~/.bundle/ ~/.gem/bundler/ ~/.gems/cache/bundler/ + + # Remove system-wide git repos and git checkouts + rm -rf $GEM_HOME/bundler/ $GEM_HOME/cache/bundler/ + + # Remove project-specific settings + rm -rf .bundle/ + + # Remove project-specific cached gems and repos + rm -rf vendor/cache/ + + # Remove the saved resolve of the Gemfile + rm -rf Gemfile.lock + + # Uninstall the rubygems-bundler and open_gem gems + rvm gemset use global # if using rvm + gem uninstall rubygems-bundler open_gem + + # Try to install one more time + bundle install diff --git a/doc/contributing/BUG_TRIAGE.md b/doc/contributing/BUG_TRIAGE.md new file mode 100644 index 0000000000..6667805ac1 --- /dev/null +++ b/doc/contributing/BUG_TRIAGE.md @@ -0,0 +1,36 @@ +# Bug triage + +Triaging is the work of processing tickets that have been opened by users. Common tasks include verifying bugs, categorizing tickets, and ensuring there's enough information to reproduce the bug for anyone who wants to try to fix it. + +We've created an [issues guide](ISSUES.md) to walk users through the process of how to report an issue with the Bundler project. We also have a [troubleshooting guide](../TROUBLESHOOTING.md) to diagnose common problems. + +Not every ticket will be a bug in Bundler's code, but open tickets usually mean that the there is something we could improve to help that user. Sometimes that means writing additional documentation or making error messages clearer. + +## Triaging existing issues + +When you're looking at a ticket, here are the main questions to ask: + + * Can I reproduce this bug myself? + * Are the steps to reproduce the bug clearly documented in the ticket? + * Which versions of Bundler (1.1.x, 1.2.x, git, etc.) manifest this bug? + * Which operating systems (OS X, Windows, Ubuntu, CentOS, etc.) manifest this bug? + * Which rubies (MRI, JRuby, Rubinius, etc.) and which versions (1.8.7, 1.9.3, etc.) have this bug? + +If you can't reproduce the issue, chances are good that the bug has been fixed already (hurrah!). That's a good time to post to the ticket explaining what you did and how it worked. + +If you can reproduce an issue, you're well on your way to fixing it. :) + +## Fixing your triaged bug + +Everyone is welcome and encouraged to fix any open bug, improve an error message or add documentation. If you have a fix or an improvement to a ticket that you would like to contribute, we have a small guide to help: + + 1. Discuss the fix on the existing issue. Coordinating with everyone else saves duplicate work and serves as a great way to get suggestions and ideas if you need any. + 2. Base your commits on the correct branch. Bugfixes for 1.x versions of Bundler should be based on the matching 1-x-stable branch. + 3. Review the [pull request guide](../development/PULL_REQUESTS.md). + 4. Commit the code with at least one test covering your changes to a named branch in your fork. + 5. Put a line in the [CHANGELOG](../../CHANGELOG.md) summarizing your changes under the next release under the “Bugfixes” heading. + 6. Send us a [pull request](https://help.github.com/articles/using-pull-requests) from your bugfix branch. + +## Duplicates! + +Finally, the ticket may be a duplicate of another older ticket. If you notice a ticket is a duplicate, simply comment on the ticket noting the original ticket’s number. For example, you could say “This is a duplicate of issue #42, and can be closed”. diff --git a/doc/contributing/COMMUNITY.md b/doc/contributing/COMMUNITY.md new file mode 100644 index 0000000000..c25ef63eeb --- /dev/null +++ b/doc/contributing/COMMUNITY.md @@ -0,0 +1,13 @@ +# Community + +Community is an important part of all we do. If you’d like to be part of the Bundler community, you can jump right in and start helping make Bundler better for everyone who uses it. + +It would be tremendously helpful to have more people answering questions about Bundler (and often simply about [Rubygems](https://github.com/rubygems/rubygems) or Ruby itself) in our [issue tracker](https://github.com/bundler/bundler/issues) or on [Stack Overflow](http://stackoverflow.com/questions/tagged/bundler). + +Additional documentation and explanation is always helpful, too. If you have any suggestions for the Bundler website [bundler.io](http://bundler.io), we would absolutely love it if you opened an issue or pull request on the [bundler-site](https://github.com/bundler/bundler-site) repository. + +Sharing your experiences and discoveries by writing them up is a valuable way to help others who have similar problems or experiences in the future. You can write a blog post, create an example and commit it to Github, take screenshots, or make videos. + +Publishing examples of how Bundler is used helps 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. + +Finally, 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. diff --git a/doc/contributing/GETTING_HELP.md b/doc/contributing/GETTING_HELP.md new file mode 100644 index 0000000000..55ccb5f5e9 --- /dev/null +++ b/doc/contributing/GETTING_HELP.md @@ -0,0 +1,11 @@ +# Getting help + +If you have any questions after reading the documentation for contributing, please feel free to contact either [@indirect](https://github.com/indirect), [@segiddins](https://github.com/segiddins), or [@RochesterinNYC](https://github.com/RochesterinNYC). They are all happy to provide help working through your first bug fix or thinking through the problem you’re trying to resolve. + +The best ways to get in touch are: + +* [Bundler Slack](https://bundler.slack.com). + * Not a member of the Slack? Join the Bundler team slack [here](http://slack.bundler.io/)! +* [Bundler mailing list](http://groups.google.com/group/ruby-bundler) + +You may also find our guide on [filing issues](ISSUES.md) to be helpful as well! diff --git a/doc/contributing/HOW_YOU_CAN_HELP.md b/doc/contributing/HOW_YOU_CAN_HELP.md new file mode 100644 index 0000000000..b1e90de506 --- /dev/null +++ b/doc/contributing/HOW_YOU_CAN_HELP.md @@ -0,0 +1,27 @@ +# How you can help: your first commits! + +If you’re interested in contributing to Bundler, that’s awesome! We’d love your help. + +If at any point you get stuck, here's how to [get in touch with the Bundler team for help](GETTING_HELP.md). + +## First contribution suggestions + +We track [small bugs and features](https://github.com/bundler/bundler/labels/contribution%3A%20small) so that anyone who wants to help can start with something that's not too overwhelming. + +Generally, great ways to get started helping out with Bundler are: + + - using prerelease versions (run `gem install bundler --pre`) + - [reporting bugs you encounter or suggesting new features](https://github.com/bundler/bundler/issues/new) + - see our [issues guide](ISSUES.md) for help on filing issues + - see the [new features documentation](../development/NEW_FEATURES.md) for more + - adding to or editing [the Bundler documentation website](http://bundler.io) and [Bundler man pages](http://bundler.io/man/bundle.1.html) + - [checking issues for completeness](BUG_TRIAGE.md) + - closing issues that are not complete + - adding a failing test for reproducible [reported bugs](https://github.com/bundler/bundler/issues) + - reviewing [pull requests](https://github.com/bundler/bundler/pulls) and suggesting improvements + - improving existing code, including suggestions from [PullReview](https://www.pullreview.com/github/bundler/bundler/reviews/master) or [CodeClimate](https://codeclimate.com/github/bundler/bundler) + - writing code (no patch is too small! fix typos or bad whitespace) + - get started setting up your dev environment with [these instructions](../development/DEVELOPMENT_SETUP.md) + - backfilling [unit tests](https://github.com/bundler/bundler/tree/master/spec/bundler) for modules that lack [coverage](https://codeclimate.com/github/bundler/bundler/coverage) + +If nothing on those lists looks good, [talk to us](http://slack.bundler.io/), and we'll figure out what you can help with. We can absolutely use your help, no matter what level of programming skill you have at the moment. diff --git a/ISSUES.md b/doc/contributing/ISSUES.md index e0bc56cafc..df4fa67e97 100644 --- a/ISSUES.md +++ b/doc/contributing/ISSUES.md @@ -1,63 +1,20 @@ -# Bundler Issues +# Filing Issues: a guide So! You're having problems with Bundler. This file is here to help. If you're running into an error, try reading the rest of this file for help. If you can't figure out how to solve your problem, there are also instructions on how to report a bug. +Before filing an issue, check our [troubleshooting guide](../TROUBLESHOOTING.md) for quick fixes to common issues. + ## Documentation 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/man/bundle.1.html) or [Bundler Command Line Reference](http://bundler.io/v1.11/commands.html). -## Troubleshooting - -### Heroku errors - -Please open a ticket with [Heroku](https://www.heroku.com) if you're having trouble deploying. They have a professional support team who can help you resolve Heroku issues far better than the Bundler team can. If the problem that you are having turns out to be a bug in Bundler itself, [Heroku support](https://www.heroku.com/support) can get the exact details to us. - -### Other problems - -First, figure out exactly what it is that you're trying to do (see [XY Problem](http://xyproblem.info/)). Then, go to the [Bundler documentation website](http://bundler.io) and see if we have instructions on how to do that. - -Second, check [the compatibility -list](http://bundler.io/compatibility.html), and make sure that the version of Bundler that you are -using works with the versions of Ruby and Rubygems that you are using. To see your versions: - - # Bundler version - bundle -v - - # Ruby version - ruby -v - - # Rubygems version - gem -v - -If these instructions don't work, or you can't find any appropriate instructions, you can try these troubleshooting steps: - - # Remove user-specific gems and git repos - rm -rf ~/.bundle/ ~/.gem/bundler/ ~/.gems/cache/bundler/ - - # Remove system-wide git repos and git checkouts - rm -rf $GEM_HOME/bundler/ $GEM_HOME/cache/bundler/ - - # Remove project-specific settings - rm -rf .bundle/ - - # Remove project-specific cached gems and repos - rm -rf vendor/cache/ - - # Remove the saved resolve of the Gemfile - rm -rf Gemfile.lock - - # Uninstall the rubygems-bundler and open_gem gems - rvm gemset use global # if using rvm - gem uninstall rubygems-bundler open_gem - - # Try to install one more time - bundle install - ## Reporting unresolved problems -Hopefully the troubleshooting steps above resolved your problem. If things still aren't working the way you expect them to, please let us know so that we can diagnose and hopefully fix the problem you're having. +Check our [troubleshooting common issues guide](../TROUBLESHOOTING.md) and see if your issues is resolved using the steps provided. + +Hopefully the troubleshooting steps above resolved your problem! If things still aren't working the way you expect them to, please let us know so that we can diagnose and hopefully fix the problem you're having. **The best way to report a bug is by providing a reproduction script.** See these examples: @@ -87,12 +44,6 @@ If your version of Bundler does not have the `bundle env` command, then please i - Whether you have the `rubygems-bundler` gem, which can break gem executables (run `gem list rubygems-bundler`) - Whether you have the `open_gem` gem, which can cause rake activation conflicts (run `gem list open_gem`) -If you are using Rails 2.3, please also include: - - - Your `boot.rb` file - - Your `preinitializer.rb` file - - Your `environment.rb` file - If you have either `rubygems-bundler` or `open_gem` installed, please try removing them and then following the troubleshooting steps above before opening a new ticket. [Create a gist](https://gist.github.com) containing all of that information, then visit the [Bundler issue tracker](https://github.com/bundler/bundler/issues) and [create a ticket](https://github.com/bundler/bundler/issues/new) describing your problem and linking to your gist. diff --git a/doc/contributing/README.md b/doc/contributing/README.md new file mode 100644 index 0000000000..3b04aabcdc --- /dev/null +++ b/doc/contributing/README.md @@ -0,0 +1,38 @@ +# Contributing to Bundler + +Welcome to Bundler! We are so happy that you're here. We know it can be daunting to joining a new open source project, so here's a quick overview of what you can expect from this documentation. + +*Something missing?* Send us a [pull request](../development/PULL_REQUESTS.md)! + +**Recommended first steps** + +- Join us in the Bundler slack! Generate an invite [here](http://slack.bundler.io/) +- *If you're interested in helping with code:* + - Get a quick overview of our [development process](../development/README.md) + - [Setup your machine for development](../development/SETUP.md) + - Checkout [How you can help: your first contributions!](HOW_YOU_CAN_HELP.md) for a list of suggestions to get started +- *If you're interested in helping with documentation:* + - Read up on [how documentation works for Bundler](../documentation/README.md) + - Learn about our [documentation vision](../documentation/VISION.md) + +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). + +##[How you can help: your first contributions!](HOW_YOU_CAN_HELP.md) + +A detailed overview of how to get started contributing to Bundler, including a long list of suggestions for your first project. + +##[Bug triage](BUG_TRIAGE.md) + +Want to take a stab at processing issues? Start here. + +##[Getting help](GETTING_HELP.md) + +How to get in touch with folks who can help when you're stuck. Don't worry! This happens to all of us. We're really nice, we promise. + +##[Filing issues](ISSUES.md) + +We see a lot of issues in the Bundler repo! Use this guide to file informative, actionable issues. + +##[Community](COMMUNITY.md) + +Learn more about our goals for the Bundler community and the ways you can help us build better together. diff --git a/doc/development/NEW_FEATURES.md b/doc/development/NEW_FEATURES.md new file mode 100644 index 0000000000..9688ef8a42 --- /dev/null +++ b/doc/development/NEW_FEATURES.md @@ -0,0 +1,10 @@ +# Adding New Features + +If you would like to add a new feature to Bundler, please follow these steps: + + 1. [Create an issue](https://github.com/bundler/bundler/issues/new) with the [`feature-request` label](https://github.com/bundler/bundler/labels/type:%20feature-request) 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. Send us a [pull request](PULL_REQUESTS.md) from your feature branch. + +If you don't hear back immediately, don’t get discouraged! We all have day jobs, but we respond to most tickets within a day or two. diff --git a/doc/development/PULL_REQUESTS.md b/doc/development/PULL_REQUESTS.md new file mode 100644 index 0000000000..37129f8ecb --- /dev/null +++ b/doc/development/PULL_REQUESTS.md @@ -0,0 +1,40 @@ +# Submitting Pull Requests + +Before you submit a pull request, please remember to do the following: + +1. Check your code format and style +2. Run the test suite +3. Use a meaningful commit message without tags + +## Code formatting + +Make sure the code formatting and styling adheres to the guidelines. We use Rubocop for this. Lack of formatting adherence will result in automatic Travis build failures. + + $ bin/rubocop -a + +## Tests + +Prior to submitting your PR, please run the test suite: + + $ bin/rspec + +If you are unable to run the entire test suite, please run the unit test suite and at least the integration specs related to the command or domain of Bundler that your code changes relate to. + +Ex. For a pull request that changes something with `bundle update`, you might run: + + $ bin/rspec spec/bundler + $ bin/rspec spec/commands/update_spec.rb + +## Commit messages + +Please ensure that the commit messages included in the pull request __do not__ have the following: + - `@tag` Github user or team references (ex. `@indirect` or `@bundler/core`) + - `#id` references to issues or pull requests (ex. `#43` or `bundler/bundler-site#12`) + +If you want to use these mechanisms, please instead include them in the pull request description. This prevents multiple notifications or references being created on commit rebases or pull request/branch force pushes. + +Additionally, do not use `[ci skip]` or `[skip ci]` mechanisms in your pull request titles/descriptions or commit messages. Every potential commit and pull request should run through Bundler's CI system. This applies to all changes/commits (ex. even a change to just documentation or the removal of a comment). + +## CHANGELOG.md + +Don't forget to add your changes into the CHANGELOG! If you're submitting documentation, note the changes under the "Documentation" heading. diff --git a/doc/development/README.md b/doc/development/README.md new file mode 100644 index 0000000000..54cb4a3342 --- /dev/null +++ b/doc/development/README.md @@ -0,0 +1,19 @@ +# Development + +So, you're ready to start contributing to Bundler! You've come to the right place. Here you'll find an overview of how to work on Bundler locally and a description of the process from code to release. + +##[Development setup](SETUP.md) + +Guidelines for setting up your local development environment. + +##[Submitting pull requests](PULL_REQUESTS.md) + +An overview of our preferred PR process, including how to run the test suite and what to expect when you submit code for review. + +##[Adding new features](NEW_FEATURES.md) + +Guidelines for proposing and writing new features for Bundler. + +##[Releasing Bundler](RELEASING.md) + +A broad-strokes overview of the release process adhered to by the Bundler core team. diff --git a/doc/development/RELEASING.md b/doc/development/RELEASING.md new file mode 100644 index 0000000000..eb496470dc --- /dev/null +++ b/doc/development/RELEASING.md @@ -0,0 +1,9 @@ +# Releasing + +_Release process documentation is in progress, see [PR 5252](https://github.com/bundler/bundler/pull/5252)._ + +## Beta testing + +Early releases require heavy testing, especially across various system setups. We :heart: testers, and are big fans of anyone who can run `gem install bundler --pre` and try out upcoming releases in their development and staging environments. + +There may not always be prereleases or beta versions of Bundler. The Bundler team will tweet from the [@bundlerio account](http://twitter.com/bundlerio) when a prerelease or beta version becomes available. You are also always welcome to try checking out master and building a gem yourself if you want to try out the latest changes. diff --git a/doc/development/SETUP.md b/doc/development/SETUP.md new file mode 100644 index 0000000000..10f8dd30f1 --- /dev/null +++ b/doc/development/SETUP.md @@ -0,0 +1,29 @@ +# Development setup + +Bundler doesn't use a Gemfile to list development dependencies, because when we tried it we couldn't tell if we were awake or it was just another level of dreams. To work on Bundler, you'll probably want to do a couple of things. + +1. Install `groff-base` and `graphviz` packages using your package manager, e.g for ubuntu + + $ sudo apt-get install graphviz groff-base -y + + and for OS X (with brew installed) + + $ brew install graphviz homebrew/dupes/groff + +2. Install Bundler's development dependencies + + $ bin/rake spec:deps + +3. Run the test suite, to make sure things are working + + $ bin/rake spec + +4. Set up a shell alias to run Bundler from your clone, e.g. a Bash alias: + + $ alias dbundle='BUNDLE_TRAMPOLINE_DISABLE=1 ruby -I /path/to/bundler/lib /path/to/bundler/exe/bundle' + + The `BUNDLE_TRAMPOLINE_DISABLE` environment variable ensures that the version of Bundler in `/path/to/bundler/lib` will be used. Without that environment setting, Bundler will automatically download, install, and run the version of Bundler listed in `Gemfile.lock`. With that set up, you can test changes you've made to Bundler by running `dbundle`, without interfering with the regular `bundle` command. + +## Debugging with `pry` + +To dive into the code with Pry: `RUBYOPT=-rpry dbundle` to require pry and then run commands. diff --git a/doc/documentation/README.md b/doc/documentation/README.md new file mode 100644 index 0000000000..7ebb05401c --- /dev/null +++ b/doc/documentation/README.md @@ -0,0 +1,29 @@ +# Documentation + +Code needs explanation, and sometimes those who know the code well have trouble explaining it to someone just getting into it. Because of that, we welcome documentation suggestions and patches from everyone, especially if they are brand new to using Bundler. + +Currently, Bundler has two main sources of documentation: + +1. built-in `help` (including usage information and man pages) +2. [Bundler documentation site](http://bundler.io) + +If you have a suggestion or proposed change for [bundler.io](http://bundler.io), please open an issue or send a pull request to the [bundler-site](https://github.com/bundler/bundler-site) repository. + +Not sure where to write documentation? In general, follow these guidelines: + +* For an explanation of a specific Bundler command (ex: `bundle clean`): make changes to the man pages +* For longer explanations or usage guides (ex: "Using Bundler with Rails"): create new documentation within the [bundler-site](https://github.com/bundler/bundler-site) repository + +If you are unsure where to begin, ping [@feministy](https://github.com/feministy) or [@indirect](https://github.com/indirect) in the Bundler Slack ([get an invite here](../contributing/GETTING_HELP.md)). + +## [Writing docs for man pages)](WRITING.md) + +If you’d like to submit a patch to the man pages, you're in the right place! Details on editing and previewing changes to man pages can be found here. + +## [Documentation vision](VISION.md) + +Just like Bundler, we have a grand plan (really, a wish list of sorts) for Bundler documentation. Preview our hopes and dreams for our documentation here. + +## Translations + +We don't currently have any translations, but please reach out to us if you would like to help get this going. diff --git a/doc/documentation/VISION.md b/doc/documentation/VISION.md new file mode 100644 index 0000000000..3d660ee36d --- /dev/null +++ b/doc/documentation/VISION.md @@ -0,0 +1,26 @@ +# Documentation vision + +Currently, documentation for using Bundler is spread across two places: + +1. built-in `help` (including usage information and man pages) +2. [Bundler documentation site](http://bundler.io) + +Additional documentation about using Bundler to publish gems can also be found on the [RubyGems guides](http://guides.rubygems.org/). + +## Goals + +Bundler documentation should provide users with assistance: + +1. Installing Bundler +2. Using Bundler to manage an application's dependencies +3. Using Bundler to create, package, and publish gems + +Our goal is to provide three types of documentation: + +* High level overviews that provide topical guidance +* Step-by-step tutorials +* Command-specific reference material for the CLI + +Additionally, this documentation should be readily available in a logical place and easy to follow. + +Someday, we'd like to create deep-dive reference material about the inner workings of Bundler. However, while this is part of our overall vision for Bundler documentation, it is not the focus of our current work. diff --git a/doc/documentation/WRITING.md b/doc/documentation/WRITING.md new file mode 100644 index 0000000000..ce6dcce653 --- /dev/null +++ b/doc/documentation/WRITING.md @@ -0,0 +1,54 @@ +# Writing docs for man pages + +A primary source of help for Bundler users are the man pages: the output printed when you run `bundle help` (or `bundler help`). These pages can be a little tricky to format and preview, but are pretty straightforward once you get the hang of it. + +_Note: `bundler` and `bundle` may be used interchangeably in the CLI. This guide uses `bundle` because it's cuter._ + +## What goes in man pages? + +We use man pages for Bundler commands used in the CLI (command line interface). They can vary in length from large (see `bundle install`) to very short (see `bundle clean`). + +To see a list of commands available in the Bundler CLI, type: + + $ bundle help + +Our goal is to have a man page for every command. + +Don't see a man page for a command? Make a new page and send us a PR! We also welcome edits to existing pages. + +## Creating a new man page + +To create a new man page, simply create a new `.ronn` file in the `man/` directory. + +For example: to create a man page for the command `bundle cookies` (not a real command, sadly), I would create a file `man/bundle-cookies.ronn` and add my documentation there. + +## Formatting + +Our man pages use ronn formatting, a combination of Markdown and standard man page conventions. It can be a little weird getting used to it at first, especially if you've used Markdown a lot. + +[The ronn guide formatting guide](https://rtomayko.github.io/ronn/ronn.7.html) provides a good overview of the common types of formatting. + +In general, make your page look like the other pages: utilize sections like `##OPTIONS` and formatting like code blocks and definition lists where appropriate. + +If you're not sure if the formatting looks right, that's ok! Make a pull request with what you've got and we'll take a peek. + +## Previewing + +To preview your changes as they will print out for Bundler users, you'll need to run a series of commands: + +``` +$ rake spec:deps +$ rake man:build +$ man man/bundle-cookies.1 +``` + +If you make more changes to `bundle-cookies.ronn`, you'll need to run the `rake man:build` again before previewing. + +## Testing + +We have tests for our documentation! The most important test file to run before you make your pull request is the one for the `help` command and another for documentation quality. + +``` +$ rspec ./spec/commands/help_spec.rb +$ rspec ./spec/quality_spec.rb +``` diff --git a/exe/bundle b/exe/bundle index ec88ea7552..ab2bde16e2 100755 --- a/exe/bundle +++ b/exe/bundle @@ -2,7 +2,10 @@ # frozen_string_literal: true # Exit cleanly from an early interrupt -Signal.trap("INT") { exit 1 } +Signal.trap("INT") do + Bundler.ui.debug("\n#{caller.join("\n")}") if defined?(Bundler) + exit 1 +end update = "update".start_with?(ARGV.first || " ") && ARGV.find {|a| a.start_with?("--bundler") } update &&= update =~ /--bundler(?:=(.+))?/ && $1 || "> 0.a" diff --git a/lib/bundler.rb b/lib/bundler.rb index 8806ae01ef..63a9f5784c 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -3,9 +3,10 @@ require "fileutils" require "pathname" require "rbconfig" require "thread" +require "tmpdir" + require "bundler/errors" require "bundler/environment_preserver" -require "bundler/gem_remote_fetcher" require "bundler/plugin" require "bundler/rubygems_ext" require "bundler/rubygems_integration" @@ -30,6 +31,7 @@ module Bundler autoload :FeatureFlag, "bundler/feature_flag" autoload :GemHelper, "bundler/gem_helper" autoload :GemHelpers, "bundler/gem_helpers" + autoload :GemRemoteFetcher, "bundler/gem_remote_fetcher" autoload :GemVersionPromoter, "bundler/gem_version_promoter" autoload :Graph, "bundler/graph" autoload :Index, "bundler/index" @@ -38,8 +40,6 @@ module Bundler autoload :LazySpecification, "bundler/lazy_specification" autoload :LockfileParser, "bundler/lockfile_parser" autoload :MatchPlatform, "bundler/match_platform" - autoload :Mirror, "bundler/mirror" - autoload :Mirrors, "bundler/mirror" autoload :RemoteSpecification, "bundler/remote_specification" autoload :Resolver, "bundler/resolver" autoload :Retry, "bundler/retry" @@ -133,7 +133,7 @@ module Bundler @locked_gems ||= if defined?(@definition) && @definition definition.locked_gems - elsif Bundler.default_lockfile.exist? + elsif Bundler.default_lockfile.file? lock = Bundler.read_file(Bundler.default_lockfile) LockfileParser.new(lock) end @@ -143,8 +143,44 @@ module Bundler "#{Bundler.rubygems.ruby_engine}/#{Bundler.rubygems.config_map[:ruby_version]}" end + def user_home + @user_home ||= begin + home = Bundler.rubygems.user_home + + warning = if home.nil? + "Your home directory is not set." + elsif !File.directory?(home) + "`#{home}` is not a directory." + elsif !File.writable?(home) + "`#{home}` is not writable." + end + + if warning + user_home = tmp_home_path(Etc.getlogin, warning) + Bundler.ui.warn "#{warning}\nBundler will use `#{user_home}' as your home directory temporarily.\n" + user_home + else + Pathname.new(home) + end + end + end + + def tmp_home_path(login, warning) + login ||= "unknown" + path = Pathname.new(Dir.tmpdir).join("bundler", "home") + SharedHelpers.filesystem_access(path) do |tmp_home_path| + unless tmp_home_path.exist? + tmp_home_path.mkpath + tmp_home_path.chmod(0o777) + end + tmp_home_path.join(login).tap(&:mkpath) + end + rescue => e + raise e.exception("#{warning}\nBundler also failed to create a temporary home directory at `#{path}':\n#{e}") + end + def user_bundle_path - Pathname.new(Bundler.rubygems.user_home).join(".bundle") + Pathname.new(user_home).join(".bundle") end def home @@ -258,6 +294,11 @@ EOF with_clean_env { Kernel.exec(*args) } end + def local_platform + return Gem::Platform::RUBY if settings[:force_ruby_platform] + Gem::Platform.local + end + def default_gemfile SharedHelpers.default_gemfile end @@ -329,17 +370,23 @@ EOF def sudo(str) SUDO_MUTEX.synchronize do prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " " - Your user account isn't allowed to install to the system Rubygems. + Your user account isn't allowed to install to the system RubyGems. You can cancel this installation and run: bundle install --path vendor/bundle to install the gems into ./vendor/bundle/, or you can enter your password - and install the bundled gems to Rubygems using sudo. + and install the bundled gems to RubyGems using sudo. Password: PROMPT + unless @prompted_for_sudo ||= system(%(sudo -k -p "#{prompt}" true)) + raise SudoNotPermittedError, + "Bundler requires sudo access to install at the moment. " \ + "Try installing again, granting Bundler sudo access when prompted, or installing into a different path." + end + `sudo -p "#{prompt}" #{str}` end end @@ -395,6 +442,12 @@ EOF end def reset! + reset_paths! + Plugin.reset! + reset_rubygems! + end + + def reset_paths! @root = nil @settings = nil @definition = nil @@ -403,9 +456,10 @@ EOF @locked_gems = nil @bundle_path = nil @bin_path = nil + @user_home = nil + end - Plugin.reset! - + def reset_rubygems! return unless defined?(@rubygems) && @rubygems rubygems.undo_replacements rubygems.reset @@ -443,7 +497,10 @@ EOF def configure_gem_path(env = ENV, settings = self.settings) blank_home = env["GEM_HOME"].nil? || env["GEM_HOME"].empty? if settings[:disable_shared_gems] - env["GEM_PATH"] = nil + # this needs to be empty string to cause + # PathSupport.split_gem_path to only load up the + # Bundler --path setting as the GEM_PATH. + env["GEM_PATH"] = "" elsif blank_home || Bundler.rubygems.gem_dir != bundle_path.to_s possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path] paths = possibles.flatten.compact.uniq.reject(&:empty?) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 3bdc3db9e9..cf75c52733 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -13,6 +13,7 @@ module Bundler Bundler.ui = UI::Shell.new raise e ensure + warn_on_outdated_bundler Bundler::SharedHelpers.print_major_deprecations! end @@ -22,10 +23,12 @@ module Bundler def initialize(*args) super - Bundler.reset! custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] - ENV["BUNDLE_GEMFILE"] = File.expand_path(custom_gemfile) if custom_gemfile && !custom_gemfile.empty? + if custom_gemfile && !custom_gemfile.empty? + ENV["BUNDLE_GEMFILE"] = File.expand_path(custom_gemfile) + Bundler.reset_paths! + end Bundler.settings[:retry] = options[:retry] if options[:retry] @@ -36,8 +39,10 @@ module Bundler ensure self.options ||= {} Bundler.settings.cli_flags_given = !options.empty? + unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) Bundler.ui.level = "debug" if options["verbose"] + unprinted_warnings.each {|w| Bundler.ui.warn(w) } if ENV["RUBYGEMS_GEMDEPS"] && !ENV["RUBYGEMS_GEMDEPS"].empty? Bundler.ui.warn( @@ -59,30 +64,21 @@ module Bundler def help(cli = nil) case cli - when "gemfile" then command = "gemfile.5" + when "gemfile" then command = "gemfile" when nil then command = "bundle" else command = "bundle-#{cli}" end - manpages = %w( - bundle - bundle-config - bundle-exec - bundle-gem - bundle-install - bundle-package - bundle-update - bundle-platform - gemfile.5 - ) - - if manpages.include?(command) - root = File.expand_path("../man", __FILE__) - - if Bundler.which("man") && root !~ %r{^file:/.+!/META-INF/jruby.home/.+} - Kernel.exec "man #{root}/#{command}" + man_path = File.expand_path("../../../man", __FILE__) + man_pages = Hash[Dir.glob(File.join(man_path, "*")).grep(/.*\.\d*\Z/).collect do |f| + [File.basename(f, ".*"), f] + end] + + if man_pages.include?(command) + if Bundler.which("man") && man_path !~ %r{^file:/.+!/META-INF/jruby.home/.+} + Kernel.exec "man #{man_pages[command]}" else - puts File.read("#{root}/#{command}.txt") + puts File.read("#{man_path}/#{File.basename(man_pages[command])}.txt") end elsif command_path = Bundler.which("bundler-#{cli}") Kernel.exec(command_path, "--help") @@ -91,6 +87,9 @@ module Bundler end end + # Ensure `bundle help --no-color` is valid + all_commands["help"].disable_class_options = false + def self.handle_no_command_error(command, has_namespace = $thor_runner) if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command) return Bundler::Plugin.exec_command(command, ARGV[1..-1]) @@ -184,11 +183,9 @@ module Bundler map "i" => "install" def install require "bundler/cli/install" - no_install = Bundler.settings[:no_install] - Bundler.settings[:no_install] = false if no_install == true - Install.new(options.dup).run - ensure - Bundler.settings[:no_install] = no_install unless no_install.nil? + Bundler.settings.temporary(:no_install => false) do + Install.new(options.dup).run + end end desc "update [OPTIONS]", "update the current environment" @@ -215,14 +212,16 @@ module Bundler "Update ruby specified in Gemfile.lock" method_option "bundler", :type => :string, :lazy_default => "> 0.a", :banner => "Update the locked version of bundler" - method_option "patch", :type => :boolean, :hide => true, :banner => + method_option "patch", :type => :boolean, :banner => "Prefer updating only to next patch version" - method_option "minor", :type => :boolean, :hide => true, :banner => + method_option "minor", :type => :boolean, :banner => "Prefer updating only to next minor version" - method_option "major", :type => :boolean, :hide => true, :banner => + method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)" - method_option "strict", :type => :boolean, :hide => true, :banner => - "Do not allow any gem to be updated past latest --patch/--minor/--major" + method_option "strict", :type => :boolean, :banner => + "Do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", :type => :boolean, :banner => + "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." def update(*gems) require "bundler/cli/update" Update.new(options, gems).run @@ -245,6 +244,13 @@ module Bundler # TODO: 2.0 remove `bundle list` map %w(list) => "show" + desc "info GEM [OPTIONS]", "Show information for the given gem" + method_option "path", :type => :boolean, :banner => "Print full path to gem" + def info(gem_name) + require "bundler/cli/info" + Info.new(options, gem_name).run + end + 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, @@ -268,16 +274,26 @@ module Bundler in the given source. Calling outdated with [GEM [GEM]] will only check for newer 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. + + For more information on patch level options (--major, --minor, --patch, + --update-strict) see documentation on the same options on the update command. D + method_option "group", :aliases => "--group", :type => :string, :banner => "List gems from a specific group" + method_option "groups", :aliases => "--groups", :type => :boolean, :banner => "List gems organized by groups" method_option "local", :type => :boolean, :banner => "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems" 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" - method_option "major", :type => :boolean, :banner => "Only list major newer versions" - method_option "minor", :type => :boolean, :banner => "Only list minor newer versions" - method_option "patch", :type => :boolean, :banner => "Only list patch newer versions" + method_option "update-strict", :type => :boolean, :banner => + "Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "minor", :type => :boolean, :banner => "Prefer updating only to next minor version" + method_option "major", :type => :boolean, :banner => "Prefer updating to next major version (default)" + method_option "patch", :type => :boolean, :banner => "Prefer updating only to next patch version" + method_option "filter-major", :type => :boolean, :banner => "Only list major newer versions" + method_option "filter-minor", :type => :boolean, :banner => "Only list minor newer versions" + method_option "filter-patch", :type => :boolean, :banner => "Only list patch newer versions" method_option "parseable", :aliases => "--porcelain", :type => :boolean, :banner => "Use minimal formatting for more parseable output" def outdated(*gems) @@ -399,6 +415,8 @@ module Bundler Viz.new(options.dup).run end + old_gem = instance_method(:gem) + desc "gem GEM [OPTIONS]", "Creates a skeleton for creating a rubygem" method_option :exe, :type => :boolean, :default => false, :aliases => ["--bin", "-b"], :desc => "Generate a binary executable for your library." method_option :coc, :type => :boolean, :desc => "Generate a code of conduct file. Set a default with `bundle config gem.coc true`." @@ -410,10 +428,30 @@ module Bundler 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 end + commands["gem"].tap do |gem_command| + def gem_command.run(instance, args = []) + arity = 1 # name + + require "bundler/cli/gem" + cmd_args = args + [instance] + cmd_args.unshift(instance.options) + + cmd = begin + Gem.new(*cmd_args) + rescue ArgumentError => e + instance.class.handle_argument_error(self, e, args, arity) + end + + cmd.run + end + end + + undef_method(:gem) + define_method(:gem, old_gem) + private :gem + def self.source_root File.expand_path(File.join(File.dirname(__FILE__), "templates")) end @@ -436,15 +474,15 @@ module Bundler Platform.new(options).run end - desc "inject GEM VERSION ...", "Add the named gem(s), with version requirements, to the resolved Gemfile" - def inject(name, version, *gems) + desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile" + def inject(name, version) SharedHelpers.major_deprecation "The `inject` command has been replaced by the `add` command" require "bundler/cli/inject" - Inject.new(options, name, version, gems).run + Inject.new(options, name, version).run end desc "lock", "Creates a lockfile without installing" - method_option "update", :type => :array, :lazy_default => [], :banner => + method_option "update", :type => :array, :lazy_default => true, :banner => "ignore the existing lockfile, update all gems by default, or update list of given gems" method_option "local", :type => :boolean, :default => false, :banner => "do not attempt to fetch remote gemspecs and use the local gem cache only" @@ -455,9 +493,19 @@ module Bundler method_option "full-index", :type => :boolean, :default => false, :banner => "Fall back to using the single-file index of all gems" method_option "add-platform", :type => :array, :default => [], :banner => - "add a new platform to the lockfile" + "Add a new platform to the lockfile" method_option "remove-platform", :type => :array, :default => [], :banner => - "remove a platform from the lockfile" + "Remove a platform from the lockfile" + method_option "patch", :type => :boolean, :banner => + "If updating, prefer updating only to next patch version" + method_option "minor", :type => :boolean, :banner => + "If updating, prefer updating only to next minor version" + method_option "major", :type => :boolean, :banner => + "If updating, prefer updating to next major version (default)" + method_option "strict", :type => :boolean, :banner => + "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" + method_option "conservative", :type => :boolean, :banner => + "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" def lock require "bundler/cli/lock" Lock.new(options).run @@ -545,5 +593,26 @@ module Bundler command.reject!(&:empty?) Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" end + + def self.warn_on_outdated_bundler + return if Bundler.settings[:disable_version_check] + + latest = Fetcher::CompactIndex. + new(nil, Source::Rubygems::Remote.new(URI("https://rubygems.org")), nil). + send(:compact_index_client). + instance_variable_get(:@cache). + dependencies("bundler"). + map {|d| Gem::Version.new(d.first) }. + max + return unless latest + + current = Gem::Version.new(VERSION) + return if current >= latest + + Bundler.ui.warn "The latest bundler is #{latest}, but you are currently running #{current}.\nTo update, run `gem install bundler#{" --pre" if latest.prerelease?}`" + rescue + nil + end + private_class_method :warn_on_outdated_bundler end end diff --git a/lib/bundler/cli/check.rb b/lib/bundler/cli/check.rb index 3f504ff621..057a7e5695 100644 --- a/lib/bundler/cli/check.rb +++ b/lib/bundler/cli/check.rb @@ -28,7 +28,7 @@ module Bundler not_installed.each {|s| Bundler.ui.error " * #{s.name} (#{s.version})" } Bundler.ui.warn "Install missing gems with `bundle install`" exit 1 - elsif !Bundler.default_lockfile.exist? && Bundler.settings[:frozen] + elsif !Bundler.default_lockfile.file? && Bundler.settings[:frozen] Bundler.ui.error "This bundle has been frozen, but there is no #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} present" exit 1 else diff --git a/lib/bundler/cli/clean.rb b/lib/bundler/cli/clean.rb index 895b6567fa..5eba09c6bc 100644 --- a/lib/bundler/cli/clean.rb +++ b/lib/bundler/cli/clean.rb @@ -8,7 +8,7 @@ module Bundler end def run - require_path_or_force + require_path_or_force unless options[:"dry-run"] Bundler.load.clean(options[:"dry-run"]) end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 491ea04a11..c1e108d752 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -1,6 +1,23 @@ # frozen_string_literal: true module Bundler module CLI::Common + def self.output_post_install_messages(messages) + return if Bundler.settings["ignore_messages"] + messages.to_a.each do |name, msg| + print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"] + end + end + + def self.print_post_install_message(name, msg) + Bundler.ui.confirm "Post-install message from #{name}:" + Bundler.ui.info msg + end + + def self.output_without_groups_message + return unless Bundler.settings.without.any? + Bundler.ui.confirm without_groups_message + end + def self.without_groups_message groups = Bundler.settings.without group_list = [groups[0...-1].join(", "), groups[-1..-1]]. @@ -52,5 +69,18 @@ module Bundler message += "\nDid you mean #{suggestions}?" if suggestions message end + + def self.configure_gem_version_promoter(definition, options) + patch_level = patch_level_options(options) + raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1 + definition.gem_version_promoter.tap do |gvp| + gvp.level = patch_level.first || :major + gvp.strict = options[:strict] || options["update-strict"] + end + end + + def self.patch_level_options(options) + [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) } + end end end diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index 728662024b..ae27983240 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -14,11 +14,11 @@ module Bundler end def otool_available? - system("otool --version 2>#{Bundler::NULL} >#{Bundler::NULL}") + Bundler.which("otool") end def ldd_available? - !system("ldd --help 2>#{Bundler::NULL} >#{Bundler::NULL}").nil? + Bundler.which("ldd") end def dylibs_darwin(path) diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb index 4f238bbb59..62f7bc26cb 100644 --- a/lib/bundler/cli/exec.rb +++ b/lib/bundler/cli/exec.rb @@ -91,6 +91,12 @@ module Bundler "#!/usr/bin/env jruby\n", "#!#{Gem.ruby}\n", ] + + if File.zero?(file) + Bundler.ui.warn "#{file} is empty" + return false + end + first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) } possibilities.any? {|shebang| first_line.start_with?(shebang) } end diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 4dc0dbdb6b..7fa005289e 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -29,8 +29,11 @@ module Bundler constant_name = name.gsub(/-[_-]*(?![_-]|$)/) { "::" }.gsub(/([_-]+|(::)|^)(.|$)/) { $2.to_s + $3.upcase } constant_array = constant_name.split("::") - git_user_name = `git config user.name`.chomp - git_user_email = `git config user.email`.chomp + git_installed = Bundler.git_present? + + git_author_name = git_installed ? `git config user.name`.chomp : "" + github_username = git_installed ? `git config github.user`.chomp : "" + git_user_email = git_installed ? `git config user.email`.chomp : "" config = { :name => name, @@ -39,19 +42,18 @@ module Bundler :makefile_path => "#{underscored_name}/#{underscored_name}", :constant_name => constant_name, :constant_array => constant_array, - :author => git_user_name.empty? ? "TODO: Write your name" : git_user_name, + :author => git_author_name.empty? ? "TODO: Write your name" : git_author_name, :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email, :test => options[:test], :ext => options[:ext], :exe => options[:exe], :bundler_version => bundler_dependency_version, - :git_user_name => git_user_name.empty? ? "[USERNAME]" : git_user_name + :github_username => github_username.empty? ? "[USERNAME]" : github_username } ensure_safe_gem_name(name, constant_array) 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", @@ -66,6 +68,8 @@ module Bundler bin/setup ) + templates.merge!("gitignore.tt" => ".gitignore") if Bundler.git_present? + if test_framework = ask_and_set_test_framework config[:test] = test_framework config[:test_framework_version] = TEST_FRAMEWORK_VERSIONS[test_framework] @@ -122,7 +126,10 @@ module Bundler end templates.each do |src, dst| - thor.template("newgem/#{src}", target.join(dst), config) + destination = target.join(dst) + SharedHelpers.filesystem_access(destination) do + thor.template("newgem/#{src}", destination, config) + end end executables.each do |file| @@ -131,14 +138,18 @@ module Bundler path.chmod(executable) end - Bundler.ui.info "Initializing git repo in #{target}" - Dir.chdir(target) do - `git init` - `git add .` + if Bundler.git_present? + Bundler.ui.info "Initializing git repo in #{target}" + Dir.chdir(target) do + `git init` + `git add .` + end end # Open gemspec in editor open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit] + rescue Errno::EEXIST => e + raise GenericSystemCallError.new(e, "There was a conflict while creating the new gem.") end private @@ -202,10 +213,23 @@ module Bundler if name =~ /^\d/ Bundler.ui.error "Invalid gem name #{name} Please give a name which does not start with numbers." exit 1 - elsif constant_array.inject(Object) {|c, s| (c.const_defined?(s) && c.const_get(s)) || break } - Bundler.ui.error "Invalid gem name #{name} constant #{constant_array.join("::")} is already in use. Please choose another gem name." - exit 1 end + + constant_name = constant_array.join("::") + + existing_constant = constant_array.inject(Object) do |c, s| + defined = begin + c.const_defined?(s) + rescue NameError + Bundler.ui.error "Invalid gem name #{name} -- `#{constant_name}` is an invalid constant name" + exit 1 + end + (defined && c.const_get(s)) || break + end + + return unless existing_constant + Bundler.ui.error "Invalid gem name #{name} constant #{constant_name} is already in use. Please choose another gem name." + exit 1 end def open_editor(editor, file) diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb new file mode 100644 index 0000000000..4465fba9d4 --- /dev/null +++ b/lib/bundler/cli/info.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +require "bundler/cli/common" + +module Bundler + class CLI::Info + attr_reader :gem_name, :options + def initialize(options, gem_name) + @options = options + @gem_name = gem_name + end + + def run + spec = spec_for_gem(gem_name) + + spec_not_found(gem_name) unless spec + return print_gem_path(spec) if @options[:path] + print_gem_info(spec) + end + + private + + def spec_for_gem(gem_name) + spec = Bundler.definition.specs.find {|s| s.name == gem_name } + spec || default_gem_spec(gem_name) + end + + def default_gem_spec(gem_name) + return unless Gem::Specification.respond_to?(:find_all_by_name) + gem_spec = Gem::Specification.find_all_by_name(gem_name).last + return gem_spec if gem_spec && gem_spec.respond_to?(:default_gem?) && gem_spec.default_gem? + end + + def spec_not_found(gem_name) + raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(gem_name, Bundler.definition.dependencies) + end + + def print_gem_path(spec) + Bundler.ui.info spec.full_gem_path + end + + def print_gem_info(spec) + gem_info = String.new + gem_info << " * #{spec.name} (#{spec.version}#{spec.git_version})\n" + gem_info << "\tSummary: #{spec.summary}\n" if spec.summary + gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage + gem_info << "\tPath: #{spec.full_gem_path}\n" + gem_info << "\tDefault Gem: yes" if spec.respond_to?(:default_gem?) && spec.default_gem? + Bundler.ui.info gem_info + end + end +end diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb index 9d1d08120a..cf35e4985b 100644 --- a/lib/bundler/cli/inject.rb +++ b/lib/bundler/cli/inject.rb @@ -2,13 +2,13 @@ module Bundler class CLI::Inject attr_reader :options, :name, :version, :group, :source, :gems - def initialize(options, name, version, gems) + def initialize(options, name, version) @options = options @name = name @version = version || last_version_number @group = options[:group] @source = options[:source] - @gems = gems + @gems = [] end def run @@ -18,6 +18,8 @@ module Bundler # Build an array of Dependency objects out of the arguments deps = [] + # when `inject` support addition of more than one gem, then this loop will + # help. Currently this loop is running once. gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source| ops = Gem::Requirement::OPS.map {|key, _val| key } has_op = ops.any? {|op| gem_version.start_with? op } diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index f1632c9ee2..752c5e9c55 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require "bundler/cli/common" + module Bundler class CLI::Install attr_reader :options @@ -57,12 +59,9 @@ module Bundler if options["binstubs"] Bundler::SharedHelpers.major_deprecation \ - "the --binstubs option will be removed in favor of `bundle binstubs`" + "The --binstubs option will be removed in favor of `bundle binstubs`" end - # rubygems plugins sometimes hook into the gem install process - Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? definition = Bundler.definition @@ -72,21 +71,17 @@ module Bundler 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 + Bundler::CLI::Common.output_without_groups_message if Bundler.settings[:path] absolute_path = File.expand_path(Bundler.settings[:path]) relative_path = absolute_path.sub(File.expand_path(".") + File::SEPARATOR, "." + File::SEPARATOR) Bundler.ui.confirm "Bundled gems are installed into #{relative_path}." else - Bundler.ui.confirm "Use `bundle show [gemname]` to see where a bundled gem is installed." + Bundler.ui.confirm "Use `bundle info [gemname]` to see where a bundled gem is installed." end - unless Bundler.settings["ignore_messages"] - installer.post_install_messages.to_a.each do |name, msg| - print_post_install_message(name, msg) unless Bundler.settings["ignore_messages.#{name}"] - end - end + Bundler::CLI::Common.output_post_install_messages installer.post_install_messages warn_ambiguous_gems @@ -135,12 +130,6 @@ module Bundler end end - def confirm_without_groups - return unless Bundler.settings.without.any? - require "bundler/cli/common" - Bundler.ui.confirm Bundler::CLI::Common.without_groups_message - end - def dependencies_count_for(definition) count = definition.dependencies.count "#{count} Gemfile #{count == 1 ? "dependency" : "dependencies"}" @@ -151,11 +140,6 @@ module Bundler "#{count} #{count == 1 ? "gem" : "gems"} now installed" end - def print_post_install_message(name, msg) - Bundler.ui.confirm "Post-install message from #{name}:" - Bundler.ui.info msg - end - def check_for_group_conflicts if options[:without] && options[:with] conflicting_groups = options[:without] & options[:with] @@ -170,8 +154,8 @@ module Bundler def check_for_options_conflicts if (options[:path] || options[:deployment]) && options[:system] error_message = String.new - error_message << "You have specified both a path to install your gems to as well as --system. Please choose.\n" if options[:path] - error_message << "You have specified both --deployment as well as --system. Please choose.\n" if options[:deployment] + error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path] + error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment] raise InvalidOption.new(error_message) end end diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index a6a95f895c..beeb2e4633 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require "bundler/cli/common" + module Bundler class CLI::Lock attr_reader :options @@ -17,14 +19,13 @@ module Bundler ui = Bundler.ui Bundler.ui = UI::Silent.new if print - gems = options[:update] Bundler::Fetcher.disable_endpoint = options["full-index"] - if gems && !gems.empty? - definition = Bundler.definition(:gems => gems) - else - definition = Bundler.definition(true) - end + update = options[:update] + update = { :gems => update, :lock_shared_dependencies => options[:conservative] } if update.is_a?(Array) + definition = Bundler.definition(update) + + Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) if options[:update] options["remove-platform"].each do |platform| definition.remove_platform(platform) @@ -32,7 +33,7 @@ module Bundler options["add-platform"].each do |platform_string| platform = Gem::Platform.new(platform_string) - if platform.to_a.compact == %w(unknown) + if platform.to_s == "unknown" Bundler.ui.warn "The platform `#{platform_string}` is unknown to RubyGems " \ "and adding it will likely lead to resolution errors" end diff --git a/lib/bundler/cli/open.rb b/lib/bundler/cli/open.rb index 0bbfa63d34..9a21f6811c 100644 --- a/lib/bundler/cli/open.rb +++ b/lib/bundler/cli/open.rb @@ -13,7 +13,8 @@ module Bundler def run editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? } return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor - path = Bundler::CLI::Common.select_spec(name, :regex_match).full_gem_path + return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match) + path = spec.full_gem_path Dir.chdir(path) do command = Shellwords.split(editor) + [path] Bundler.with_clean_env do diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index de71075522..93e50b10b6 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -4,6 +4,7 @@ require "bundler/cli/common" module Bundler class CLI::Outdated attr_reader :options, :gems + def initialize(options, gems) @options = options @gems = gems @@ -21,7 +22,11 @@ module Bundler Bundler.definition.validate_runtime! current_specs = Bundler.ui.silence { Bundler.load.specs } current_dependencies = {} - Bundler.ui.silence { Bundler.load.dependencies.each {|dep| current_dependencies[dep.name] = dep } } + Bundler.ui.silence do + Bundler.load.dependencies.each do |dep| + current_dependencies[dep.name] = dep + end + end definition = if gems.empty? && sources.empty? # We're doing a full update @@ -30,7 +35,23 @@ module Bundler Bundler.definition(:gems => gems, :sources => sources) end - definition_resolution = proc { options["local"] ? definition.resolve_with_cache! : definition.resolve_remotely! } + Bundler::CLI::Common.configure_gem_version_promoter( + Bundler.definition, + options + ) + + # the patch level options imply strict is also true. It wouldn't make + # sense otherwise. + strict = options[:strict] || + Bundler::CLI::Common.patch_level_options(options).any? + + filter_options_patch = options.keys & + %w(filter-major filter-minor filter-patch) + + definition_resolution = proc do + options[:local] ? definition.resolve_with_cache! : definition.resolve_remotely! + end + if options[:parseable] Bundler.ui.silence(&definition_resolution) else @@ -38,83 +59,169 @@ module Bundler end Bundler.ui.info "" + outdated_gems_by_groups = {} + outdated_gems_list = [] - out_count = 0 # Loop through the current specs - gemfile_specs, dependency_specs = current_specs.partition {|spec| current_dependencies.key? spec.name } - [gemfile_specs.sort_by(&:name), dependency_specs.sort_by(&:name)].flatten.each do |current_spec| + gemfile_specs, dependency_specs = current_specs.partition do |spec| + current_dependencies.key? spec.name + end + + (gemfile_specs + dependency_specs).sort_by(&:name).each do |current_spec| next if !gems.empty? && !gems.include?(current_spec.name) dependency = current_dependencies[current_spec.name] + active_spec = retrieve_active_spec(strict, definition, current_spec) - if options["strict"] - active_spec = definition.specs.detect {|spec| spec.name == current_spec.name && spec.platform == current_spec.platform } - else - active_specs = definition.index[current_spec.name].select {|spec| spec.platform == current_spec.platform }.sort_by(&:version) - if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 - active_spec = active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } + next if active_spec.nil? + if filter_options_patch.any? + update_present = update_present_via_semver_portions(current_spec, active_spec, options) + next unless update_present + end + + gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version) + if gem_outdated || (current_spec.git_version != active_spec.git_version) + groups = nil + if dependency && !options[:parseable] + groups = dependency.groups.join(", ") end - active_spec = active_specs.last - if options[:major] || options[:minor] || options[:patch] - update_present = update_present_via_semver_portions(current_spec, active_spec, options) - active_spec = nil unless update_present + outdated_gems_list << { :active_spec => active_spec, + :current_spec => current_spec, + :dependency => dependency, + :groups => groups } + + outdated_gems_by_groups[groups] ||= [] + outdated_gems_by_groups[groups] << { :active_spec => active_spec, + :current_spec => current_spec, + :dependency => dependency, + :groups => groups } + end + + Bundler.ui.debug "from #{active_spec.loaded_from}" + end + + if outdated_gems_list.empty? + display_nothing_outdated_message(filter_options_patch) + else + unless options[:parseable] + if options[:pre] + Bundler.ui.info "Outdated gems included in the bundle (including " \ + "pre-releases):" + else + Bundler.ui.info "Outdated gems included in the bundle:" end end - next if active_spec.nil? + options_include_groups = [:group, :groups].select do |v| + options.keys.include?(v.to_s) + end - gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version) - git_outdated = current_spec.git_version != active_spec.git_version - if gem_outdated || git_outdated - unless options[:parseable] - if out_count == 0 - if options["pre"] - Bundler.ui.info "Outdated gems included in the bundle (including pre-releases):" + if options_include_groups.any? + ordered_groups = outdated_gems_by_groups.keys.compact.sort + [nil, ordered_groups].flatten.each do |groups| + gems = outdated_gems_by_groups[groups] + contains_group = if groups + groups.split(",").include?(options[:group]) + else + options[:group] == "group" + end + + next if (!options[:groups] && !contains_group) || gems.nil? + + unless options[:parseable] + if groups + Bundler.ui.info "===== Group #{groups} =====" else - Bundler.ui.info "Outdated gems included in the bundle:" + Bundler.ui.info "===== Without group =====" end end + + gems.each do |gem| + print_gem( + gem[:current_spec], + gem[:active_spec], + gem[:dependency], + groups, + options_include_groups.any? + ) + end + end + else + outdated_gems_list.each do |gem| + print_gem( + gem[:current_spec], + gem[:active_spec], + gem[:dependency], + gem[:groups], + options_include_groups.any? + ) end + end - spec_version = "#{active_spec.version}#{active_spec.git_version}" - current_version = "#{current_spec.version}#{current_spec.git_version}" - dependency_version = %(, requested #{dependency.requirement}) if dependency && dependency.specific? + exit 1 + end + end - if dependency && !options[:parseable] - groups = dependency.groups.join(", ") - pl = (dependency.groups.length > 1) ? "s" : "" - groups = " in group#{pl} \"#{groups}\"" - end + private - spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, installed #{current_version}#{dependency_version})" - if options[:parseable] - Bundler.ui.info spec_outdated_info.to_s.rstrip - else - Bundler.ui.info " * #{spec_outdated_info}#{groups}".rstrip - end + def retrieve_active_spec(strict, definition, current_spec) + if strict + active_spec = definition.find_resolved_spec(current_spec) + else + active_specs = definition.find_indexed_specs(current_spec) + if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 + active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } + end + active_spec = active_specs.last + end - out_count += 1 + active_spec + end + + def display_nothing_outdated_message(filter_options_patch) + unless options[:parseable] + if filter_options_patch.any? + display = filter_options_patch.map do |o| + o.sub("filter-", "") + end.join(" or ") + + Bundler.ui.info "No #{display} updates to display.\n" + else + Bundler.ui.info "Bundle up to date!\n" end - Bundler.ui.debug "from #{active_spec.loaded_from}" end + end - if out_count.zero? - Bundler.ui.info "Bundle up to date!\n" unless options[:parseable] + def print_gem(current_spec, active_spec, dependency, groups, options_include_groups) + spec_version = "#{active_spec.version}#{active_spec.git_version}" + current_version = "#{current_spec.version}#{current_spec.git_version}" + + if dependency && dependency.specific? + dependency_version = %(, requested #{dependency.requirement}) + end + + spec_outdated_info = "#{active_spec.name} (newest #{spec_version}, " \ + "installed #{current_version}#{dependency_version})" + + output_message = if options[:parseable] + spec_outdated_info.to_s + elsif options_include_groups || !groups + " * #{spec_outdated_info}" else - exit 1 + " * #{spec_outdated_info} in groups \"#{groups}\"" end - end - private + Bundler.ui.info output_message.rstrip + end def check_for_deployment_mode if Bundler.settings[:frozen] - error_message = "You are trying to check outdated gems in deployment mode. " \ - "Run `bundle outdated` elsewhere.\n" \ - "\nIf this is a development machine, remove the #{Bundler.default_gemfile} freeze" \ - "\nby running `bundle install --no-deployment`." - raise ProductionError, error_message + raise ProductionError, "You are trying to check outdated gems in " \ + "deployment mode. Run `bundle outdated` elsewhere.\n" \ + "\nIf this is a development machine, remove the " \ + "#{Bundler.default_gemfile} freeze" \ + "\nby running `bundle install --no-deployment`." end end @@ -123,16 +230,15 @@ module Bundler active_major = active_spec.version.segments.first update_present = false + update_present = active_major > current_major if options["filter-major"] - update_present = active_major > current_major if options[:major] - - if !update_present && (options[:minor] || options[:patch]) && current_major == active_major + if !update_present && (options["filter-minor"] || options["filter-patch"]) && current_major == active_major current_minor = get_version_semver_portion_value(current_spec, 1) active_minor = get_version_semver_portion_value(active_spec, 1) - update_present = active_minor > current_minor if options[:minor] + update_present = active_minor > current_minor if options["filter-minor"] - if !update_present && options[:patch] && current_minor == active_minor + if !update_present && options["filter-patch"] && current_minor == active_minor current_patch = get_version_semver_portion_value(current_spec, 2) active_patch = get_version_semver_portion_value(active_spec, 2) diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb index 77e845a603..47d4470aec 100644 --- a/lib/bundler/cli/show.rb +++ b/lib/bundler/cli/show.rb @@ -64,6 +64,7 @@ module Bundler else definition.resolve_with_cache! end + Bundler.reset! definition.specs end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 5aac47bd09..8a7541c259 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require "bundler/cli/common" + module Bundler class CLI::Update attr_reader :options, :gems @@ -27,7 +29,6 @@ module Bundler names = Bundler.locked_gems.specs.map(&:name) gems.each do |g| next if names.include?(g) - require "bundler/cli/common" raise GemNotFound, Bundler::CLI::Common.gem_not_found_message(g, names) end @@ -36,15 +37,11 @@ module Bundler gems.concat(specs.map(&:name)) end - Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby]) + Bundler.definition(:gems => gems, :sources => sources, :ruby => options[:ruby], + :lock_shared_dependencies => options[:conservative]) end - patch_level = [:major, :minor, :patch].select {|v| options.keys.include?(v.to_s) } - raise InvalidOption, "Provide only one of the following options: #{patch_level.join(", ")}" unless patch_level.length <= 1 - Bundler.definition.gem_version_promoter.tap do |gvp| - gvp.level = patch_level.first || :major - gvp.strict = options[:strict] - end + Bundler::CLI::Common.configure_gem_version_promoter(Bundler.definition, options) Bundler::Fetcher.disable_endpoint = options["full-index"] @@ -54,11 +51,8 @@ module Bundler Bundler.settings[:jobs] = opts["jobs"] if opts["jobs"] - # rubygems plugins sometimes hook into the gem install process - Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) - Bundler.definition.validate_runtime! - Installer.install Bundler.root, Bundler.definition, opts + installer = Installer.install Bundler.root, Bundler.definition, opts Bundler.load.cache if Bundler.app_cache.exist? if Bundler.settings[:clean] && Bundler.settings[:path] @@ -67,15 +61,8 @@ module Bundler end Bundler.ui.confirm "Bundle updated!" - without_groups_messages - end - - private - - def without_groups_messages - return unless Bundler.settings.without.any? - require "bundler/cli/common" - Bundler.ui.confirm Bundler::CLI::Common.without_groups_message + Bundler::CLI::Common.output_without_groups_message + Bundler::CLI::Common.output_post_install_messages installer.post_install_messages end end end diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb index 75e6affff9..418c462a93 100644 --- a/lib/bundler/cli/viz.rb +++ b/lib/bundler/cli/viz.rb @@ -21,7 +21,7 @@ module Bundler rescue StandardError => e raise unless e.message =~ /GraphViz not installed or dot not in PATH/ Bundler.ui.error e.message - Bundler.ui.warn "Please install GraphViz. On a Mac with homebrew, you can run `brew install graphviz`." + Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`." end end end diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb new file mode 100644 index 0000000000..3ed05ca484 --- /dev/null +++ b/lib/bundler/compact_index_client.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true +require "pathname" +require "set" + +module Bundler + class CompactIndexClient + DEBUG_MUTEX = Mutex.new + def self.debug + return unless ENV["DEBUG_COMPACT_INDEX"] + DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") } + end + + class Error < StandardError; end + + require "bundler/compact_index_client/cache" + require "bundler/compact_index_client/updater" + + attr_reader :directory + + # @return [Lambda] A lambda that takes an array of inputs and a block, and + # maps the inputs with the block in parallel. + # + attr_accessor :in_parallel + + def initialize(directory, fetcher) + @directory = Pathname.new(directory) + @updater = Updater.new(fetcher) + @cache = Cache.new(@directory) + @endpoints = Set.new + @info_checksums_by_name = {} + @parsed_checksums = false + @mutex = Mutex.new + @in_parallel = lambda do |inputs, &blk| + inputs.map(&blk) + end + end + + def names + Bundler::CompactIndexClient.debug { "/names" } + update(@cache.names_path, "names") + @cache.names + end + + def versions + Bundler::CompactIndexClient.debug { "/versions" } + update(@cache.versions_path, "versions") + versions, @info_checksums_by_name = @cache.versions + versions + end + + def dependencies(names) + Bundler::CompactIndexClient.debug { "dependencies(#{names})" } + in_parallel.call(names) do |name| + update_info(name) + @cache.dependencies(name).map {|d| d.unshift(name) } + end.flatten(1) + end + + def spec(name, version, platform = nil) + Bundler::CompactIndexClient.debug { "spec(name = #{name}, version = #{version}, platform = #{platform})" } + update_info(name) + @cache.specific_dependency(name, version, platform) + end + + def update_and_parse_checksums! + Bundler::CompactIndexClient.debug { "update_and_parse_checksums!" } + return @info_checksums_by_name if @parsed_checksums + update(@cache.versions_path, "versions") + @info_checksums_by_name = @cache.checksums + @parsed_checksums = true + end + + private + + def update(local_path, remote_path) + Bundler::CompactIndexClient.debug { "update(#{local_path}, #{remote_path})" } + unless synchronize { @endpoints.add?(remote_path) } + Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" } + return + end + @updater.update(local_path, url(remote_path)) + end + + def update_info(name) + Bundler::CompactIndexClient.debug { "update_info(#{name})" } + path = @cache.info_path(name) + checksum = @updater.checksum_for_file(path) + unless existing = @info_checksums_by_name[name] + Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since it is missing from versions" } + return + end + if checksum == existing + Bundler::CompactIndexClient.debug { "skipping updating info for #{name} since the versions checksum matches the local checksum" } + return + end + Bundler::CompactIndexClient.debug { "updating info for #{name} since the versions checksum #{existing} != the local checksum #{checksum}" } + update(path, "info/#{name}") + end + + def url(path) + path + end + + def synchronize + @mutex.synchronize { yield } + end + end +end diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb new file mode 100644 index 0000000000..e44f05dc7e --- /dev/null +++ b/lib/bundler/compact_index_client/cache.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true +require "digest/md5" + +module Bundler + class CompactIndexClient + class Cache + attr_reader :directory + + def initialize(directory) + @directory = Pathname.new(directory).expand_path + info_roots.each do |dir| + SharedHelpers.filesystem_access(dir) do + FileUtils.mkdir_p(dir) + end + end + end + + def names + lines(names_path) + end + + def names_path + directory.join("names") + end + + def versions + versions_by_name = Hash.new {|hash, key| hash[key] = [] } + info_checksums_by_name = {} + + lines(versions_path).each do |line| + name, versions_string, info_checksum = line.split(" ", 3) + info_checksums_by_name[name] = info_checksum || "" + versions_string.split(",").each do |version| + if version.start_with?("-") + version = version[1..-1].split("-", 2).unshift(name) + versions_by_name[name].delete(version) + else + version = version.split("-", 2).unshift(name) + versions_by_name[name] << version + end + end + end + + [versions_by_name, info_checksums_by_name] + end + + def versions_path + directory.join("versions") + end + + def checksums + checksums = {} + + lines(versions_path).each do |line| + name, _, checksum = line.split(" ", 3) + checksums[name] = checksum + end + + checksums + end + + def dependencies(name) + lines(info_path(name)).map do |line| + parse_gem(line) + end + end + + def info_path(name) + name = name.to_s + if name =~ /[^a-z0-9_-]/ + name += "-#{Digest::MD5.hexdigest(name).downcase}" + info_roots.last.join(name) + else + info_roots.first.join(name) + end + end + + def specific_dependency(name, version, platform) + pattern = [version, platform].compact.join("-") + return nil if pattern.empty? + + gem_lines = info_path(name).read + gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0] + gem_line ? parse_gem(gem_line) : nil + end + + private + + def lines(path) + return [] unless path.file? + lines = SharedHelpers.filesystem_access(path, :read, &:read).split("\n") + header = lines.index("---") + header ? lines[header + 1..-1] : lines + end + + def parse_gem(string) + version_and_platform, rest = string.split(" ", 2) + version, platform = version_and_platform.split("-", 2) + dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest + dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : [] + requirements = requirements ? requirements.map {|r| parse_dependency(r) } : [] + [version, platform, dependencies, requirements] + end + + def parse_dependency(string) + dependency = string.split(":") + dependency[-1] = dependency[-1].split("&") if dependency.size > 1 + dependency + end + + def info_roots + [ + directory.join("info"), + directory.join("info-special-characters"), + ] + end + end + end +end diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb new file mode 100644 index 0000000000..5aa480fdcc --- /dev/null +++ b/lib/bundler/compact_index_client/updater.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true +require "fileutils" +require "stringio" +require "tmpdir" +require "zlib" + +module Bundler + class CompactIndexClient + class Updater + class MisMatchedChecksumError < Error + def initialize(path, server_checksum, local_checksum) + @path = path + @server_checksum = server_checksum + @local_checksum = local_checksum + end + + def message + "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \ + "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})." + end + end + + def initialize(fetcher) + @fetcher = fetcher + end + + def update(local_path, remote_path, retrying = nil) + headers = {} + + Dir.mktmpdir("bundler-compact-index-") do |local_temp_dir| + local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) + + # first try to fetch any new bytes on the existing file + if retrying.nil? && local_path.file? + FileUtils.cp local_path, local_temp_path + headers["If-None-Match"] = etag_for(local_temp_path) + headers["Range"] = "bytes=#{local_temp_path.size}-" + else + # Fastly ignores Range when Accept-Encoding: gzip is set + headers["Accept-Encoding"] = "gzip" + end + + response = @fetcher.call(remote_path, headers) + return nil if response.is_a?(Net::HTTPNotModified) + + content = response.body + if response["Content-Encoding"] == "gzip" + content = Zlib::GzipReader.new(StringIO.new(content)).read + end + + mode = response.is_a?(Net::HTTPPartialContent) ? "a" : "w" + SharedHelpers.filesystem_access(local_temp_path) do + local_temp_path.open(mode) {|f| f << content } + end + + response_etag = (response["ETag"] || "").gsub(%r{\AW/}, "") + if etag_for(local_temp_path) == response_etag + SharedHelpers.filesystem_access(local_path) do + FileUtils.mv(local_temp_path, local_path) + end + return nil + end + + if retrying + raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path)) + end + + update(local_path, remote_path, :retrying) + end + end + + def etag_for(path) + sum = checksum_for_file(path) + sum ? %("#{sum}") : nil + end + + def checksum_for_file(path) + return nil unless path.file? + # This must use IO.read instead of Digest.file().hexdigest + # because we need to preserve \n line endings on windows when calculating + # the checksum + SharedHelpers.filesystem_access(path, :read) do + Digest::MD5.hexdigest(IO.read(path)) + end + end + end + end +end diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index 6180285942..cca40100ad 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -16,6 +16,7 @@ module Bundler 2.2 2.3 2.4 + 2.5 ).freeze KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze @@ -57,15 +58,15 @@ module Bundler end def mswin64? - Bundler::WINDOWS && Gem::Platform.local.os == "mswin64" && Gem::Platform.local.cpu == "x64" + Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mswin64" && Bundler.local_platform.cpu == "x64" end def mingw? - Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu != "x64" + Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu != "x64" end def x64_mingw? - Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu == "x64" + Bundler::WINDOWS && Bundler.local_platform != Gem::Platform::RUBY && Bundler.local_platform.os == "mingw32" && Bundler.local_platform.cpu == "x64" end (KNOWN_MINOR_VERSIONS + KNOWN_MAJOR_VERSIONS).each do |version| diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 0cb6e7e399..8e8897df02 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -102,15 +102,19 @@ module Bundler end @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version) - @gem_version_promoter = create_gem_version_promoter - add_current_platform unless Bundler.settings[:frozen] + converge_path_sources_to_gemspec_sources @path_changes = converge_paths - eager_unlock = expand_dependencies(@unlock[:gems]) - @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name) - @source_changes = converge_sources + + unless @unlock[:lock_shared_dependencies] + eager_unlock = expand_dependencies(@unlock[:gems]) + @unlock[:gems] = @locked_specs.for(eager_unlock).map(&:name) + end + + @gem_version_promoter = create_gem_version_promoter + @dependency_changes = converge_dependencies @local_changes = converge_locals @@ -135,17 +139,15 @@ module Bundler end def create_gem_version_promoter - locked_specs = begin + locked_specs = if @unlocking && @locked_specs.empty? && !@lockfile_contents.empty? # Definition uses an empty set of locked_specs to indicate all gems # are unlocked, but GemVersionPromoter needs the locked_specs # for conservative comparison. - locked = Bundler::LockfileParser.new(@lockfile_contents) - Bundler::SpecSet.new(locked.specs) + Bundler::SpecSet.new(@locked_gems.specs) else @locked_specs end - end GemVersionPromoter.new(locked_specs, @unlock[:gems]) end @@ -175,7 +177,7 @@ module Bundler rescue GemNotFound => e # Handle yanked gem gem_name, gem_version = extract_gem_info(e) locked_gem = @locked_specs[gem_name].last - raise if locked_gem.nil? || locked_gem.version.to_s != gem_version + raise if locked_gem.nil? || locked_gem.version.to_s != gem_version || !@remote raise GemNotFound, "Your bundle is locked to #{locked_gem}, but that version could not " \ "be found in any of the sources listed in your Gemfile. If you haven't changed sources, " \ "that means the author of #{locked_gem} has removed it. You'll need to update your bundle " \ @@ -247,7 +249,7 @@ module Bundler else # Run a resolve against the locally available gems Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}") - last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve) + last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms) end end end @@ -407,8 +409,10 @@ module Bundler "updated #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} to version control." unless explicit_flag + + suggested_command = Bundler.settings.locations("frozen")[:global] == "1" ? "bundle config --delete frozen" : "bundle install --no-deployment" msg << "\n\nIf this is a development machine, remove the #{Bundler.default_gemfile} " \ - "freeze \nby running `bundle install --no-deployment`." + "freeze \nby running `#{suggested_command}`." end added = [] @@ -460,12 +464,13 @@ module Bundler changed << "* #{name} from `#{gemfile_source_name}` to `#{lockfile_source_name}`" end + msg << "\n\n#{change_reason.split(", ").join("\n")}\n" msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? msg << "\n" - raise ProductionError, msg if added.any? || deleted.any? || changed.any? + raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed? end def validate_runtime! @@ -498,14 +503,10 @@ module Bundler end end - # TODO: refactor this so that `match_platform` can be called with two platforms - DummyPlatform = Struct.new(:platform) - class DummyPlatform; include MatchPlatform; end def validate_platforms! return if @platforms.any? do |bundle_platform| - bundle_platform = DummyPlatform.new(bundle_platform) Bundler.rubygems.platforms.any? do |local_platform| - bundle_platform.match_platform(local_platform) + MatchPlatform.platforms_match?(bundle_platform, local_platform) end end @@ -525,11 +526,19 @@ module Bundler end def add_current_platform - current_platform = Bundler.rubygems.platforms.last + current_platform = Bundler.local_platform add_platform(current_platform) if Bundler.settings[:specific_platform] add_platform(generic(current_platform)) end + def find_resolved_spec(current_spec) + specs.find_by_name_and_platform(current_spec.name, current_spec.platform) + end + + def find_indexed_specs(current_spec) + index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) + end + attr_reader :sources private :sources @@ -586,7 +595,8 @@ module Bundler locked_index = Index.new locked_index.use(@locked_specs.select {|s| source.can_lock?(s) }) - source.specs != locked_index + # order here matters, since Index#== is checking source.specs.include?(locked_index) + locked_index != source.specs end # Get all locals and override their matching sources. @@ -622,15 +632,20 @@ module Bundler gemspec_source || source end - def converge_sources - changes = false - + def converge_path_sources_to_gemspec_sources @locked_sources.map! do |source| converge_path_source_to_gemspec_source(source) end @locked_specs.each do |spec| spec.source &&= converge_path_source_to_gemspec_source(spec.source) end + @locked_deps.each do |dep| + dep.source &&= converge_path_source_to_gemspec_source(dep.source) + end + end + + def converge_sources + changes = false # Get the Rubygems sources from the Gemfile.lock locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } @@ -680,7 +695,8 @@ module Bundler dep.platforms.concat(@platforms.map {|p| Dependency::REVERSE_PLATFORM_MAP[p] }.flatten(1)).uniq! end end - Set.new(@dependencies) != Set.new(@locked_deps) + dependency_without_type = proc {|d| Gem::Dependency.new(d.name, *d.requirement.as_list) } + Set.new(@dependencies.map(&dependency_without_type)) != Set.new(@locked_deps.map(&dependency_without_type)) end # Remove elements from the locked specs that are expired. This will most @@ -738,8 +754,9 @@ module Bundler next unless other deps2 = other.dependencies.select {|d| d.type != :development } + runtime_dependencies = s.dependencies.select {|d| d.type != :development } # If the dependencies of the path source have changed, unlock it - next unless s.dependencies.sort == deps2.sort + next unless runtime_dependencies.sort == deps2.sort end converged << s @@ -810,8 +827,18 @@ module Bundler deps = [] dependencies.each do |dep| dep = Dependency.new(dep, ">= 0") unless dep.respond_to?(:name) - next unless remote || dep.current_platform? - dep.gem_platforms(@platforms).each do |p| + next if !remote && !dep.current_platform? + platforms = dep.gem_platforms(@platforms) + if platforms.empty? + mapped_platforms = dep.platforms.map {|p| Dependency::PLATFORM_MAP[p] } + Bundler.ui.warn \ + "The dependency #{dep} will be unused by any of the platforms Bundler is installing for. " \ + "Bundler is installing for #{@platforms.join ", "} but the dependency " \ + "is only for #{mapped_platforms.join ", "}. " \ + "To add those platforms to the bundle, " \ + "run `bundle lock --add-platform #{mapped_platforms.join " "}`." + end + platforms.each do |p| deps << DepProxy.new(dep, p) if remote || p == generic_local_platform end end @@ -888,7 +915,7 @@ module Bundler end def additional_base_requirements_for_resolve - return [] unless @locked_gems && Bundler.settings[:only_update_to_newer_versions] + return [] unless @locked_gems && Bundler.feature_flag.only_update_to_newer_versions? @locked_gems.specs.reduce({}) do |requirements, locked_spec| dep = Gem::Dependency.new(locked_spec.name, ">= #{locked_spec.version}") requirements[locked_spec.name] = DepProxy.new(dep, locked_spec.platform) diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 66162d741a..d2bac66cdb 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -17,6 +17,8 @@ module Bundler :ruby_21 => Gem::Platform::RUBY, :ruby_22 => Gem::Platform::RUBY, :ruby_23 => Gem::Platform::RUBY, + :ruby_24 => Gem::Platform::RUBY, + :ruby_25 => Gem::Platform::RUBY, :mri => Gem::Platform::RUBY, :mri_18 => Gem::Platform::RUBY, :mri_19 => Gem::Platform::RUBY, @@ -24,6 +26,8 @@ module Bundler :mri_21 => Gem::Platform::RUBY, :mri_22 => Gem::Platform::RUBY, :mri_23 => Gem::Platform::RUBY, + :mri_24 => Gem::Platform::RUBY, + :mri_25 => Gem::Platform::RUBY, :rbx => Gem::Platform::RUBY, :jruby => Gem::Platform::JAVA, :jruby_18 => Gem::Platform::JAVA, @@ -35,12 +39,16 @@ module Bundler :mswin_21 => Gem::Platform::MSWIN, :mswin_22 => Gem::Platform::MSWIN, :mswin_23 => Gem::Platform::MSWIN, + :mswin_24 => Gem::Platform::MSWIN, + :mswin_25 => Gem::Platform::MSWIN, :mswin64 => Gem::Platform::MSWIN64, :mswin64_19 => Gem::Platform::MSWIN64, :mswin64_20 => Gem::Platform::MSWIN64, :mswin64_21 => Gem::Platform::MSWIN64, :mswin64_22 => Gem::Platform::MSWIN64, :mswin64_23 => Gem::Platform::MSWIN64, + :mswin64_24 => Gem::Platform::MSWIN64, + :mswin64_25 => Gem::Platform::MSWIN64, :mingw => Gem::Platform::MINGW, :mingw_18 => Gem::Platform::MINGW, :mingw_19 => Gem::Platform::MINGW, @@ -48,11 +56,15 @@ module Bundler :mingw_21 => Gem::Platform::MINGW, :mingw_22 => Gem::Platform::MINGW, :mingw_23 => Gem::Platform::MINGW, + :mingw_24 => Gem::Platform::MINGW, + :mingw_25 => Gem::Platform::MINGW, :x64_mingw => Gem::Platform::X64_MINGW, :x64_mingw_20 => Gem::Platform::X64_MINGW, :x64_mingw_21 => Gem::Platform::X64_MINGW, :x64_mingw_22 => Gem::Platform::X64_MINGW, :x64_mingw_23 => Gem::Platform::X64_MINGW, + :x64_mingw_24 => Gem::Platform::X64_MINGW, + :x64_mingw_25 => Gem::Platform::X64_MINGW, }.freeze REVERSE_PLATFORM_MAP = {}.tap do |reverse_platform_map| diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index b064c80d4c..cdbae076f0 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -65,7 +65,7 @@ module Bundler case specs_by_name_and_version.size when 1 specs = specs_by_name_and_version.values.first - spec = specs.find {|s| s.match_platform(Gem::Platform.local) } || specs.first + spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first @gemspecs << spec @@ -393,7 +393,8 @@ module Bundler "as an option for #{command}, but it is invalid." end - message << " Valid options are: #{valid_keys.join(", ")}" + message << " Valid options are: #{valid_keys.join(", ")}." + message << " You may be able to resolve this by upgrading Bundler to the newest version." raise InvalidOption, message end end diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 69d05167e8..5e14f03265 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -91,6 +91,13 @@ module Bundler end def __swap__(spec) + without_type = proc {|d| Gem::Dependency.new(d.name, d.requirements_list.sort) } + if (extra_deps = spec.runtime_dependencies.map(&without_type).-(dependencies.map(&without_type))) && extra_deps.any? + Bundler.ui.debug "#{full_name} from #{remote} has corrupted API dependencies (API returned #{dependencies}, real spec has (#{spec.runtime_dependencies}))" + raise APIResponseMismatchError, + "Downloading #{full_name} revealed dependencies not in the API (#{extra_deps.map(&:to_s).join(", ")})." \ + "\nInstalling with `--full-index` should fix the problem." + end @remote_specification = spec end @@ -113,6 +120,8 @@ module Bundler @required_ruby_version = Gem::Requirement.new(v) end end + rescue => e + raise GemspecError, "There was an error parsing the metadata for the gem #{name} (#{version}): #{e.class}\n#{e}\nThe metadata was #{data.inspect}" end def build_dependency(name, requirements) diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb index 8baf698dff..7e9bcc2866 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -12,41 +12,49 @@ module Bundler print_gemfile = options.delete(:print_gemfile) print_gemspecs = options.delete(:print_gemspecs) - out = String.new("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" - out << " OpenSSL #{OpenSSL::OPENSSL_VERSION}\n" if defined?(OpenSSL::OPENSSL_VERSION) + out = String.new("## Environment\n\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" + out << "Platform #{Gem::Platform.local}\n" + out << "OpenSSL #{OpenSSL::OPENSSL_VERSION}\n" if defined?(OpenSSL::OPENSSL_VERSION) %w(rubygems-bundler open_gem).each do |name| specs = Bundler.rubygems.find_name(name) - out << " #{name} (#{specs.map(&:version).join(",")})\n" unless specs.empty? + out << "#{name} (#{specs.map(&:version).join(",")})\n" unless specs.empty? end - out << "\nBundler settings\n\n" unless Bundler.settings.all.empty? + out << "```\n" + + out << "\n## Bundler settings\n\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" + + return out unless SharedHelpers.in_bundle? if print_gemfile - out << "\n#{Bundler.default_gemfile.relative_path_from(SharedHelpers.pwd)}\n\n" - out << " " << read_file(Bundler.default_gemfile).gsub(/\n/, "\n ") << "\n" + out << "\n## Gemfile\n" + out << "\n### #{Bundler.default_gemfile.relative_path_from(SharedHelpers.pwd)}\n\n" + out << "```ruby\n" << read_file(Bundler.default_gemfile).chomp << "\n```\n" - out << "\n#{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}\n\n" - out << " " << read_file(Bundler.default_lockfile).gsub(/\n/, "\n ") << "\n" + out << "\n### #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}\n\n" + out << "```\n" << read_file(Bundler.default_lockfile).chomp << "\n```\n" end if print_gemspecs dsl = Dsl.new.tap {|d| d.eval_gemfile(Bundler.default_gemfile) } + out << "\n## Gemspecs\n" unless dsl.gemspecs.empty? dsl.gemspecs.each do |gs| - out << "\n#{File.basename(gs.loaded_from)}" - out << "\n\n " << read_file(gs.loaded_from).gsub(/\n/, "\n ") << "\n" + out << "\n### #{File.basename(gs.loaded_from)}" + out << "\n\n```ruby\n" << read_file(gs.loaded_from).chomp << "\n```\n" end end diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index 7681ea73ae..6ce8493ea7 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -52,6 +52,9 @@ module Bundler class CyclicDependencyError < BundlerError; status_code(21); end class GemfileLockNotFound < BundlerError; status_code(22); end class PluginError < BundlerError; status_code(29); end + class SudoNotPermittedError < BundlerError; status_code(30); end + class ThreadCreationError < BundlerError; status_code(33); end + class APIResponseMismatchError < BundlerError; status_code(34); end class GemfileEvalError < GemfileError; end class MarshalError < StandardError; end @@ -131,4 +134,24 @@ module Bundler status_code(28) end + + class NoSpaceOnDeviceError < PermissionError + def message + "There was an error while trying to #{action} `#{@path}`. " \ + "There was insufficient space remaining on the device." + end + + status_code(31) + end + + class GenericSystemCallError < BundlerError + attr_reader :underlying_error + + def initialize(underlying_error, message) + @underlying_error = underlying_error + super("#{message}\nThe underlying system error is #{@underlying_error.class}: #{@underlying_error}") + end + + status_code(32) + end end diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 80bf2a5150..150cac1e67 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -1,17 +1,22 @@ # frozen_string_literal: true module Bundler class FeatureFlag - def self.settings_flag(flag) + def self.settings_flag(flag, &default) unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s) raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key" end - define_method("#{flag}?") { Bundler.settings[flag] } + define_method("#{flag}?") do + value = Bundler.settings[flag] + value = instance_eval(&default) if value.nil? && !default.nil? + value + end end (1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } } - settings_flag :allow_offline_install - settings_flag :plugins + settings_flag(:allow_offline_install) { bundler_2_mode? } + settings_flag(:only_update_to_newer_versions) { bundler_2_mode? } + settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } def initialize(bundler_version) @bundler_version = Gem::Version.create(bundler_version) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 0e890d491c..9e208e4957 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -237,7 +237,7 @@ module Bundler Bundler.settings[:ssl_client_cert] raise SSLError if needs_ssl && !defined?(OpenSSL::SSL) - con = Net::HTTP::Persistent.new "bundler", :ENV + con = Bundler::Persistent::Net::HTTP::Persistent.new "bundler", :ENV if gem_proxy = Bundler.rubygems.configuration[:http_proxy] con.proxy = URI.parse(gem_proxy) if gem_proxy != :no_proxy end @@ -273,7 +273,7 @@ module Bundler Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, - Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH + Bundler::Persistent::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH ].freeze def bundler_cert_store diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index 9461368df5..97de88101b 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -3,10 +3,10 @@ require "bundler/fetcher/base" require "bundler/worker" module Bundler + autoload :CompactIndexClient, "bundler/compact_index_client" + class Fetcher class CompactIndex < Base - require "bundler/vendor/compact_index_client/lib/compact_index_client" - def self.compact_index_request(method_name) method = instance_method(method_name) undef_method(method_name) @@ -61,12 +61,13 @@ module Bundler compact_index_request :fetch_spec def available? - user_home = Pathname.new(Bundler.rubygems.user_home) + return nil unless md5_available? + user_home = Bundler.user_home return nil unless user_home.directory? && user_home.writable? # Read info file checksums out of /versions, so we can know if gems are up to date fetch_uri.scheme != "file" && compact_index_client.update_and_parse_checksums! rescue CompactIndexClient::Updater::MisMatchedChecksumError => e - Bundler.ui.warn(e.message) + Bundler.ui.debug(e.message) nil end compact_index_request :available? @@ -78,9 +79,9 @@ module Bundler private def compact_index_client - @compact_index_client ||= + @compact_index_client ||= begin SharedHelpers.filesystem_access(cache_path) do - CompactIndexClient.new(cache_path, compact_fetcher) + CompactIndexClient.new(cache_path, client_fetcher) end.tap do |client| client.in_parallel = lambda do |inputs, &blk| func = lambda {|object, _index| blk.call(object) } @@ -89,12 +90,13 @@ module Bundler inputs.map { worker.deq } end end + end end def bundle_worker(func = nil) @bundle_worker ||= begin worker_name = "Compact Index (#{display_uri.host})" - Bundler::Worker.new(25, worker_name, func) + Bundler::Worker.new(Bundler.current_ruby.rbx? ? 1 : 25, worker_name, func) end @bundle_worker.tap do |worker| worker.instance_variable_set(:@func, func) if func @@ -105,17 +107,29 @@ module Bundler Bundler.user_cache.join("compact_index", remote.cache_slug) end - def compact_fetcher - lambda do |path, headers| - begin - downloader.fetch(fetch_uri + path, headers) - rescue NetworkDownError => e - raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] - Bundler.ui.warn "Using the cached data for the new index because of a network error: #{e}" - Net::HTTPNotModified.new(nil, nil, nil) - end + def client_fetcher + ClientFetcher.new(self, Bundler.ui) + end + + ClientFetcher = Struct.new(:fetcher, :ui) do + def call(path, headers) + fetcher.downloader.fetch(fetcher.fetch_uri + path, headers) + rescue NetworkDownError => e + raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] + ui.warn "Using the cached data for the new index because of a network error: #{e}" + Net::HTTPNotModified.new(nil, nil, nil) end end + + def md5_available? + require "openssl" + OpenSSL::Digest::MD5.digest("") + true + rescue LoadError + true + rescue OpenSSL::Digest::DigestError + false + end end end end diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index 1cd5f9a213..445b0f2332 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -73,7 +73,7 @@ module Bundler def dependency_api_uri(gem_names = []) uri = fetch_uri + "api/v1/dependencies" - uri.query = "gems=#{CGI.escape(gem_names.join(","))}" if gem_names.any? + uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any? uri end end diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index c8d714c05a..ee1aa1a972 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -38,6 +38,8 @@ module Bundler end def request(uri, options) + validate_uri_scheme!(uri) + Bundler.ui.debug "HTTP GET #{uri}" req = Net::HTTP::Get.new uri.request_uri, options if uri.user @@ -61,6 +63,15 @@ module Bundler raise HTTPError, "Network error while fetching #{URICredentialsFilter.credential_filtered_uri(uri)}" end end + + private + + def validate_uri_scheme!(uri) + return if uri.scheme =~ /\Ahttps?\z/ + raise InvalidOption, + "The request uri `#{uri}` has an invalid scheme (`#{uri.scheme}`). " \ + "Did you mean `http` or `https`?" + end end end end diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index df45dd6946..3ba3dcdd91 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -37,6 +37,11 @@ module Bundler when Gem::InvalidSpecificationException Bundler.ui.error error.message, :wrap => true when SystemExit + when *[defined?(Java::JavaLang::OutOfMemoryError) && Java::JavaLang::OutOfMemoryError].compact + Bundler.ui.error "\nYour JVM has run out of memory, and Bundler cannot continue. " \ + "You can decrease the amount of memory Bundler needs by removing gems from your Gemfile, " \ + "especially large gems. (Gems can be as large as hundreds of megabytes, and Bundler has to read those files!). " \ + "Alternatively, you can increase the amount of memory the JVM is able to use by running Bundler with jruby -J-Xmx1024m -S bundle (JRuby defaults to 500MB)." else request_issue_report_for(error) end end @@ -53,25 +58,41 @@ module Bundler def request_issue_report_for(e) Bundler.ui.info <<-EOS.gsub(/^ {8}/, "") --- ERROR REPORT TEMPLATE ------------------------------------------------------- - - What did you do? + # Error Report + + ## Questions + + Please fill out answers to these questions, it'll help us figure out + why things are going wrong. + + - **What did you do?** I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}` - - What did you expect to happen? + - **What did you expect to happen?** I expected Bundler to... - - What happened instead? + - **What happened instead?** Instead, what happened was... + - **Have you tried any solutions posted on similar issues in our issue tracker, stack overflow, or google?** + + I tried... + + - **Have you read our issues document, https://github.com/bundler/bundler/blob/master/doc/contributing/ISSUES.md?** + + ... - Error details + ## Backtrace - #{e.class}: #{e.message} - #{e.backtrace && e.backtrace.join("\n ")} + ``` + #{e.class}: #{e.message} + #{e.backtrace && e.backtrace.join("\n ").chomp} + ``` - #{Bundler::Env.new.report(:print_gemfile => false, :print_gemspecs => false).gsub(/\n/, "\n ").strip} + #{Bundler::Env.new.report} --- TEMPLATE END ---------------------------------------------------------------- EOS diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index fdb2db7dbf..936d1361fa 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -94,11 +94,12 @@ module Bundler def rubygem_push(path) allowed_push_host = nil gem_command = "gem push '#{path}'" + gem_command += " --key #{gem_key}" if gem_key if @gemspec.respond_to?(:metadata) allowed_push_host = @gemspec.metadata["allowed_push_host"] gem_command += " --host #{allowed_push_host}" if allowed_push_host end - unless allowed_push_host || Pathname.new("~/.gem/credentials").expand_path.file? + unless allowed_push_host || Bundler.user_home.join(".gem/credentials").file? raise "Your rubygems.org credentials aren't set. Run `gem push` to set them." end sh(gem_command) @@ -181,6 +182,10 @@ module Bundler end end + def gem_key + Bundler.settings["gem.push_key"].to_s.downcase if Bundler.settings["gem.push_key"] + end + def gem_push? !%w(n no nil false off 0).include?(ENV["gem_push"].to_s.downcase) end diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb index 6d926ce83f..955834ff01 100644 --- a/lib/bundler/gem_helpers.rb +++ b/lib/bundler/gem_helpers.rb @@ -25,7 +25,7 @@ module Bundler module_function :generic def generic_local_platform - generic(Gem::Platform.local) + generic(Bundler.local_platform) end module_function :generic_local_platform diff --git a/lib/bundler/gemdeps.rb b/lib/bundler/gemdeps.rb new file mode 100644 index 0000000000..8595b8c7ea --- /dev/null +++ b/lib/bundler/gemdeps.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +module Bundler + class Gemdeps + def initialize(runtime) + @runtime = runtime + end + + def requested_specs + @runtime.requested_specs + end + + def specs + @runtime.specs + end + + def dependencies + @runtime.dependencies + end + + def current_dependencies + @runtime.current_dependencies + end + + def requires + @runtime.requires + end + end +end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index 4529c57279..373f6132af 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -109,14 +109,19 @@ module Bundler # returns a list of the dependencies def unmet_dependency_names - names = dependency_names - names.delete_if {|n| n == "bundler" } - names.select {|n| search(n).empty? } + dependency_names.select do |name| + name != "bundler" && search(name).empty? + end end def dependency_names names = [] - each {|s| names.concat(s.dependencies.map(&:name)) } + each do |spec| + spec.dependencies.each do |dep| + next if dep.type == :development + names << dep.name + end + end names.uniq end @@ -139,6 +144,8 @@ module Bundler end end + # Whether all the specs in self are in other + # TODO: rename to #include? def ==(other) all? do |spec| other_spec = other[spec].first @@ -179,7 +186,8 @@ module Bundler end wants_prerelease = dependency.requirement.prerelease? - only_prerelease = specs.all? {|spec| spec.version.prerelease? } + wants_prerelease ||= base && base.any? {|base_spec| base_spec.version.prerelease? } + only_prerelease = specs.all? {|spec| spec.version.prerelease? } unless wants_prerelease || only_prerelease found.reject! {|spec| spec.version.prerelease? } diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index dec3be3e98..4d3791bfb2 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -39,7 +39,7 @@ def gemfile(install = false, options = {}, &gemfile) def Bundler.root Bundler::SharedHelpers.pwd.expand_path end - ENV["BUNDLE_GEMFILE"] ||= "Gemfile" + ENV["BUNDLE_GEMFILE"] = "Gemfile" Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? builder = Bundler::Dsl.new @@ -60,7 +60,7 @@ def gemfile(install = false, options = {}, &gemfile) Bundler.ui = ui if install if install || missing_specs.call - installer = Bundler::Installer.install(Bundler.root, definition, :system => true) + installer = Bundler::Installer.install(Bundler.root, definition, :system => true, :inline => true) installer.post_install_messages.each do |name, message| Bundler.ui.info "Post-install message from #{name}:\n#{message}" end diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 824b1a45cd..cbfdddeaf7 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -49,7 +49,7 @@ module Bundler # Bundler returns a warning message stating so and this method returns. # # Fourthly, Bundler checks if the default lockfile (Gemfile.lock) exists, and if so - # then proceeds to set up a defintion based on the default gemfile (Gemfile) and the + # then proceeds to set up a definition based on the default gemfile (Gemfile) and the # default lock file (Gemfile.lock). However, this is not the case if the platform is different # to that which is specified in Gemfile.lock, or if there are any missing specs for the gems. # @@ -159,6 +159,7 @@ module Bundler # that said, it's a rare situation (other than rake), and parallel # installation is SO MUCH FASTER. so we let people opt in. def install(options) + Bundler.rubygems.load_plugins force = options["force"] jobs = 1 jobs = [Bundler.settings[:jobs].to_i - 1, 1].max if can_install_in_parallel? @@ -211,7 +212,7 @@ module Bundler end def resolve_if_need(options) - if Bundler.default_lockfile.exist? && !options["update"] + if !options["update"] && !options[:inline] && Bundler.default_lockfile.file? local = Bundler.ui.silence do begin tmpdef = Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, nil) diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb index 84dee979b5..10e7c7fcd7 100644 --- a/lib/bundler/installer/gem_installer.rb +++ b/lib/bundler/installer/gem_installer.rb @@ -16,7 +16,7 @@ module Bundler Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}" generate_executable_stubs return true, post_install_message - rescue Bundler::InstallHookError, Bundler::SecurityError + rescue Bundler::InstallHookError, Bundler::SecurityError, APIResponseMismatchError raise rescue Errno::ENOSPC return false, out_of_space_message @@ -52,12 +52,12 @@ module Bundler end def install - spec.source.install(spec, :force => force, :ensure_builtin_gems_cached => standalone) + spec.source.install(spec, :force => force, :ensure_builtin_gems_cached => standalone, :build_args => Array(spec_settings)) end def install_with_settings # Build arguments are global, so this is mutexed - Bundler.rubygems.with_build_args([spec_settings]) { install } + Bundler.rubygems.install_with_build_args([spec_settings]) { install } end def out_of_space_message diff --git a/lib/bundler/installer/parallel_installer.rb b/lib/bundler/installer/parallel_installer.rb index 12f11bb5f0..97c124e015 100644 --- a/lib/bundler/installer/parallel_installer.rb +++ b/lib/bundler/installer/parallel_installer.rb @@ -47,27 +47,30 @@ module Bundler # sure needed dependencies have been installed. def dependencies_installed?(all_specs) installed_specs = all_specs.select(&:installed?).map(&:name) - dependencies(all_specs.map(&:name)).all? {|d| installed_specs.include? d.name } + dependencies.all? {|d| installed_specs.include? d.name } end # Represents only the non-development dependencies, the ones that are # itself and are in the total list. - def dependencies(all_spec_names) + def dependencies @dependencies ||= begin - deps = all_dependencies.reject {|dep| ignorable_dependency? dep } - missing = deps.reject {|dep| all_spec_names.include? dep.name } - unless missing.empty? - raise Bundler::LockfileError, "Your Gemfile.lock is corrupt. The following #{missing.size > 1 ? "gems are" : "gem is"} missing " \ - "from the DEPENDENCIES section: '#{missing.map(&:name).join('\' \'')}'" - end - deps + all_dependencies.reject {|dep| ignorable_dependency? dep } end end + def missing_lockfile_dependencies(all_spec_names) + deps = all_dependencies.reject {|dep| ignorable_dependency? dep } + deps.reject {|dep| all_spec_names.include? dep.name } + end + # Represents all dependencies def all_dependencies @spec.dependencies end + + def to_s + "#<#{self.class} #{@spec.full_name} (#{state})>" + end end def self.call(*args) @@ -79,15 +82,23 @@ module Bundler [Bundler.settings[:jobs].to_i - 1, 1].max end + attr_reader :size + def initialize(installer, all_specs, size, standalone, force) @installer = installer @size = size @standalone = standalone @force = force @specs = all_specs.map {|s| SpecInstallation.new(s) } + @spec_set = all_specs end def call + # Since `autoload` has the potential for threading issues on 1.8.7 + # TODO: remove in bundler 2.0 + require "bundler/gem_remote_fetcher" if RUBY_VERSION < "1.9" + + check_for_corrupt_lockfile enqueue_specs process_specs until @specs.all?(&:installed?) || @specs.any?(&:failed?) handle_error if @specs.any?(&:failed?) @@ -106,7 +117,7 @@ module Bundler spec_install.post_install_message = message elsif !success spec_install.state = :failed - spec_install.error = message + spec_install.error = "#{message}\n\n#{require_tree_for_spec(spec_install.spec)}" end spec_install } @@ -131,6 +142,44 @@ module Bundler raise Bundler::InstallError, errors.map(&:to_s).join("\n\n") end + def check_for_corrupt_lockfile + missing_dependencies = @specs.map do |s| + [ + s, + s.missing_lockfile_dependencies(@specs.map(&:name)), + ] + end.reject { |a| a.last.empty? } + return if missing_dependencies.empty? + + warning = [] + warning << "Your lockfile was created by an old Bundler that left some things out." + if @size != 1 + warning << "Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing #{@size} at a time." + @size = 1 + end + warning << "You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile." + warning << "The missing gems are:" + + missing_dependencies.each do |spec, missing| + warning << "* #{missing.map(&:name).join(", ")} depended upon by #{spec.name}" + end + + Bundler.ui.warn(warning.join("\n")) + end + + def require_tree_for_spec(spec) + tree = @spec_set.what_required(spec) + t = String.new("In #{File.basename(SharedHelpers.default_gemfile)}:\n") + tree.each_with_index do |s, depth| + t << " " * depth.succ << s.name + unless tree.last == s + t << %( was resolved to #{s.version}, which depends on) + end + t << %(\n) + end + t + end + # Keys in the remains hash represent uninstalled gems specs. # We enqueue all gem specs that do not have any dependencies. # Later we call this lambda again to install specs that depended on diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 0b667f7dbe..891495c1d4 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require "uri" -require "rubygems/spec_fetcher" require "bundler/match_platform" module Bundler @@ -69,8 +68,19 @@ module Bundler end def __materialize__ - search_object = Bundler.settings[:specific_platform] ? self : Dependency.new(name, version) - @specification = source.specs.search(search_object).last + search_object = Bundler.settings[:specific_platform] || Bundler.settings[:force_ruby_platform] ? self : Dependency.new(name, version) + @specification = if source.is_a?(Source::Gemspec) && source.gemspec.name == name + source.gemspec.tap {|s| s.source = source } + else + search = source.specs.search(search_object).last + if search && Gem::Platform.new(search.platform) != Gem::Platform.new(platform) && !search.runtime_dependencies.-(dependencies.reject {|d| d.type == :development }).empty? + Bundler.ui.warn "Unable to use the platform-specific (#{search.platform}) version of #{name} (#{version}) " \ + "because it has different dependencies from the #{platform} version. " \ + "To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again." + search = source.specs.search(self).last + end + search + end end def respond_to?(*args) diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 51148ab614..d885c049d2 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require "strscan" # Some versions of the Bundler 1.1 RC series introduced corrupted # lockfiles. There were two major problems: diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb index 0a4e4c7e3a..050cd0efd3 100644 --- a/lib/bundler/match_platform.rb +++ b/lib/bundler/match_platform.rb @@ -6,10 +6,18 @@ module Bundler include GemHelpers def match_platform(p) - Gem::Platform::RUBY == platform || - platform.nil? || p == platform || - generic(Gem::Platform.new(platform)) === p || - Gem::Platform.new(platform) === p + MatchPlatform.platforms_match?(platform, p) + end + + def self.platforms_match?(gemspec_platform, local_platform) + return true if gemspec_platform.nil? + return true if Gem::Platform::RUBY == gemspec_platform + return true if local_platform == gemspec_platform + gemspec_platform = Gem::Platform.new(gemspec_platform) + return true if GemHelpers.generic(gemspec_platform) === local_platform + return true if gemspec_platform === local_platform + + false end end end diff --git a/lib/bundler/mirror.rb b/lib/bundler/mirror.rb index a1f3aaefa2..97a6776adb 100644 --- a/lib/bundler/mirror.rb +++ b/lib/bundler/mirror.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require "socket" + module Bundler class Settings # Class used to build the mirror set and then find a mirror for a given URI diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 8fb4119323..66f485ef8e 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true +require "bundler/plugin/api" module Bundler module Plugin - autoload :API, "bundler/plugin/api" autoload :DSL, "bundler/plugin/dsl" autoload :Index, "bundler/plugin/index" autoload :Installer, "bundler/plugin/installer" @@ -37,7 +37,11 @@ module Bundler save_plugins names, specs rescue PluginError => e - specs.values.map {|spec| Bundler.rm_rf(spec.full_gem_path) } if specs + if specs + specs_to_delete = Hash[specs.select {|k, _v| names.include?(k) && !index.commands.values.include?(k) }] + specs_to_delete.values.each {|spec| Bundler.rm_rf(spec.full_gem_path) } + end + Bundler.ui.error "Failed to install plugin #{name}: #{e.message}\n #{e.backtrace.join("\n ")}" end diff --git a/lib/bundler/plugin/api.rb b/lib/bundler/plugin/api.rb index 5bd8792e55..a2d5cbb4ac 100644 --- a/lib/bundler/plugin/api.rb +++ b/lib/bundler/plugin/api.rb @@ -24,6 +24,7 @@ module Bundler module Plugin class API autoload :Source, "bundler/plugin/api/source" + # The plugins should declare that they handle a command through this helper. # # @param [String] command being handled by them diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb index 78514563f7..5d3f58df92 100644 --- a/lib/bundler/plugin/api/source.rb +++ b/lib/bundler/plugin/api/source.rb @@ -6,7 +6,7 @@ module Bundler module Plugin class API # This class provides the base to build source plugins - # All the method here are require to build a source plugin (except + # All the method here are required to build a source plugin (except # `uri_hash`, `gem_install_dir`; they are helpers). # # Defaults for methods, where ever possible are provided which is @@ -93,7 +93,7 @@ module Bundler # It should be called in `install` after the plugin is done placing the # gem at correct install location. # - # It also runs Gem hooks `post_install`, `post_build` and `post_install` + # It also runs Gem hooks `pre_install`, `post_build` and `post_install` # # Note: Do not override if you don't know what you are doing. def post_install(spec, disable_exts = false) @@ -287,6 +287,12 @@ module Bundler def root Bundler.root end + + # @private + # Returns true + def bundler_plugin_api_source? + true + end end end end diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb index 7f89d26178..8dde072f16 100644 --- a/lib/bundler/plugin/index.rb +++ b/lib/bundler/plugin/index.rb @@ -20,6 +20,8 @@ module Bundler end end + attr_reader :commands + def initialize @plugin_paths = {} @commands = {} diff --git a/lib/bundler/postit_trampoline.rb b/lib/bundler/postit_trampoline.rb index 2a22489954..b62a5e7676 100644 --- a/lib/bundler/postit_trampoline.rb +++ b/lib/bundler/postit_trampoline.rb @@ -8,11 +8,11 @@ environment = BundlerVendoredPostIt::PostIt::Environment.new([]) version = Gem::Requirement.new(environment.bundler_version) if version.requirements.size == 1 && version.requirements.first.first == "=" # version.exact? if version.requirements.first.last.segments.first >= 2 - ENV["BUNDLE_ENABLE_TRAMPOLINE"] = "true" + ENV["BUNDLE_TRAMPOLINE_FORCE"] = "true" end end -if ENV["BUNDLE_ENABLE_TRAMPOLINE"] && !ENV["BUNDLE_DISABLE_POSTIT"] +if ENV["BUNDLE_TRAMPOLINE_FORCE"] && !ENV["BUNDLE_TRAMPOLINE_DISABLE"] installed_version = if defined?(Bundler::VERSION) Bundler::VERSION @@ -70,4 +70,4 @@ You're running Bundler #{installed_version} but this project uses #{running_vers abort "The running bundler (#{running_version}) does not match the required `#{version}`" end -end # if ENV["BUNDLE_ENABLE_TRAMPOLINE"] && !ENV["BUNDLE_DISABLE_POSTIT"] +end # if ENV["BUNDLE_TRAMPOLINE_FORCE"] && !ENV["BUNDLE_TRAMPOLINE_DISABLE"] diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index f45bf4a5ed..e5f9c78b00 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require "uri" -require "rubygems/spec_fetcher" module Bundler # Represents a lazily loaded gem specification, where the full specification @@ -50,6 +49,13 @@ module Bundler # once the remote gem is downloaded, the backend specification will # be swapped out. def __swap__(spec) + without_type = proc {|d| Gem::Dependency.new(d.name, d.requirements_list) } + if (extra_deps = spec.runtime_dependencies.map(&without_type).-(dependencies.map(&without_type))) && extra_deps.any? + Bundler.ui.debug "#{full_name} from #{remote} has corrupted API dependencies (API returned #{dependencies}, real spec has (#{spec.runtime_dependencies}))" + raise APIResponseMismatchError, + "Downloading #{full_name} revealed dependencies not in the API (#{extra_deps.map(&without_type).map(&:to_s).join(", ")})." \ + "\nInstalling with `--full-index` should fix the problem." + end @_remote_specification = spec end @@ -82,8 +88,9 @@ module Bundler _remote_specification.send(method, *args, &blk) end - def respond_to_missing?(method, include_all) - _remote_specification.respond_to?(method, include_all) + def respond_to?(method, include_all = false) + super || _remote_specification.respond_to?(method, include_all) end + public :respond_to? end end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index b8016b37a9..b8d044e6ef 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -14,7 +14,7 @@ module Bundler def message conflicts.sort.reduce(String.new) do |o, (name, conflict)| - o << %(Bundler could not find compatible versions for gem "#{name}":\n) + o << %(\nBundler could not find compatible versions for gem "#{name}":\n) if conflict.locked_requirement o << %( In snapshot (#{Bundler.default_lockfile.basename}):\n) o << %( #{printable_dep(conflict.locked_requirement)}\n) @@ -62,7 +62,7 @@ module Bundler end end o - end + end.strip end end @@ -175,14 +175,15 @@ module Bundler # ==== Returns # <GemBundle>,nil:: If the list of dependencies can be resolved, a # collection of gemspecs is returned. Otherwise, nil is returned. - def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = []) + def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil) + platforms = Set.new(platforms) if platforms base = SpecSet.new(base) unless base.is_a?(SpecSet) - resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements) + resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) result = resolver.start(requirements) SpecSet.new(result) end - def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements) + def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms) @index = index @source_requirements = source_requirements @base = base @@ -194,6 +195,7 @@ module Bundler @base_dg.add_vertex(ls.name, DepProxy.new(dep, ls.platform), true) end additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) } + @platforms = platforms @gem_version_promoter = gem_version_promoter end @@ -228,11 +230,11 @@ module Bundler def debug? return @debug_mode if defined?(@debug_mode) - @debug_mode = ENV["DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER_TREE"] + @debug_mode = ENV["DEBUG_RESOLVER"] || ENV["DEBUG_RESOLVER_TREE"] || false end def before_resolution - Bundler.ui.info "Resolving dependencies...", false + Bundler.ui.info "Resolving dependencies...", debug? end def after_resolution @@ -240,7 +242,7 @@ module Bundler end def indicate_progress - Bundler.ui.info ".", false + Bundler.ui.info ".", false unless debug? end include Molinillo::SpecificationProvider @@ -308,13 +310,15 @@ module Bundler def requirement_satisfied_by?(requirement, activated, spec) return false unless requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) - spec.activate_platform!(requirement.__platform) || spec.for?(requirement.__platform) + spec.activate_platform!(requirement.__platform) if !@platforms || @platforms.include?(requirement.__platform) + true end def sort_dependencies(dependencies, activated, conflicts) dependencies.sort_by do |dependency| name = name_for(dependency) [ + @base_dg.vertex_named(name) ? 0 : 1, activated.vertex_named(name).payload ? 0 : 1, amount_constrained(dependency), conflicts[name] ? 0 : 1, @@ -325,6 +329,12 @@ module Bundler private + # returns an integer \in (-\infty, 0] + # a number closer to 0 means the dependency is less constraining + # + # dependencies w/ 0 or 1 possibilities (ignoring version requirements) + # are given very negative values, so they _always_ sort first, + # before dependencies that are unconstrained def amount_constrained(dependency) @amount_constrained ||= {} @amount_constrained[dependency.name] ||= begin @@ -332,8 +342,9 @@ module Bundler dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1 else all = index_for(dependency).search(dependency.name).size + if all <= 1 - all + all - 1_000_000 else search = search_for(dependency).size search - all @@ -365,8 +376,13 @@ module Bundler "Source does not contain any versions of '#{requirement}'" end else + cache_message = begin + " or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist? + rescue GemfileNotFound + nil + end message = "Could not find gem '#{requirement}' in any of the gem sources " \ - "listed in your Gemfile or available on this machine." + "listed in your Gemfile#{cache_message}." end raise GemNotFound, message end diff --git a/lib/bundler/retry.rb b/lib/bundler/retry.rb index a7a72feed5..092fb866b3 100644 --- a/lib/bundler/retry.rb +++ b/lib/bundler/retry.rb @@ -43,7 +43,10 @@ module Bundler def fail_attempt(e) @failed = true - raise e if last_attempt? || @exceptions.any? {|k| e.is_a?(k) } + if last_attempt? || @exceptions.any? {|k| e.is_a?(k) } + Bundler.ui.info "" unless Bundler.ui.debug? + raise e + end return true unless name Bundler.ui.info "" unless Bundler.ui.debug? # Add new line incase dots preceded this Bundler.ui.warn "Retrying #{name} due to error (#{current_run.next}/#{total_runs}): #{e.class} #{e.message}", Bundler.ui.debug? diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 8c050c6d31..f0a001d296 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -21,10 +21,14 @@ module Bundler # must not be specified, or the engine version # specified must match the version. - @versions = Array(versions) + @versions = Array(versions).map do |v| + op, v = Gem::Requirement.parse(v) + op == "=" ? v.to_s : "#{op} #{v}" + end + @gem_version = Gem::Requirement.create(@versions.first).requirements.first.last - @input_engine = engine - @engine = engine || "ruby" + @input_engine = engine && engine.to_s + @engine = engine && engine.to_s || "ruby" @engine_versions = (engine_version && Array(engine_version)) || @versions @engine_gem_version = Gem::Requirement.create(@engine_versions.first).requirements.first.last @patchlevel = patchlevel diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 7cd83e631e..7293e7cfc7 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -8,6 +8,16 @@ end require "rubygems" require "rubygems/specification" + +begin + # Possible use in Gem::Specification#source below and require + # shouldn't be deferred. + require "rubygems/source" +rescue LoadError + # Not available before Rubygems 2.0.0, ignore + nil +end + require "bundler/match_platform" module Gem @@ -32,7 +42,11 @@ module Gem attr_writer :full_gem_path unless instance_methods.include?(:full_gem_path=) def full_gem_path - if source.respond_to?(:path) || source.is_a?(Bundler::Plugin::API::Source) + # this cannot check source.is_a?(Bundler::Plugin::API::Source) + # because that _could_ trip the autoload, and if there are unresolved + # gems at that time, this method could be called inside another require, + # thus raising with that constant being undefined. Better to check a method + if source.respond_to?(:path) || (source.respond_to?(:bundler_plugin_api_source?) && source.bundler_plugin_api_source?) Pathname.new(loaded_from).dirname.expand_path(source.root).to_s.untaint else rg_full_gem_path diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 0aa9fd91d6..977e13d948 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -28,15 +28,27 @@ module Bundler digest = Digest::SHA256.new digest << io.read(16_384) until io.eof? io.rewind - digest.send(checksum_type(checksum)) + send(checksum_type(checksum), digest) end unless digest == checksum - raise SecurityError, - "The checksum for the downloaded `#{spec.full_name}.gem` did not match " \ - "the checksum given by the API. This means that the contents of the " \ - "gem appear to be different from what was uploaded, and could be an indicator of a security issue.\n" \ - "(The expected SHA256 checksum was #{checksum.inspect}, but the checksum for the downloaded gem was #{digest.inspect}.)\n" \ - "Bundler cannot continue installing #{spec.name} (#{spec.version})." + raise SecurityError, <<-MESSAGE + Bundler cannot continue installing #{spec.name} (#{spec.version}). + The checksum for the downloaded `#{spec.full_name}.gem` does not match \ + the checksum given by the server. This means the contents of the downloaded \ + gem is different from what was uploaded to the server, and could be a potential security issue. + + To resolve this issue: + 1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem` + 2. run `bundle install` + + If you wish to continue installing the downloaded gem, and are certain it does not pose a \ + security issue despite the mismatching checksum, do the following: + 1. run `bundle config disable_checksum_validation true` to turn off checksum verification + 2. run `bundle install` + + (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \ + checksum for the downloaded gem was #{digest.inspect}.) + MESSAGE end true end @@ -48,5 +60,17 @@ module Bundler else raise InstallError, "The given checksum for #{spec.full_name} (#{checksum.inspect}) is not a valid SHA256 hexdigest nor base64digest" end end + + def hexdigest!(digest) + digest.hexdigest! + end + + def base64digest!(digest) + if digest.respond_to?(:base64digest!) + digest.base64digest! + else + [digest.digest!].pack("m0") + end + end end end diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index c1bb6c7ab8..90768aac1d 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -71,8 +71,13 @@ module Bundler spec.installed_by_version = Gem::Version.create(installed_by_version) end - def spec_missing_extensions?(spec) - !spec.respond_to?(:missing_extensions?) || spec.missing_extensions? + def spec_missing_extensions?(spec, default = true) + return spec.missing_extensions? if spec.respond_to?(:missing_extensions?) + + return false if spec.respond_to?(:default_gem?) && spec.default_gem? + return false if spec.extensions.empty? + + default end def path(obj) @@ -80,6 +85,7 @@ module Bundler end def platforms + return [Gem::Platform::RUBY] if Bundler.settings[:force_ruby_platform] Gem.platforms end @@ -194,6 +200,10 @@ module Bundler end end + def load_plugins + Gem.load_plugins if Gem.respond_to?(:load_plugins) + end + def ui=(obj) Gem::DefaultUserInteraction.ui = obj end @@ -203,6 +213,7 @@ module Bundler end def fetch_specs(all, pre, &blk) + require "rubygems/spec_fetcher" specs = Gem::SpecFetcher.new.list(all, pre) specs.each { yield } if block_given? specs @@ -243,6 +254,10 @@ module Bundler end end + def install_with_build_args(args) + with_build_args(args) { yield } + end + def gem_from_path(path, policy = nil) require "rubygems/format" Gem::Format.from_file_by_path(path, policy) @@ -348,6 +363,10 @@ module Bundler true end + + # TODO: delete this in 2.0, it's a backwards compatibility shim + # see https://github.com/bundler/bundler/issues/5102 + kernel_class.send(:public, :gem) end end @@ -397,6 +416,17 @@ module Bundler spec end + redefine_method(gem_class, :activate_bin_path) do |name, *args| + exec_name = args.first + return ENV["BUNDLE_BIN_PATH"] if exec_name == "bundle" + + # Copy of Rubygems activate_bin_path impl + requirement = args.last + spec = find_spec_for_exe name, exec_name, [requirement] + Gem::LOADED_SPECS_MUTEX.synchronize { spec.activate } + spec.bin_file exec_name + end + redefine_method(gem_class, :bin_path) do |name, *args| exec_name = args.first return ENV["BUNDLE_BIN_PATH"] if exec_name == "bundle" @@ -489,6 +519,7 @@ module Bundler end def redefine_method(klass, method, unbound_method = nil, &block) + visibility = method_visibility(klass, method) begin if (instance_method = klass.instance_method(method)) && method != :initialize # doing this to ensure we also get private methods @@ -501,8 +532,20 @@ module Bundler @replaced_methods[[method, klass]] = instance_method if unbound_method klass.send(:define_method, method, unbound_method) + klass.send(visibility, method) elsif block klass.send(:define_method, method, &block) + klass.send(visibility, method) + end + end + + def method_visibility(klass, method) + if klass.private_method_defined?(method) + :private + elsif klass.protected_method_defined?(method) + :protected + else + :public end end @@ -682,6 +725,10 @@ module Bundler def repository_subdirectories Gem::REPOSITORY_SUBDIRECTORIES end + + def install_with_build_args(args) + yield + end end # RubyGems 2.1.0 @@ -722,6 +769,14 @@ module Bundler end.map(&:to_spec) end end + + def use_gemdeps(gemfile) + ENV["BUNDLE_GEMFILE"] ||= File.expand_path(gemfile) + runtime = Bundler.setup + Bundler.ui = nil + activated_spec_names = runtime.requested_specs.map(&:to_spec).sort_by(&:name) + [Gemdeps.new(runtime), activated_spec_names] + end end end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index c5fd46d440..7339f721ad 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -3,13 +3,20 @@ require "uri" module Bundler class Settings + autoload :Mirror, "bundler/mirror" + autoload :Mirrors, "bundler/mirror" + BOOL_KEYS = %w( allow_offline_install + auto_install cache_all + cache_all_platforms disable_checksum_validation disable_exec_load disable_local_branch_check disable_shared_gems + disable_version_check + force_ruby_platform frozen gem.coc gem.mit @@ -42,11 +49,18 @@ module Bundler @local_config = load_config(local_config_file) @global_config = load_config(global_config_file) @cli_flags_given = false + @temporary = {} end def [](name) key = key_for(name) - value = (@local_config[key] || ENV[key] || @global_config[key] || DEFAULT_CONFIG[name]) + value = @temporary.fetch(name) do + @local_config.fetch(key) do + ENV.fetch(key) do + @global_config.fetch(key) do + DEFAULT_CONFIG.fetch(name) do + nil + end end end end end if value.nil? nil @@ -76,9 +90,19 @@ module Bundler local_config_file || raise(GemfileNotFound, "Could not locate Gemfile") set_key(key, value, @local_config, local_config_file) end - alias_method :set_local, :[]= + def temporary(update) + existing = Hash[update.map {|k, _| [k, @temporary[k]] }] + @temporary.update(update) + return unless block_given? + begin + yield + ensure + existing.each {|k, v| v.nil? ? @temporary.delete(k) : @temporary[k] = v } + end + end + def delete(key) @local_config.delete(key_for(key)) end @@ -221,7 +245,12 @@ module Bundler end def to_bool(value) - !(value.nil? || value == "" || value =~ /^(false|f|no|n|0)$/i || value == false) + case value + when nil, /\A(false|f|no|n|0|)\z/i, false + false + else + true + end end def is_num(value) @@ -256,7 +285,11 @@ module Bundler if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? Pathname.new(ENV["BUNDLE_CONFIG"]) else - Bundler.user_bundle_path.join("config") + begin + Bundler.user_bundle_path.join("config") + rescue PermissionError, GenericSystemCallError + nil + end end end @@ -279,10 +312,10 @@ module Bundler }xo def load_config(config_file) - return unless config_file + return {} if !config_file || ignore_config? SharedHelpers.filesystem_access(config_file, :read) do |file| valid_file = file.exist? && !file.size.zero? - return {} if ignore_config? || !valid_file + return {} unless valid_file require "bundler/yaml_serializer" YAMLSerializer.load file.read end diff --git a/lib/bundler/setup.rb b/lib/bundler/setup.rb index 4e213beed3..eb816b9988 100644 --- a/lib/bundler/setup.rb +++ b/lib/bundler/setup.rb @@ -22,7 +22,7 @@ if Bundler::SharedHelpers.in_bundle? unless ENV["BUNDLE_POSTIT_TRAMPOLINING_VERSION"] # Add bundler to the load path after disabling system gems - # This is guarenteed to be done already if we've trampolined + # This is guaranteed to be done already if we've trampolined bundler_lib = File.expand_path("../..", __FILE__) $LOAD_PATH.unshift(bundler_lib) unless $LOAD_PATH.include?(bundler_lib) end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index ca4eafd623..0d68c85831 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -8,6 +8,7 @@ require "bundler/current_ruby" module Gem class Dependency + # This is only needed for RubyGems < 1.4 unless method_defined? :requirement def requirement version_requirements @@ -18,8 +19,6 @@ end module Bundler module SharedHelpers - attr_accessor :gem_loaded - def default_gemfile gemfile = find_gemfile raise GemfileNotFound, "Could not locate Gemfile" unless gemfile @@ -39,10 +38,12 @@ module Bundler bundle_dir = find_directory(".bundle") return nil unless bundle_dir - global_bundle_dir = File.join(Bundler.rubygems.user_home, ".bundle") + bundle_dir = Pathname.new(bundle_dir) + + global_bundle_dir = Bundler.user_home.join(".bundle") return nil if bundle_dir == global_bundle_dir - Pathname.new(bundle_dir) + bundle_dir end def in_bundle? @@ -101,16 +102,24 @@ module Bundler # end # # @see {Bundler::PermissionError} - def filesystem_access(path, action = :write) - yield path.dup.untaint + def filesystem_access(path, action = :write, &block) + # Use block.call instead of yield because of a bug in Ruby 2.2.2 + # See https://github.com/bundler/bundler/issues/5341 for details + block.call(path.dup.untaint) rescue Errno::EACCES raise PermissionError.new(path, action) rescue Errno::EAGAIN raise TemporaryResourceError.new(path, action) rescue Errno::EPROTO raise VirtualProtocolError.new + rescue Errno::ENOSPC + raise NoSpaceOnDeviceError.new(path, action) rescue *[const_get_safely(:ENOTSUP, Errno)].compact raise OperationNotSupportedError.new(path, action) + rescue Errno::EEXIST, Errno::ENOENT + raise + rescue SystemCallError => e + raise GenericSystemCallError.new(e, "There was an error accessing `#{path}`.") end def const_get_safely(constant_name, namespace) @@ -202,24 +211,27 @@ module Bundler def set_rubylib rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR) - rubylib.unshift File.expand_path("../..", __FILE__) + rubylib.unshift bundler_ruby_lib ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR) end + def bundler_ruby_lib + File.expand_path("../..", __FILE__) + end + def clean_load_path # handle 1.9 where system gems are always on the load path - if defined?(::Gem) - me = File.expand_path("../../", __FILE__) - me = /^#{Regexp.escape(me)}/ + return unless defined?(::Gem) - loaded_gem_paths = Bundler.rubygems.loaded_gem_paths + bundler_lib = bundler_ruby_lib - $LOAD_PATH.reject! do |p| - next if File.expand_path(p) =~ me - loaded_gem_paths.delete(p) - end - $LOAD_PATH.uniq! + loaded_gem_paths = Bundler.rubygems.loaded_gem_paths + + $LOAD_PATH.reject! do |p| + next if File.expand_path(p).start_with?(bundler_lib) + loaded_gem_paths.delete(p) end + $LOAD_PATH.uniq! end def prints_major_deprecations? @@ -234,7 +246,7 @@ module Bundler def deprecate_gemfile(gemfile) return unless gemfile && File.basename(gemfile) == "Gemfile" Bundler::SharedHelpers.major_deprecation \ - "gems.rb and gems.locked will be prefered to Gemfile and Gemfile.lock." + "gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock." end extend self diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index b6f3a4311d..cf56ed1cc1 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -14,13 +14,13 @@ module Bundler def version_message(spec) message = "#{spec.name} #{spec.version}" - message += " (#{spec.platform})" if spec.platform != Gem::Platform::RUBY + message += " (#{spec.platform})" if spec.platform != Gem::Platform::RUBY && !spec.platform.nil? if Bundler.locked_gems locked_spec = Bundler.locked_gems.specs.find {|s| s.name == spec.name } locked_spec_version = locked_spec.version if locked_spec if locked_spec_version && spec.version != locked_spec_version - message += " (#{Bundler.ui.add_color("was #{locked_spec_version}", :green)})" + message += Bundler.ui.add_color(" (was #{locked_spec_version})", version_color(spec.version, locked_spec_version)) end end @@ -34,5 +34,25 @@ module Bundler def include?(other) other == self end + + def inspect + "#<#{self.class}:0x#{object_id} #{self}>" + end + + private + + def version_color(spec_version, locked_spec_version) + if Gem::Version.correct?(spec_version) && Gem::Version.correct?(locked_spec_version) + # display yellow if there appears to be a regression + earlier_version?(spec_version, locked_spec_version) ? :yellow : :green + else + # default to green if the versions cannot be directly compared + :green + end + end + + def earlier_version?(spec_version, locked_spec_version) + Gem::Version.new(spec_version) < Gem::Version.new(locked_spec_version) + end end end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index d1757a4a93..30ff27c446 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -161,7 +161,9 @@ module Bundler local_specs end - def install(spec, force = false) + def install(spec, options = {}) + force = options[:force] + Bundler.ui.info "Using #{version_message(spec)} from #{self}" if requires_checkout? && !@copied && !force @@ -170,7 +172,8 @@ module Bundler serialize_gemspecs_in(install_path) @copied = true end - generate_bin(spec, !Bundler.rubygems.spec_missing_extensions?(spec)) + generate_bin_options = { :disable_extensions => !Bundler.rubygems.spec_missing_extensions?(spec), :build_args => options[:build_args] } + generate_bin(spec, generate_bin_options) requires_checkout? ? spec.post_install_message : nil end diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index c44f00d7b1..e9b9c4dbe4 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require "shellwords" require "tempfile" module Bundler class Source @@ -180,7 +181,7 @@ module Bundler def find_local_revision allowed_in_path do - git("rev-parse --verify #{ref}", true).strip + git("rev-parse --verify #{Shellwords.shellescape(ref)}", true).strip end end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index 69bb0c1af2..2900b3f939 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -4,10 +4,12 @@ module Bundler class Path < Source autoload :Installer, "bundler/source/path/installer" - attr_reader :path, :options, :root_path + attr_reader :path, :options, :root_path, :original_path attr_writer :name attr_accessor :version + protected :original_path + DEFAULT_GLOB = "{,*,*/*}.gemspec".freeze def initialize(options) @@ -46,7 +48,7 @@ module Bundler def to_lock out = String.new("PATH\n") - out << " remote: #{relative_path}\n" + out << " remote: #{lockfile_path}\n" out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB out << " specs:\n" end @@ -61,7 +63,7 @@ module Bundler def eql?(other) return unless other.class == self.class - expanded_path == expand(other.path) && + expand(@original_path) == expand(other.original_path) && version == other.version end @@ -71,9 +73,9 @@ module Bundler File.basename(expanded_path.to_s) end - def install(spec, force = false) + def install(spec, options = {}) Bundler.ui.info "Using #{version_message(spec)} from #{self}" - generate_bin(spec, :disable_extensions) + generate_bin(spec, :disable_extensions => true) nil # no post-install message end @@ -129,6 +131,11 @@ module Bundler "`#{somepath}`.\nThe error message was: #{e.message}." end + def lockfile_path + return relative_path(original_path) if original_path.absolute? + expand(original_path).relative_path_from(Bundler.root) + end + def app_cache_path(custom_path = nil) @app_cache_path ||= Bundler.app_cache(custom_path).join(app_cache_dirname) end @@ -181,14 +188,14 @@ module Bundler index end - def relative_path + def relative_path(path = self.path) if path.to_s.start_with?(root_path.to_s) return path.relative_path_from(root_path) end path end - def generate_bin(spec, disable_extensions = false) + def generate_bin(spec, options = {}) gem_dir = Pathname.new(spec.full_gem_path) # Some gem authors put absolute paths in their gemspec @@ -203,7 +210,12 @@ module Bundler end end.compact - installer = Path::Installer.new(spec, :env_shebang => false, :disable_extensions => disable_extensions) + installer = Path::Installer.new( + spec, + :env_shebang => false, + :disable_extensions => options[:disable_extensions], + :build_args => options[:build_args] + ) installer.post_install rescue Gem::InvalidSpecificationException => e Bundler.ui.warn "\n#{spec.name} at #{spec.full_gem_path} did not have a valid gemspec.\n" \ diff --git a/lib/bundler/source/path/installer.rb b/lib/bundler/source/path/installer.rb index abc46d5a04..9c2f74a31b 100644 --- a/lib/bundler/source/path/installer.rb +++ b/lib/bundler/source/path/installer.rb @@ -13,7 +13,7 @@ module Bundler @format_executable = options[:format_executable] || false @build_args = options[:build_args] || Bundler.rubygems.build_args @gem_bin_dir = "#{Bundler.rubygems.gem_dir}/bin" - @disable_extentions = options[:disable_extensions] + @disable_extensions = options[:disable_extensions] if Bundler.requires_sudo? @tmp_dir = Bundler.tmp(spec.full_name).to_s @@ -27,7 +27,7 @@ module Bundler SharedHelpers.chdir(@gem_dir) do run_hooks(:pre_install) - unless @disable_extentions + unless @disable_extensions build_extensions run_hooks(:post_build) end diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 89f7673eb8..8c592cb727 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "uri" require "rubygems/user_interaction" -require "rubygems/spec_fetcher" module Bundler class Source @@ -141,6 +140,7 @@ module Bundler :ignore_dependencies => true, :wrappers => true, :env_shebang => true, + :build_args => opts[:build_args], :bundler_expected_checksum => spec.respond_to?(:checksum) && spec.checksum ).install end @@ -292,6 +292,10 @@ module Bundler next if spec.name == "bundler" && spec.version.to_s != VERSION have_bundler = true if spec.name == "bundler" spec.source = self + if Bundler.rubygems.spec_missing_extensions?(spec, false) + Bundler.ui.debug "Source #{self} is ignoring #{spec} because it is missing extensions" + next + end idx << spec end @@ -322,6 +326,10 @@ module Bundler next if gemfile =~ /^bundler\-[\d\.]+?\.gem/ s ||= Bundler.rubygems.spec_from_gem(gemfile) s.source = self + if Bundler.rubygems.spec_missing_extensions?(s, false) + Bundler.ui.debug "Source #{self} is ignoring #{s} because it is missing extensions" + next + end idx << s end end @@ -402,6 +410,7 @@ module Bundler return false unless spec.remote uri = spec.remote.uri spec.fetch_platform + Bundler.ui.confirm("Fetching #{version_message(spec)}") download_path = requires_sudo? ? Bundler.tmp(spec.full_name) : rubygems_dir gem_path = "#{rubygems_dir}/cache/#{spec.full_name}.gem" diff --git a/lib/bundler/source/rubygems/remote.rb b/lib/bundler/source/rubygems/remote.rb index 92f8a40588..b49e645506 100644 --- a/lib/bundler/source/rubygems/remote.rb +++ b/lib/bundler/source/rubygems/remote.rb @@ -30,6 +30,10 @@ module Bundler end end + def to_s + "rubygems remote at #{anonymized_uri}" + end + private def apply_auth(uri, auth) diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index fe31b17f0e..f3826beaa5 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -20,8 +20,8 @@ module Bundler specs = [] skip += ["bundler"] - until deps.empty? - dep = deps.shift + loop do + break unless dep = deps.shift next if handled[dep] || skip.include?(dep.name) handled[dep] = true @@ -114,6 +114,17 @@ module Bundler SpecSet.new(arr) end + def find_by_name_and_platform(name, platform) + @specs.detect {|spec| spec.name == name && spec.match_platform(platform) } + end + + def what_required(spec) + unless req = find {|s| s.dependencies.any? {|d| d.type == :runtime && d.name == spec.name } } + return [spec] + end + what_required(req) << spec + end + private def sorted @@ -151,14 +162,15 @@ module Bundler end def spec_for_dependency(dep, match_current_platform) + specs_for_platforms = lookup[dep.name] if match_current_platform Bundler.rubygems.platforms.reverse_each do |pl| - match = GemHelpers.select_best_platform_match(lookup[dep.name], pl) + match = GemHelpers.select_best_platform_match(specs_for_platforms, pl) return match if match end nil else - GemHelpers.select_best_platform_match(lookup[dep.name], dep.__platform) + GemHelpers.select_best_platform_match(specs_for_platforms, dep.__platform) end end diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index f4ee7d0644..cbcadee269 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -15,6 +15,13 @@ module Bundler _remote_specification.to_yaml end + if Bundler.rubygems.provides?(">= 2.3") + # This is defined directly to avoid having to load every installed spec + def missing_extensions? + stub.missing_extensions? + end + end + private def _remote_specification diff --git a/lib/bundler/templates/Executable b/lib/bundler/templates/Executable index b72c267dd1..fe22de0a6d 100755 --- a/lib/bundler/templates/Executable +++ b/lib/bundler/templates/Executable @@ -1,4 +1,4 @@ -#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG['ruby_install_name'] %> +#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> # frozen_string_literal: true # # This file was generated by Bundler. diff --git a/lib/bundler/templates/Executable.standalone b/lib/bundler/templates/Executable.standalone index c114afe337..4bf0753f44 100644 --- a/lib/bundler/templates/Executable.standalone +++ b/lib/bundler/templates/Executable.standalone @@ -1,4 +1,4 @@ -#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG['ruby_install_name'] %> +#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> # # This file was generated by Bundler. # @@ -6,9 +6,9 @@ # this file is here to facilitate running it. # -require 'pathname' +require "pathname" path = Pathname.new(__FILE__) -$:.unshift File.expand_path '../<%= standalone_path %>', path.realpath +$:.unshift File.expand_path "../<%= standalone_path %>", path.realpath -require 'bundler/setup' -load File.expand_path '../<%= executable_path %>', path.realpath +require "bundler/setup" +load File.expand_path "../<%= executable_path %>", path.realpath diff --git a/lib/bundler/templates/newgem/Gemfile.tt b/lib/bundler/templates/newgem/Gemfile.tt index d24b8525da..4cd2e40f4f 100644 --- a/lib/bundler/templates/newgem/Gemfile.tt +++ b/lib/bundler/templates/newgem/Gemfile.tt @@ -1,4 +1,4 @@ -source 'https://rubygems.org' +source "https://rubygems.org" -# Specify your gem's dependencies in <%=config[:name]%>.gemspec +# Specify your gem's dependencies in <%= config[:name] %>.gemspec gemspec diff --git a/lib/bundler/templates/newgem/LICENSE.txt.tt b/lib/bundler/templates/newgem/LICENSE.txt.tt index 8fef84cd53..76ef4b0191 100644 --- a/lib/bundler/templates/newgem/LICENSE.txt.tt +++ b/lib/bundler/templates/newgem/LICENSE.txt.tt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) <%=Time.now.year%> <%=config[:author]%> +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 diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index ad8d88b6e4..6eb270a124 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -1,6 +1,6 @@ -# <%=config[:constant_name]%> +# <%= config[:constant_name] %> -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. +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 @@ -9,7 +9,7 @@ TODO: Delete this and the text above, and describe your gem Add this line to your application's Gemfile: ```ruby -gem '<%=config[:name]%>' +gem '<%= config[:name] %>' ``` And then execute: @@ -18,7 +18,7 @@ And then execute: Or install it yourself as: - $ gem install <%=config[:name]%> + $ gem install <%= config[:name] %> ## Usage @@ -32,10 +32,16 @@ To install this gem onto your local machine, run `bundle exec rake install`. To ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:git_user_name] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %> -<% if config[:mit] %> +Bug reports and pull requests are welcome on GitHub at https://github.com/<%= config[:github_username] %>/<%= config[:name] %>.<% if config[:coc] %> This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.<% end %> +<% if config[:mit] -%> ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). -<% end %> +<% end -%> + +<% if config[:coc] -%> +## Code of Conduct + +Everyone interacting in the <%= config[:constant_name] %> project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/<%= config[:github_username] %>/<%= config[:name] %>/blob/master/CODE_OF_CONDUCT.md). +<% end -%> diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index 8beb91aee6..099da6f3ec 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -1,14 +1,14 @@ require "bundler/gem_tasks" -<% if config[:test] == 'minitest' -%> +<% if config[:test] == "minitest" -%> require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" - t.test_files = FileList['test/**/*_test.rb'] + t.test_files = FileList["test/**/*_test.rb"] end -<% elsif config[:test] == 'rspec' -%> +<% elsif config[:test] == "rspec" -%> require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) @@ -19,8 +19,8 @@ require "rake/extensiontask" task :build => :compile -Rake::ExtensionTask.new("<%=config[:underscored_name]%>") do |ext| - ext.lib_dir = "lib/<%=config[:namespaced_path]%>" +Rake::ExtensionTask.new("<%= config[:underscored_name] %>") do |ext| + ext.lib_dir = "lib/<%= config[:namespaced_path] %>" end task :default => [:clobber, :compile, :<%= config[:test_task] %>] diff --git a/lib/bundler/templates/newgem/bin/console.tt b/lib/bundler/templates/newgem/bin/console.tt index f402bd639e..a27f82430f 100644 --- a/lib/bundler/templates/newgem/bin/console.tt +++ b/lib/bundler/templates/newgem/bin/console.tt @@ -11,4 +11,4 @@ require "<%= config[:namespaced_path] %>" # Pry.start require "irb" -IRB.start +IRB.start(__FILE__) diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt index 5dad3641ff..8177c4d202 100644 --- a/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt +++ b/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt @@ -1,9 +1,9 @@ -#include "<%=config[:underscored_name]%>.h" +#include "<%= config[:underscored_name] %>.h" -VALUE rb_m<%=config[:constant_array].join%>; +VALUE rb_m<%= config[:constant_array].join %>; void -Init_<%=config[:underscored_name]%>(void) +Init_<%= config[:underscored_name] %>(void) { - rb_m<%=config[:constant_array].join%> = rb_define_module(<%=config[:constant_name].inspect%>); + rb_m<%= config[:constant_array].join %> = rb_define_module(<%= config[:constant_name].inspect %>); } diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt index 960fdfb492..c6e420b66e 100644 --- a/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt +++ b/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt @@ -1,6 +1,6 @@ -#ifndef <%=config[:underscored_name].upcase%>_H -#define <%=config[:underscored_name].upcase%>_H 1 +#ifndef <%= config[:underscored_name].upcase %>_H +#define <%= config[:underscored_name].upcase %>_H 1 #include "ruby.h" -#endif /* <%=config[:underscored_name].upcase%>_H */ +#endif /* <%= config[:underscored_name].upcase %>_H */ diff --git a/lib/bundler/templates/newgem/gitignore.tt b/lib/bundler/templates/newgem/gitignore.tt index ebff7ac53c..573d76b4c2 100644 --- a/lib/bundler/templates/newgem/gitignore.tt +++ b/lib/bundler/templates/newgem/gitignore.tt @@ -14,3 +14,8 @@ *.a mkmf.log <%- end -%> +<%- if config[:test] == "rspec" -%> + +# rspec failure tracking +.rspec_status +<%- end -%> diff --git a/lib/bundler/templates/newgem/lib/newgem.rb.tt b/lib/bundler/templates/newgem/lib/newgem.rb.tt index b3f816bac6..7d8ad90ab0 100644 --- a/lib/bundler/templates/newgem/lib/newgem.rb.tt +++ b/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -1,12 +1,12 @@ -require "<%=config[:namespaced_path]%>/version" +require "<%= config[:namespaced_path] %>/version" <%- if config[:ext] -%> -require "<%=config[:namespaced_path]%>/<%=config[:underscored_name]%>" +require "<%= config[:namespaced_path] %>/<%= config[:underscored_name] %>" <%- end -%> -<%- config[:constant_array].each_with_index do |c,i| -%> -<%= ' '*i %>module <%= c %> +<%- config[:constant_array].each_with_index do |c, i| -%> +<%= " " * i %>module <%= c %> <%- end -%> -<%= ' '*config[:constant_array].size %># Your code goes here... +<%= " " * config[:constant_array].size %># Your code goes here... <%- (config[:constant_array].size-1).downto(0) do |i| -%> -<%= ' '*i %>end +<%= " " * i %>end <%- end -%> diff --git a/lib/bundler/templates/newgem/lib/newgem/version.rb.tt b/lib/bundler/templates/newgem/lib/newgem/version.rb.tt index 5874085d61..389daf5048 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 %> +<%- config[:constant_array].each_with_index do |c, i| -%> +<%= " " * i %>module <%= c %> <%- end -%> -<%= ' '*config[:constant_array].size %>VERSION = "0.1.0" +<%= " " * config[:constant_array].size %>VERSION = "0.1.0" <%- (config[:constant_array].size-1).downto(0) do |i| -%> -<%= ' '*i %>end +<%= " " * i %>end <%- end -%> diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index 9d6d491dce..caea7fe7be 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -1,13 +1,13 @@ # coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require '<%=config[:namespaced_path]%>/version' +require "<%= config[:namespaced_path] %>/version" Gem::Specification.new do |spec| - spec.name = <%=config[:name].inspect%> - spec.version = <%=config[:constant_name]%>::VERSION - spec.authors = [<%=config[:author].inspect%>] - spec.email = [<%=config[:email].inspect%>] + spec.name = <%= config[:name].inspect %> + spec.version = <%= config[:constant_name] %>::VERSION + spec.authors = [<%= config[:author].inspect %>] + spec.email = [<%= config[:email].inspect %>] spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.} spec.description = %q{TODO: Write a longer description or delete this line.} @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' # to allow pushing to a single host or delete this section to allow pushing to any host. if spec.respond_to?(:metadata) - spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" + spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" else raise "RubyGems 2.0 or newer is required to protect against " \ "public gem pushes." @@ -32,7 +32,7 @@ Gem::Specification.new do |spec| 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"] + spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> spec.add_development_dependency "bundler", "~> <%= config[:bundler_version] %>" @@ -41,6 +41,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rake-compiler" <%- end -%> <%- if config[:test] -%> - spec.add_development_dependency "<%=config[:test]%>", "~> <%=config[:test_framework_version]%>" + spec.add_development_dependency "<%= config[:test] %>", "~> <%= config[:test_framework_version] %>" <%- end -%> end diff --git a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt index b33d65188a..b7ef7f9e4a 100644 --- a/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +++ b/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt @@ -1,6 +1,6 @@ require "spec_helper" -describe <%= config[:constant_name] %> do +RSpec.describe <%= config[:constant_name] %> do it "has a version number" do expect(<%= config[:constant_name] %>::VERSION).not_to be nil end diff --git a/lib/bundler/templates/newgem/spec/spec_helper.rb.tt b/lib/bundler/templates/newgem/spec/spec_helper.rb.tt index 4c69d354b3..805cf57e01 100644 --- a/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +++ b/lib/bundler/templates/newgem/spec/spec_helper.rb.tt @@ -1,2 +1,14 @@ -$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) +require "bundler/setup" require "<%= config[:namespaced_path] %>" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/lib/bundler/templates/newgem/test/newgem_test.rb.tt b/lib/bundler/templates/newgem/test/newgem_test.rb.tt index 95e33a34ea..f2af9f90e0 100644 --- a/lib/bundler/templates/newgem/test/newgem_test.rb.tt +++ b/lib/bundler/templates/newgem/test/newgem_test.rb.tt @@ -1,4 +1,4 @@ -require 'test_helper' +require "test_helper" class <%= config[:constant_name] %>Test < Minitest::Test def test_that_it_has_a_version_number diff --git a/lib/bundler/templates/newgem/test/test_helper.rb.tt b/lib/bundler/templates/newgem/test/test_helper.rb.tt index 49a56c1800..725e3e4647 100644 --- a/lib/bundler/templates/newgem/test/test_helper.rb.tt +++ b/lib/bundler/templates/newgem/test/test_helper.rb.tt @@ -1,4 +1,4 @@ -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -require '<%= config[:namespaced_path] %>' +$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) +require "<%= config[:namespaced_path] %>" -require 'minitest/autorun' +require "minitest/autorun" diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb index 5c1fa61568..697290f795 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -83,6 +83,10 @@ module Bundler with_level("silent", &blk) end + def unprinted_warnings + [] + end + private # valimism diff --git a/lib/bundler/ui/silent.rb b/lib/bundler/ui/silent.rb index 367eaa58c2..48390b7198 100644 --- a/lib/bundler/ui/silent.rb +++ b/lib/bundler/ui/silent.rb @@ -2,6 +2,12 @@ module Bundler module UI class Silent + attr_writer :shell + + def initialize + @warnings = [] + end + def add_color(string, color) string end @@ -13,6 +19,7 @@ module Bundler end def warn(message, newline = nil) + @warnings |= [message] end def error(message, newline = nil) @@ -32,18 +39,30 @@ module Bundler def ask(message) end + def yes?(msg) + raise "Cannot ask yes? with a silent shell" + end + + def no? + raise "Cannot ask no? with a silent shell" + end + def level=(name) end def level(name = nil) end - def trace(message, newline = nil) + def trace(message, newline = nil, force = false) end def silence yield end + + def unprinted_warnings + @warnings + end end end end diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb deleted file mode 100644 index 9ab2722f18..0000000000 --- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true -require "pathname" -require "set" - -class Bundler::CompactIndexClient - class Error < StandardError; end - - require "bundler/vendor/compact_index_client/lib/compact_index_client/cache" - require "bundler/vendor/compact_index_client/lib/compact_index_client/updater" - require "bundler/vendor/compact_index_client/lib/compact_index_client/version" - - attr_reader :directory - - # @return [Lambda] A lambda that takes an array of inputs and a block, and - # maps the inputs with the block in parallel. - # - attr_accessor :in_parallel - - def initialize(directory, fetcher) - @directory = Pathname.new(directory) - @updater = Updater.new(fetcher) - @cache = Cache.new(@directory) - @endpoints = Set.new - @info_checksums_by_name = {} - @in_parallel = lambda do |inputs, &blk| - inputs.map(&blk) - end - end - - def names - update(@cache.names_path, "names") - @cache.names - end - - def versions - update(@cache.versions_path, "versions") - versions, @info_checksums_by_name = @cache.versions - versions - end - - def dependencies(names) - in_parallel.call(names) do |name| - update_info(name) - @cache.dependencies(name).map {|d| d.unshift(name) } - end.flatten(1) - end - - def spec(name, version, platform = nil) - update_info(name) - @cache.specific_dependency(name, version, platform) - end - - def update_and_parse_checksums! - return @info_checksums_by_name if @parsed_checksums - update(@cache.versions_path, "versions") - @info_checksums_by_name = @cache.checksums - @parsed_checksums = true - end - -private - - def update(local_path, remote_path) - return unless @endpoints.add?(remote_path) - @updater.update(local_path, url(remote_path)) - end - - def update_info(name) - path = @cache.info_path(name) - checksum = @updater.checksum_for_file(path) - return unless existing = @info_checksums_by_name[name] - return if checksum == existing - update(path, "info/#{name}") - end - - def url(path) - path - end -end diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb deleted file mode 100644 index d2639ee717..0000000000 --- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/cache.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true -require "digest/md5" -class Bundler::CompactIndexClient - class Cache - attr_reader :directory - - def initialize(directory) - @directory = Pathname.new(directory).expand_path - info_roots.each {|dir| FileUtils.mkdir_p(dir) } - end - - def names - lines(names_path) - end - - def names_path - directory.join("names") - end - - def versions - versions_by_name = Hash.new {|hash, key| hash[key] = [] } - info_checksums_by_name = {} - - lines(versions_path).each do |line| - name, versions_string, info_checksum = line.split(" ", 3) - info_checksums_by_name[name] = info_checksum || "" - versions_string.split(",").each do |version| - if version.start_with?("-") - version = version[1..-1].split("-", 2).unshift(name) - versions_by_name[name].delete(version) - else - version = version.split("-", 2).unshift(name) - versions_by_name[name] << version - end - end - end - - [versions_by_name, info_checksums_by_name] - end - - def versions_path - directory.join("versions") - end - - def checksums - checksums = {} - - lines(versions_path).each do |line| - name, _, checksum = line.split(" ", 3) - checksums[name] = checksum - end - - checksums - end - - def dependencies(name) - lines(info_path(name)).map do |line| - parse_gem(line) - end - end - - def info_path(name) - name = name.to_s - if name =~ /[^a-z0-9_-]/ - name += "-#{Digest::MD5.hexdigest(name).downcase}" - info_roots.last.join(name) - else - info_roots.first.join(name) - end - end - - def specific_dependency(name, version, platform) - pattern = [version, platform].compact.join("-") - return nil if pattern.empty? - - gem_lines = info_path(name).read - gem_line = gem_lines[/^#{Regexp.escape(pattern)}\b.*/, 0] - gem_line ? parse_gem(gem_line) : nil - end - - private - - def lines(path) - return [] unless path.file? - lines = path.read.split("\n") - header = lines.index("---") - lines = header ? lines[header + 1..-1] : lines - end - - def parse_gem(string) - version_and_platform, rest = string.split(" ", 2) - version, platform = version_and_platform.split("-", 2) - dependencies, requirements = rest.split("|", 2).map {|s| s.split(",") } if rest - dependencies = dependencies ? dependencies.map {|d| parse_dependency(d) } : [] - requirements = requirements ? requirements.map {|r| parse_dependency(r) } : [] - [version, platform, dependencies, requirements] - end - - def parse_dependency(string) - dependency = string.split(":") - dependency[-1] = dependency[-1].split("&") if dependency.size > 1 - dependency - end - - def info_roots - [ - directory.join("info"), - directory.join("info-special-characters"), - ] - end - end -end diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb deleted file mode 100644 index 5c5ba41434..0000000000 --- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/updater.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true -require "fileutils" -require "stringio" -require "tmpdir" -require "zlib" - -class Bundler::CompactIndexClient - class Updater - class MisMatchedChecksumError < Error - def initialize(path, server_checksum, local_checksum) - @path = path - @server_checksum = server_checksum - @local_checksum = local_checksum - end - - def message - "The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \ - "(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})." - end - end - - def initialize(fetcher) - @fetcher = fetcher - end - - def update(local_path, remote_path, retrying = nil) - headers = {} - - Dir.mktmpdir(local_path.basename.to_s, local_path.dirname) do |local_temp_dir| - local_temp_path = Pathname.new(local_temp_dir).join(local_path.basename) - - # download new file if retrying - if retrying.nil? && local_path.file? - FileUtils.cp local_path, local_temp_path - headers["If-None-Match"] = etag_for(local_temp_path) - headers["Range"] = "bytes=#{local_temp_path.size}-" - else - # Fastly ignores Range when Accept-Encoding: gzip is set - headers["Accept-Encoding"] = "gzip" - end - - response = @fetcher.call(remote_path, headers) - return if response.is_a?(Net::HTTPNotModified) - - content = response.body - if response["Content-Encoding"] == "gzip" - content = Zlib::GzipReader.new(StringIO.new(content)).read - end - - mode = response.is_a?(Net::HTTPPartialContent) ? "a" : "w" - local_temp_path.open(mode) {|f| f << content } - - response_etag = response["ETag"] - if etag_for(local_temp_path) == response_etag - FileUtils.mv(local_temp_path, local_path) - return - end - - if retrying.nil? - update(local_path, remote_path, :retrying) - else - raise MisMatchedChecksumError.new(remote_path, response_etag, etag_for(local_temp_path)) - end - end - end - - def etag_for(path) - sum = checksum_for_file(path) - sum ? %("#{sum}") : nil - end - - def checksum_for_file(path) - return nil unless path.file? - # This must use IO.read instead of Digest.file().hexdigest - # because we need to preserve \n line endings on windows when calculating - # the checksum - Digest::MD5.hexdigest(IO.read(path)) - end - end -end diff --git a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb b/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb deleted file mode 100644 index 64520daead..0000000000 --- a/lib/bundler/vendor/compact_index_client/lib/compact_index_client/version.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true -class Bundler::CompactIndexClient - VERSION = "0.1.0".freeze -end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb index ce5c5efa46..76e84ab7e6 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb @@ -98,18 +98,27 @@ module Bundler::Molinillo "#{self.class}:#{vertices.values.inspect}" end + # @param [Hash] options options for dot output. # @return [String] Returns a dot format representation of the graph - def to_dot + def to_dot(options = {}) + edge_label = options.delete(:edge_label) + raise ArgumentError, "Unknown options: #{options.keys}" unless options.empty? + dot_vertices = [] dot_edges = [] vertices.each do |n, v| dot_vertices << " #{n} [label=\"{#{n}|#{v.payload}}\"]" v.outgoing_edges.each do |e| - dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=\"#{e.requirement}\"]" + label = edge_label ? edge_label.call(e) : e.requirement + dot_edges << " #{e.origin.name} -> #{e.destination.name} [label=#{label.to_s.dump}]" end end + + dot_vertices.uniq! dot_vertices.sort! + dot_edges.uniq! dot_edges.sort! + dot = dot_vertices.unshift('digraph G {').push('') + dot_edges.push('}') dot.join("\n") end @@ -119,10 +128,12 @@ module Bundler::Molinillo # {Vertex#successors} def ==(other) return false unless other + return true if equal?(other) vertices.each do |name, vertex| other_vertex = other.vertex_named(name) return false unless other_vertex - return false unless other_vertex.successors.map(&:name).to_set == vertex.successors.map(&:name).to_set + return false unless vertex.payload == other_vertex.payload + return false unless other_vertex.successors.to_set == vertex.successors.to_set end end @@ -134,6 +145,7 @@ module Bundler::Molinillo def add_child_vertex(name, payload, parent_names, requirement) root = !parent_names.delete(nil) { true } vertex = add_vertex(name, payload, root) + vertex.explicit_requirements << requirement if root parent_names.each do |parent_name| parent_node = vertex_named(parent_name) add_edge(parent_node, vertex, requirement) @@ -152,7 +164,7 @@ module Bundler::Molinillo # Detaches the {#vertex_named} `name` {Vertex} from the graph, recursively # removing any non-root vertices that were orphaned in the process # @param [String] name - # @return [void] + # @return [Array<Vertex>] the vertices which have been detached def detach_vertex_named(name) log.detach_vertex_named(self, name) end @@ -182,6 +194,13 @@ module Bundler::Molinillo add_edge_no_circular(origin, destination, requirement) end + # Deletes an {Edge} from the dependency graph + # @param [Edge] edge + # @return [Void] + def delete_edge(edge) + log.delete_edge(self, edge.origin.name, edge.destination.name, edge.requirement) + end + # Sets the payload of the vertex with the given name # @param [String] name the name of the vertex # @param [Object] payload the payload diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb index c8eacbe08f..e0dfe6cbbd 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/action.rb @@ -7,7 +7,7 @@ module Bundler::Molinillo # rubocop:disable Lint/UnusedMethodArgument # @return [Symbol] The name of the action. - def self.name + def self.action_name raise 'Abstract' end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb index a7e703a8f9..9092e4d546 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular.rb @@ -7,8 +7,8 @@ module Bundler::Molinillo class AddEdgeNoCircular < Action # @!group Action - # (see Action.name) - def self.name + # (see Action.action_name) + def self.action_name :add_vertex end @@ -23,8 +23,8 @@ module Bundler::Molinillo # (see Action#down) def down(graph) edge = make_edge(graph) - edge.origin.outgoing_edges.delete(edge) - edge.destination.incoming_edges.delete(edge) + delete_first(edge.origin.outgoing_edges, edge) + delete_first(edge.destination.incoming_edges, edge) end # @!group AddEdgeNoCircular @@ -53,6 +53,13 @@ module Bundler::Molinillo @destination = destination @requirement = requirement end + + private + + def delete_first(array, item) + return unless index = array.index(item) + array.delete_at(index) + end end end end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb index 14cd027804..eda4251801 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex.rb @@ -7,8 +7,8 @@ module Bundler::Molinillo class AddVertex < Action # :nodoc: # @!group Action - # (see Action.name) - def self.name + # (see Action.action_name) + def self.action_name :add_vertex end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb new file mode 100644 index 0000000000..e9125a59c6 --- /dev/null +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/action' +module Bundler::Molinillo + class DependencyGraph + # @!visibility private + # (see DependencyGraph#delete_edge) + class DeleteEdge < Action + # @!group Action + + # (see Action.action_name) + def self.action_name + :delete_edge + end + + # (see Action#up) + def up(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges.delete(edge) + edge.destination.incoming_edges.delete(edge) + end + + # (see Action#down) + def down(graph) + edge = make_edge(graph) + edge.origin.outgoing_edges << edge + edge.destination.incoming_edges << edge + edge + end + + # @!group DeleteEdge + + # @return [String] the name of the origin of the edge + attr_reader :origin_name + + # @return [String] the name of the destination of the edge + attr_reader :destination_name + + # @return [Object] the requirement that the edge represents + attr_reader :requirement + + # @param [DependencyGraph] graph the graph to find vertices from + # @return [Edge] The edge this action adds + def make_edge(graph) + Edge.new( + graph.vertex_named(origin_name), + graph.vertex_named(destination_name), + requirement + ) + end + + # Initialize an action to add an edge to a dependency graph + # @param [String] origin_name the name of the origin of the edge + # @param [String] destination_name the name of the destination of the edge + # @param [Object] requirement the requirement that the edge represents + def initialize(origin_name, destination_name, requirement) + @origin_name = origin_name + @destination_name = destination_name + @requirement = requirement + end + end + end +end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb index 78c0da67ef..d20b2cb0e0 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named.rb @@ -8,22 +8,29 @@ module Bundler::Molinillo # @!group Action # (see Action#name) - def self.name + def self.action_name :add_vertex end # (see Action#up) def up(graph) - return unless @vertex = graph.vertices.delete(name) + return [] unless @vertex = graph.vertices.delete(name) + + removed_vertices = [@vertex] @vertex.outgoing_edges.each do |e| v = e.destination v.incoming_edges.delete(e) - graph.detach_vertex_named(v.name) unless v.root? || v.predecessors.any? + if !v.root? && v.incoming_edges.empty? + removed_vertices.concat graph.detach_vertex_named(v.name) + end end + @vertex.incoming_edges.each do |e| v = e.origin v.outgoing_edges.delete(e) end + + removed_vertices end # (see Action#down) diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb index 863b4912be..72a705e023 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/log.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_edge_no_circular' require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/add_vertex' +require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/delete_edge' require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/detach_vertex_named' require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload' require 'bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag' @@ -40,6 +41,16 @@ module Bundler::Molinillo push_action(graph, AddEdgeNoCircular.new(origin, destination, requirement)) end + # {include:DependencyGraph#delete_edge} + # @param [Graph] graph the graph to perform the action on + # @param [String] origin_name + # @param [String] destination_name + # @param [Object] requirement + # @return (see DependencyGraph#delete_edge) + def delete_edge(graph, origin_name, destination_name, requirement) + push_action(graph, DeleteEdge.new(origin_name, destination_name, requirement)) + end + # @macro action def set_payload(graph, name, payload) push_action(graph, SetPayload.new(name, payload)) @@ -92,7 +103,7 @@ module Bundler::Molinillo loop do action = pop!(graph) raise "No tag #{tag.inspect} found" unless action - break if action.class.name == :tag && action.tag == tag + break if action.class.action_name == :tag && action.tag == tag end end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb index f2fe4b0289..8d8e10fedf 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/set_payload.rb @@ -7,8 +7,8 @@ module Bundler::Molinillo class SetPayload < Action # :nodoc: # @!group Action - # (see Action.name) - def self.name + # (see Action.action_name) + def self.action_name :set_payload end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb index cb0e626e6a..53524d36ad 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/tag.rb @@ -7,8 +7,8 @@ module Bundler::Molinillo class Tag < Action # @!group Action - # (see Action.name) - def self.name + # (see Action.action_name) + def self.action_name :tag end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb index 14d4957a91..eab989e7bc 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph/vertex.rb @@ -10,7 +10,7 @@ module Bundler::Molinillo # @return [Object] the payload the vertex holds attr_accessor :payload - # @return [Arrary<Object>] the explicit requirements that required + # @return [Array<Object>] the explicit requirements that required # this vertex attr_reader :explicit_requirements @@ -81,6 +81,7 @@ module Bundler::Molinillo # @return [Boolean] whether the two vertices are equal, determined # by a recursive traversal of each {Vertex#successors} def ==(other) + return true if equal?(other) shallow_eql?(other) && successors.to_set == other.successors.to_set end @@ -89,6 +90,7 @@ module Bundler::Molinillo # @return [Boolean] whether the two vertices are equal, determined # solely by {#name} and {#payload} equality def shallow_eql?(other) + return true if equal?(other) other && name == other.name && payload == other.payload diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb index c44da1ce3e..a4fb6dd68e 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/gem_metadata.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Bundler::Molinillo # The version of Bundler::Molinillo. - VERSION = '0.5.0'.freeze + VERSION = '0.5.7'.freeze end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb index a903b21239..d47cfa2928 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/modules/ui.rb @@ -48,7 +48,7 @@ module Bundler::Molinillo if debug? debug_info = yield debug_info = debug_info.inspect unless debug_info.is_a?(String) - output.puts debug_info.split("\n").map { |s| ' ' * depth + s } + output.puts debug_info.split("\n").map { |s| ' ' * depth + s } end end diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb index 5707e890b7..1845966a75 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/resolution.rb @@ -52,7 +52,7 @@ module Bundler::Molinillo @base = base @states = [] @iteration_counter = 0 - @parent_of = {} + @parents_of = Hash.new { |h, k| h[k] = [] } end # Resolves the {#original_requested} dependencies into a full dependency @@ -105,7 +105,7 @@ module Bundler::Molinillo handle_missing_or_push_dependency_state(initial_state) - debug { "Starting resolution (#{@started_at})" } + debug { "Starting resolution (#{@started_at})\nUser-requested dependencies: #{original_requested}" } resolver_ui.before_resolution end @@ -178,12 +178,14 @@ module Bundler::Molinillo # Unwinds the states stack because a conflict has been encountered # @return [void] def unwind_for_conflict - debug(depth) { "Unwinding for conflict: #{requirement}" } + debug(depth) { "Unwinding for conflict: #{requirement} to #{state_index_for_unwind / 2}" } conflicts.tap do |c| sliced_states = states.slice!((state_index_for_unwind + 1)..-1) raise VersionConflict.new(c) unless state activated.rewind_to(sliced_states.first || :initial_state) if sliced_states state.conflicts = c + index = states.size - 1 + @parents_of.each { |_, a| a.reject! { |i| i >= index } } end end @@ -192,24 +194,29 @@ module Bundler::Molinillo def state_index_for_unwind current_requirement = requirement existing_requirement = requirement_for_existing_name(name) - until current_requirement.nil? - current_state = find_state_for(current_requirement) - return states.index(current_state) if state_any?(current_state) - current_requirement = parent_of(current_requirement) + index = -1 + [current_requirement, existing_requirement].each do |r| + until r.nil? + current_state = find_state_for(r) + if state_any?(current_state) + current_index = states.index(current_state) + index = current_index if current_index > index + break + end + r = parent_of(r) + end end - until existing_requirement.nil? - existing_state = find_state_for(existing_requirement) - return states.index(existing_state) if state_any?(existing_state) - existing_requirement = parent_of(existing_requirement) - end - -1 + index end # @return [Object] the requirement that led to `requirement` being added # to the list of requirements. def parent_of(requirement) - @parent_of[requirement] + return unless requirement + return unless index = @parents_of[requirement].last + return unless parent_state = @states[index] + parent_state.requirement end # @return [Object] the requirement that led to a version of a possibility @@ -349,20 +356,40 @@ module Bundler::Molinillo # Ensures there are no orphaned successors to the given {vertex}. # @param [DependencyGraph::Vertex] vertex the vertex to fix up. # @return [void] - def fixup_swapped_children(vertex) + def fixup_swapped_children(vertex) # rubocop:disable Metrics/CyclomaticComplexity payload = vertex.payload - dep_names = dependencies_for(payload).map(&method(:name_for)) - vertex.successors.each do |succ| - if !dep_names.include?(succ.name) && !succ.root? && succ.predecessors.to_a == [vertex] - debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" } - activated.detach_vertex_named(succ.name) + deps = dependencies_for(payload).group_by(&method(:name_for)) + vertex.outgoing_edges.each do |outgoing_edge| + requirement = outgoing_edge.requirement + parent_index = @parents_of[requirement].last + succ = outgoing_edge.destination + matching_deps = Array(deps[succ.name]) + dep_matched = matching_deps.include?(requirement) + + # only push the current index when it was originally required by the + # same named spec + if parent_index && states[parent_index].name == name + @parents_of[requirement].push(states.size - 1) + end - all_successor_names = succ.recursive_successors.map(&:name) + if matching_deps.empty? && !succ.root? && succ.predecessors.to_a == [vertex] + debug(depth) { "Removing orphaned spec #{succ.name} after swapping #{name}" } + succ.requirements.each { |r| @parents_of.delete(r) } - requirements.delete_if do |requirement| - requirement_name = name_for(requirement) - (requirement_name == succ.name) || all_successor_names.include?(requirement_name) + removed_names = activated.detach_vertex_named(succ.name).map(&:name) + requirements.delete_if do |r| + # the only removed vertices are those with no other requirements, + # so it's safe to delete only based upon name here + removed_names.include?(name_for(r)) end + elsif !dep_matched + debug(depth) { "Removing orphaned dependency #{requirement} after swapping #{name}" } + # also reset if we're removing the edge, but only if its parent has + # already been fixed up + @parents_of[requirement].push(states.size - 1) if @parents_of[requirement].empty? + + activated.delete_edge(outgoing_edge) + requirements.delete(requirement) end end end @@ -382,13 +409,18 @@ module Bundler::Molinillo # @return [Boolean] whether the current spec is satisfied as a new # possibility. def new_spec_satisfied? + unless requirement_satisfied_by?(requirement, activated, possibility) + debug(depth) { 'Unsatisfied by requested spec' } + return false + end + locked_requirement = locked_requirement_named(name) - requested_spec_satisfied = requirement_satisfied_by?(requirement, activated, possibility) + locked_spec_satisfied = !locked_requirement || requirement_satisfied_by?(locked_requirement, activated, possibility) - debug(depth) { 'Unsatisfied by requested spec' } unless requested_spec_satisfied debug(depth) { 'Unsatisfied by locked spec' } unless locked_spec_satisfied - requested_spec_satisfied && locked_spec_satisfied + + locked_spec_satisfied end # @param [String] requirement_name the spec name to search for @@ -404,7 +436,7 @@ module Bundler::Molinillo # @return [void] def activate_spec conflicts.delete(name) - debug(depth) { 'Activated ' + name + ' at ' + possibility.to_s } + debug(depth) { "Activated #{name} at #{possibility}" } activated.set_payload(name, possibility) require_nested_dependencies_for(possibility) end @@ -418,7 +450,9 @@ module Bundler::Molinillo debug(depth) { "Requiring nested dependencies (#{nested_dependencies.join(', ')})" } nested_dependencies.each do |d| activated.add_child_vertex(name_for(d), nil, [name_for(activated_spec)], d) - @parent_of[d] = requirement + parent_index = states.size - 1 + parents = @parents_of[d] + parents << parent_index if parents.empty? end push_state_for_requirements(requirements + nested_dependencies, !nested_dependencies.empty?) diff --git a/lib/bundler/vendor/net/http/faster.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/faster.rb index 74b94d1d99..e5e09080c2 100644 --- a/lib/bundler/vendor/net/http/faster.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/faster.rb @@ -24,3 +24,4 @@ class Net::BufferedIO #:nodoc: end end end if RUBY_VERSION < '1.9' + diff --git a/lib/bundler/vendor/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb index e2805b1236..5195be2152 100644 --- a/lib/bundler/vendor/net/http/persistent.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb @@ -4,7 +4,7 @@ begin rescue LoadError # net/https or openssl end if RUBY_VERSION < '1.9' # but only for 1.8 -require 'net/http/faster' +require 'bundler/vendor/net-http-persistent/lib/net/http/faster' require 'uri' require 'cgi' # for escaping @@ -18,27 +18,27 @@ autoload :OpenSSL, 'openssl' ## # Persistent connections for Net::HTTP # -# Net::HTTP::Persistent maintains persistent connections across all the +# Bundler::Persistent::Net::HTTP::Persistent maintains persistent connections across all the # servers you wish to talk to. For each host:port you communicate with a # single persistent connection is created. # -# Multiple Net::HTTP::Persistent objects will share the same set of +# Multiple Bundler::Persistent::Net::HTTP::Persistent objects will share the same set of # connections. # # For each thread you start a new connection will be created. A -# Net::HTTP::Persistent connection will not be shared across threads. +# Bundler::Persistent::Net::HTTP::Persistent connection will not be shared across threads. # # You can shut down the HTTP connections when done by calling #shutdown. You -# should name your Net::HTTP::Persistent object if you intend to call this +# should name your Bundler::Persistent::Net::HTTP::Persistent object if you intend to call this # method. # # Example: # -# require 'net/http/persistent' +# require 'bundler/vendor/net-http-persistent/lib/net/http/persistent' # # uri = URI 'http://example.com/awesome/web/service' # -# http = Net::HTTP::Persistent.new 'my_app_name' +# http = Bundler::Persistent::Net::HTTP::Persistent.new 'my_app_name' # # # perform a GET # response = http.request uri @@ -149,19 +149,19 @@ autoload :OpenSSL, 'openssl' # # The recommended way to handle non-idempotent requests is the following: # -# require 'net/http/persistent' +# require 'bundler/vendor/net-http-persistent/lib/net/http/persistent' # # uri = URI 'http://example.com/awesome/web/service' # post_uri = uri + 'create' # -# http = Net::HTTP::Persistent.new 'my_app_name' +# http = Bundler::Persistent::Net::HTTP::Persistent.new 'my_app_name' # # post = Net::HTTP::Post.new post_uri.path # # ... fill in POST request # # begin # response = http.request post_uri, post -# rescue Net::HTTP::Persistent::Error +# rescue Bundler::Persistent::Net::HTTP::Persistent::Error # # # POST failed, make a new request to verify the server did not process # # the request @@ -178,7 +178,7 @@ autoload :OpenSSL, 'openssl' # # === Connection Termination # -# If you are done using the Net::HTTP::Persistent instance you may shut down +# If you are done using the Bundler::Persistent::Net::HTTP::Persistent instance you may shut down # all the connections in the current thread with #shutdown. This is not # recommended for normal use, it should only be used when it will be several # minutes before you make another HTTP request. @@ -188,7 +188,7 @@ autoload :OpenSSL, 'openssl' # Ruby will automatically garbage collect and shutdown your HTTP connections # when the thread terminates. -class Net::HTTP::Persistent +class Bundler::Persistent::Net::HTTP::Persistent ## # The beginning of Time @@ -201,9 +201,9 @@ class Net::HTTP::Persistent HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: ## - # The version of Net::HTTP::Persistent you are using + # The version of Bundler::Persistent::Net::HTTP::Persistent you are using - VERSION = '2.9.3' + VERSION = '2.9.4' ## # Exceptions rescued for automatic retry on ruby 2.0.0. This overlaps with @@ -221,7 +221,7 @@ class Net::HTTP::Persistent ].compact ## - # Error class for errors raised by Net::HTTP::Persistent. Various + # Error class for errors raised by Bundler::Persistent::Net::HTTP::Persistent. Various # SystemCallErrors are re-raised with a human-readable message under this # class. @@ -241,7 +241,7 @@ class Net::HTTP::Persistent # NOTE: This may not work on ruby > 1.9. def self.detect_idle_timeout uri, max = 10 - uri = URI uri unless uri.is_a?(URI::Generic) + uri = URI uri unless URI::Generic === uri uri += '/' req = Net::HTTP::Head.new uri.request_uri @@ -257,7 +257,7 @@ class Net::HTTP::Persistent $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG - unless response.is_a?(Net::HTTPOK) then + unless Net::HTTPOK === response then raise Error, "bad response code #{response.code} detecting idle timeout" end @@ -463,7 +463,7 @@ class Net::HTTP::Persistent attr_accessor :retry_change_requests ## - # Creates a new Net::HTTP::Persistent. + # Creates a new Bundler::Persistent::Net::HTTP::Persistent. # # Set +name+ to keep your connections apart from everybody else's. Not # required currently, but highly recommended. Your library name should be @@ -594,7 +594,7 @@ class Net::HTTP::Persistent use_ssl = uri.scheme.downcase == 'https' if use_ssl then - raise Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless + raise Bundler::Persistent::Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless HAVE_OPENSSL ssl_generation = @ssl_generation @@ -728,7 +728,7 @@ class Net::HTTP::Persistent } or not @reuse_ssl_sessions then Net::HTTP else - Net::HTTP::Persistent::SSLReuse + Bundler::Persistent::Net::HTTP::Persistent::SSLReuse end end @@ -1065,7 +1065,7 @@ class Net::HTTP::Persistent # Returns the request. def request_setup req_or_uri # :nodoc: - req = if req_or_uri.is_a?(URI) then + req = if URI === req_or_uri then Net::HTTP::Get.new req_or_uri.request_uri else req_or_uri @@ -1092,7 +1092,7 @@ class Net::HTTP::Persistent # # Uses the current thread by default. # - # If you've used Net::HTTP::Persistent across multiple threads you should + # If you've used Bundler::Persistent::Net::HTTP::Persistent across multiple threads you should # call this in each thread when you're done making HTTP requests. # # *NOTE*: Calling shutdown for another thread can be dangerous! @@ -1227,4 +1227,5 @@ application: end -require 'net/http/persistent/ssl_reuse' +require 'bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse' + diff --git a/lib/bundler/vendor/net/http/persistent/ssl_reuse.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse.rb index d73aa54996..1b6b789f6d 100644 --- a/lib/bundler/vendor/net/http/persistent/ssl_reuse.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/ssl_reuse.rb @@ -7,7 +7,7 @@ # This class is an implementation detail and is subject to change or removal # at any time. -class Net::HTTP::Persistent::SSLReuse < Net::HTTP +class Bundler::Persistent::Net::HTTP::Persistent::SSLReuse < Net::HTTP @is_proxy_class = false @proxy_addr = nil @@ -126,3 +126,4 @@ class Net::HTTP::Persistent::SSLReuse < Net::HTTP private :connect end + diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb index 9ed67a44e2..563e361bd3 100644 --- a/lib/bundler/vendor/thor/lib/thor.rb +++ b/lib/bundler/vendor/thor/lib/thor.rb @@ -1,7 +1,7 @@ require "set" require "bundler/vendor/thor/lib/thor/base" -class Bundler::Thor # rubocop:disable ClassLength +class Bundler::Thor class << self # Allows for custom "Command" package naming. # @@ -9,7 +9,7 @@ class Bundler::Thor # rubocop:disable ClassLength # name<String> # options<Hash> # - def package_name(name, options = {}) + def package_name(name, _ = {}) @package_name = name.nil? || name == "" ? nil : name end @@ -57,7 +57,9 @@ class Bundler::Thor # rubocop:disable ClassLength command.usage = usage if usage command.description = description if description else - @usage, @desc, @hide = usage, description, options[:hide] || false + @usage = usage + @desc = description + @hide = options[:hide] || false end end @@ -156,6 +158,10 @@ class Bundler::Thor # rubocop:disable ClassLength end alias_method :option, :method_option + def disable_class_options + @disable_class_options = true + end + # Prints help information for the given command. # # ==== Parameters @@ -170,7 +176,7 @@ class Bundler::Thor # rubocop:disable ClassLength shell.say "Usage:" shell.say " #{banner(command)}" shell.say - class_options_help(shell, nil => command.options.map { |_, o| o }) + class_options_help(shell, nil => command.options.values) if command.long_description shell.say "Description:" shell.print_wrapped(command.long_description, :indent => 2) @@ -231,8 +237,9 @@ class Bundler::Thor # rubocop:disable ClassLength define_method(subcommand) do |*args| args, opts = Bundler::Thor::Arguments.split(args) - args.unshift("help") if opts.include? "--help" or opts.include? "-h" - invoke subcommand_class, args, opts, :invoked_via_subcommand => true, :class_options => options + invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}] + invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h") + invoke subcommand_class, *invoke_args end end alias_method :subtask, :subcommand @@ -320,6 +327,7 @@ class Bundler::Thor # rubocop:disable ClassLength end protected + def stop_on_unknown_option #:nodoc: @stop_on_unknown_option ||= Set.new end @@ -345,12 +353,14 @@ class Bundler::Thor # rubocop:disable ClassLength opts.clear end else - args, opts = given_args, nil + args = given_args + opts = nil command = dynamic_command_class.new(meth) end opts = given_opts || opts || [] - config.merge!(:current_command => command, :command_options => command.options) + config[:current_command] = command + config[:command_options] = command.options instance = new(args, opts, config) yield instance if block_given? @@ -380,17 +390,18 @@ class Bundler::Thor # rubocop:disable ClassLength @usage ||= nil @desc ||= nil @long_desc ||= nil + @disable_class_options ||= nil if @usage && @desc base_class = @hide ? Bundler::Thor::HiddenCommand : Bundler::Thor::Command - commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options) - @usage, @desc, @long_desc, @method_options, @hide = nil + commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options, @disable_class_options) + @usage, @desc, @long_desc, @method_options, @hide, @disable_class_options = nil true elsif all_commands[meth] || meth == "method_missing" true else - puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " << - "Call desc if you want this method to be available as command or declare it inside a " << + puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \ + "Call desc if you want this method to be available as command or declare it inside a " \ "no_commands{} block. Invoked from #{caller[1].inspect}." false end @@ -405,11 +416,7 @@ class Bundler::Thor # rubocop:disable ClassLength # Retrieve the command name from given args. def retrieve_command_name(args) #:nodoc: meth = args.first.to_s unless args.empty? - if meth && (map[meth] || meth !~ /^\-/) - args.shift - else - nil - end + args.shift if meth && (map[meth] || meth !~ /^\-/) end alias_method :retrieve_task_name, :retrieve_command_name @@ -421,20 +428,20 @@ class Bundler::Thor # rubocop:disable ClassLength # +normalize_command_name+ also converts names like +animal-prison+ # into +animal_prison+. def normalize_command_name(meth) #:nodoc: - return default_command.to_s.gsub("-", "_") unless meth + return default_command.to_s.tr("-", "_") unless meth possibilities = find_command_possibilities(meth) - if possibilities.size > 1 - fail AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" - elsif possibilities.size < 1 - meth = meth || default_command + raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" if possibilities.size > 1 + + if possibilities.empty? + meth ||= default_command elsif map[meth] meth = map[meth] else meth = possibilities.first end - meth.to_s.gsub("-", "_") # treat foo-bar as foo_bar + meth.to_s.tr("-", "_") # treat foo-bar as foo_bar end alias_method :normalize_task_name, :normalize_command_name @@ -470,6 +477,7 @@ class Bundler::Thor # rubocop:disable ClassLength map HELP_MAPPINGS => :help desc "help [COMMAND]", "Describe available commands or one specific command" + disable_class_options def help(command = nil, subcommand = false) if command if self.class.subcommands.include? command diff --git a/lib/bundler/vendor/thor/lib/thor/actions.rb b/lib/bundler/vendor/thor/lib/thor/actions.rb index 5a82dfd45f..9f1c9f23e8 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions.rb @@ -73,14 +73,15 @@ class Bundler::Thor # def initialize(args = [], options = {}, config = {}) self.behavior = case config[:behavior].to_s - when "force", "skip" - _cleanup_options_and_set(options, config[:behavior]) - :invoke - when "revoke" - :revoke - else - :invoke - end + when "force", "skip" + _cleanup_options_and_set(options, config[:behavior]) + :invoke + when "revoke" + :revoke + else + :invoke + end + super self.destination_root = config[:destination_root] end @@ -129,7 +130,7 @@ class Bundler::Thor # Receives a file or directory and search for it in the source paths. # - def find_in_source_paths(file) # rubocop:disable MethodLength + def find_in_source_paths(file) possible_files = [file, file + TEMPLATE_EXTNAME] relative_root = relative_to_original_destination_root(destination_root, false) @@ -146,13 +147,13 @@ class Bundler::Thor message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. " end - if source_paths.empty? - message << "Currently you have no source paths." - else - message << "Your current source paths are: \n#{source_paths.join("\n")}" - end + message << if source_paths.empty? + "Currently you have no source paths." + else + "Your current source paths are: \n#{source_paths.join("\n")}" + end - fail Error, message + raise Error, message end # Do something in the root or on a provided subfolder. If a relative path @@ -214,10 +215,10 @@ class Bundler::Thor say_status :apply, path, verbose shell.padding += 1 if verbose - if is_uri - contents = open(path, "Accept" => "application/x-thor-template") { |io| io.read } + contents = if is_uri + open(path, "Accept" => "application/x-thor-template", &:read) else - contents = open(path) { |io| io.read } + open(path, &:read) end instance_eval(contents, path) @@ -250,9 +251,7 @@ class Bundler::Thor say_status :run, desc, config.fetch(:verbose, true) - unless options[:pretend] - config[:capture] ? `#{command}` : system("#{command}") - end + !options[:pretend] && config[:capture] ? `#{command}` : system(command.to_s) end # Executes a ruby script (taking into account WIN32 platform quirks). @@ -308,7 +307,7 @@ class Bundler::Thor def _cleanup_options_and_set(options, key) #:nodoc: case options when Array - %w[--force -f --skip -s].each { |i| options.delete(i) } + %w(--force -f --skip -s).each { |i| options.delete(i) } options << "--#{key}" when Hash [:force, :skip, "force", "skip"].each { |i| options.delete(i) } diff --git a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb index a0f5640333..ade3f85bde 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb @@ -84,7 +84,7 @@ class Bundler::Thor def force_or_skip_or_conflict(force, skip, &block) if force say_status :force, :yellow - block.call unless pretend? + yield unless pretend? elsif skip say_status :skip, :yellow else diff --git a/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb b/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb index be437922b6..7577d12533 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/create_link.rb @@ -14,7 +14,7 @@ class Bundler::Thor # # create_link "config/apache.conf", "/etc/apache.conf" # - def create_link(destination, *args, &block) + def create_link(destination, *args) config = args.last.is_a?(Hash) ? args.pop : {} source = args.first action CreateLink.new(self, destination, source, config) diff --git a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb index 1a2e25da2f..f555f7b7e0 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb @@ -72,7 +72,7 @@ class Bundler::Thor protected - def execute! # rubocop:disable MethodLength + def execute! lookup = Util.escape_globs(source) lookup = config[:recursive] ? File.join(lookup, "**") : lookup lookup = file_level_lookup(lookup) @@ -85,7 +85,7 @@ class Bundler::Thor case file_source when /\.empty_directory$/ - dirname = File.dirname(file_destination).gsub(/\/\.$/, "") + dirname = File.dirname(file_destination).gsub(%r{/\.$}, "") next if dirname == given_destination base.empty_directory(dirname, config) when /#{TEMPLATE_EXTNAME}$/ diff --git a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb index cdc3768b4c..309cb31d9d 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb @@ -32,7 +32,8 @@ class Bundler::Thor # config<Hash>:: give :verbose => false to not log the status. # def initialize(base, destination, config = {}) - @base, @config = base, {:verbose => true}.merge(config) + @base = base + @config = {:verbose => true}.merge(config) self.destination = destination end @@ -80,11 +81,10 @@ class Bundler::Thor # given_destination #=> baz # def destination=(destination) - if destination - @given_destination = convert_encoded_instructions(destination.to_s) - @destination = ::File.expand_path(@given_destination, base.destination_root) - @relative_destination = base.relative_to_original_destination_root(@destination) - end + return unless destination + @given_destination = convert_encoded_instructions(destination.to_s) + @destination = ::File.expand_path(@given_destination, base.destination_root) + @relative_destination = base.relative_to_original_destination_root(@destination) end # Filenames in the encoded form are converted. If you have a file: @@ -113,7 +113,7 @@ class Bundler::Thor on_conflict_behavior(&block) else say_status :create, :green - block.call unless pretend? + yield unless pretend? end destination @@ -121,7 +121,7 @@ class Bundler::Thor # What to do when the destination file already exists. # - def on_conflict_behavior(&block) + def on_conflict_behavior say_status :exist, :blue end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb index 2bdc78f578..54272fc0c6 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -26,7 +26,7 @@ class Bundler::Thor create_file destination, nil, config do content = File.binread(source) - content = block.call(content) if block + content = yield(content) if block content end if config[:mode] == :preserve @@ -49,7 +49,7 @@ class Bundler::Thor # # link_file "doc/README" # - def link_file(source, *args, &block) + def link_file(source, *args) config = args.last.is_a?(Hash) ? args.pop : {} destination = args.first || source source = File.expand_path(find_in_source_paths(source.to_s)) @@ -82,7 +82,7 @@ class Bundler::Thor render = open(source) { |input| input.binmode.read } destination ||= if block_given? - block.arity == 1 ? block.call(render) : block.call + block.arity == 1 ? yield(render) : yield else File.basename(source) end @@ -110,11 +110,11 @@ class Bundler::Thor destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "") source = File.expand_path(find_in_source_paths(source.to_s)) - context = instance_eval("binding") + context = config.delete(:context) || instance_eval("binding") create_file destination, nil, config do - content = ERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context) - content = block.call(content) if block + content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context) + content = yield(content) if block content end end @@ -154,7 +154,7 @@ class Bundler::Thor # def prepend_to_file(path, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} - config.merge!(:after => /\A/) + config[:after] = /\A/ insert_into_file(path, *(args << config), &block) end alias_method :prepend_file, :prepend_to_file @@ -176,7 +176,7 @@ class Bundler::Thor # def append_to_file(path, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} - config.merge!(:before => /\z/) + config[:before] = /\z/ insert_into_file(path, *(args << config), &block) end alias_method :append_file, :append_to_file @@ -200,7 +200,7 @@ class Bundler::Thor # def inject_into_class(path, klass, *args, &block) config = args.last.is_a?(Hash) ? args.pop : {} - config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/) + config[:after] = /class #{klass}\n|class #{klass} .*\n/ insert_into_file(path, *(args << config), &block) end @@ -285,7 +285,7 @@ class Bundler::Thor # def remove_file(path, config = {}) return unless behavior == :invoke - path = File.expand_path(path, destination_root) + path = File.expand_path(path, destination_root) say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) ::FileUtils.rm_rf(path) if !options[:pretend] && File.exist?(path) @@ -301,8 +301,8 @@ class Bundler::Thor @output_buffer.concat(string) end - def capture(*args, &block) - with_output_buffer { block.call(*args) } + def capture(*args) + with_output_buffer { yield(*args) } end def with_output_buffer(buf = "") #:nodoc: @@ -312,5 +312,16 @@ class Bundler::Thor ensure self.output_buffer = old_buffer end + + # Bundler::Thor::Actions#capture depends on what kind of buffer is used in ERB. + # Thus CapturableERB fixes ERB to use String buffer. + class CapturableERB < ERB + def set_eoutvar(compiler, eoutvar = "_erbout") + compiler.put_cmd = "#{eoutvar}.concat" + compiler.insert_cmd = "#{eoutvar}.concat" + compiler.pre_cmd = ["#{eoutvar} = ''"] + compiler.post_cmd = [eoutvar] + end + end end end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb index 91ab245ae1..781ee63140 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb @@ -22,11 +22,8 @@ class Bundler::Thor # end # def insert_into_file(destination, *args, &block) - if block_given? - data, config = block, args.shift - else - data, config = args.shift, args.shift - end + data = block_given? ? block : args.shift + config = args.shift action InjectIntoFile.new(self, destination, data, config) end alias_method :inject_into_file, :insert_into_file @@ -39,9 +36,9 @@ class Bundler::Thor @behavior, @flag = if @config.key?(:after) [:after, @config.delete(:after)] - else - [:before, @config.delete(:before)] - end + else + [:before, @config.delete(:before)] + end @replacement = data.is_a?(Proc) ? data.call : data @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp) @@ -94,12 +91,11 @@ class Bundler::Thor # Adds the content to the file. # def replace!(regexp, string, force) - unless base.options[:pretend] - content = File.binread(destination) - if force || !content.include?(replacement) - content.gsub!(regexp, string) - File.open(destination, "wb") { |file| file.write(content) } - end + return if base.options[:pretend] + content = File.binread(destination) + if force || !content.include?(replacement) + content.gsub!(regexp, string) + File.open(destination, "wb") { |file| file.write(content) } end end end diff --git a/lib/bundler/vendor/thor/lib/thor/base.rb b/lib/bundler/vendor/thor/lib/thor/base.rb index c3667521a5..a95974a62d 100644 --- a/lib/bundler/vendor/thor/lib/thor/base.rb +++ b/lib/bundler/vendor/thor/lib/thor/base.rb @@ -14,11 +14,11 @@ class Bundler::Thor autoload :Group, "bundler/vendor/thor/lib/thor/group" # Shortcuts for help. - HELP_MAPPINGS = %w[-h -? --help -D] + HELP_MAPPINGS = %w(-h -? --help -D) # Bundler::Thor methods that should not be overwritten by the user. - THOR_RESERVED_WORDS = %w[invoke shell options behavior root destination_root relative_root - action add_file create_file in_root inside run run_ruby_script] + THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root + action add_file create_file in_root inside run run_ruby_script) TEMPLATE_EXTNAME = ".tt" @@ -41,8 +41,8 @@ class Bundler::Thor # # config<Hash>:: Configuration for this Bundler::Thor class. # - def initialize(args = [], local_options = {}, config = {}) # rubocop:disable MethodLength - parse_options = self.class.class_options + def initialize(args = [], local_options = {}, config = {}) + parse_options = config[:current_command] && config[:current_command].disable_class_options ? {} : self.class.class_options # The start method splits inbound arguments at the first argument # that looks like an option (starts with - or --). It then calls @@ -52,11 +52,13 @@ class Bundler::Thor command_options = config.delete(:command_options) # hook for start parse_options = parse_options.merge(command_options) if command_options if local_options.is_a?(Array) - array_options, hash_options = local_options, {} + array_options = local_options + hash_options = {} else # Handle the case where the class was explicitly instantiated # with pre-parsed options. - array_options, hash_options = [], local_options + array_options = [] + hash_options = local_options end # Let Bundler::Thor::Options parse the options first, so it can remove @@ -205,7 +207,7 @@ class Bundler::Thor # ==== Errors # ArgumentError:: Raised if you supply a required argument after a non required one. # - def argument(name, options = {}) # rubocop:disable MethodLength + def argument(name, options = {}) is_thor_reserved_word?(name, :argument) no_commands { attr_accessor name } @@ -219,11 +221,13 @@ class Bundler::Thor remove_argument name - arguments.each do |argument| - next if argument.required? - fail ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " << - "the non-required argument #{argument.human_name.inspect}." - end if required + if required + arguments.each do |argument| + next if argument.required? + raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \ + "the non-required argument #{argument.human_name.inspect}." + end + end options[:required] = required @@ -343,7 +347,7 @@ class Bundler::Thor # def all_commands @all_commands ||= from_superclass(:all_commands, Bundler::Thor::CoreExt::OrderedHash.new) - @all_commands.merge(commands) + @all_commands.merge!(commands) end alias_method :all_tasks, :all_commands @@ -467,11 +471,8 @@ class Bundler::Thor alias_method :public_task, :public_command def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc: - if has_namespace - fail UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." - else - fail UndefinedCommandError, "Could not find command #{command.inspect}." - end + raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace + raise UndefinedCommandError, "Could not find command #{command.inspect}." end alias_method :handle_no_task_error, :handle_no_command_error @@ -480,7 +481,7 @@ class Bundler::Thor msg << "no arguments" if args.empty? msg << "arguments " << args.inspect unless args.empty? msg << "\nUsage: #{banner(command).inspect}" - fail InvocationError, msg + raise InvocationError, msg end protected @@ -513,14 +514,13 @@ class Bundler::Thor padding = options.map { |o| o.aliases.size }.max.to_i * 4 options.each do |option| - unless option.hide - item = [option.usage(padding)] - item.push(option.description ? "# #{option.description}" : "") + next if option.hide + item = [option.usage(padding)] + item.push(option.description ? "# #{option.description}" : "") - list << item - list << ["", "# Default: #{option.default}"] if option.show_default? - list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum - end + list << item + list << ["", "# Default: #{option.default}"] if option.show_default? + list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum end shell.say(group_name ? "#{group_name} options:" : "Options:") @@ -531,7 +531,7 @@ class Bundler::Thor # Raises an error if the word given is a Bundler::Thor reserved word. def is_thor_reserved_word?(word, type) #:nodoc: return false unless THOR_RESERVED_WORDS.include?(word.to_s) - fail "#{word.inspect} is a Bundler::Thor reserved word and cannot be defined as #{type}" + raise "#{word.inspect} is a Bundler::Thor reserved word and cannot be defined as #{type}" end # Build an option and adds it to the given scope. @@ -566,7 +566,7 @@ class Bundler::Thor elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition commands[name.to_s] = command.clone else - fail ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." + raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." end end alias_method :find_and_refresh_task, :find_and_refresh_command @@ -649,7 +649,7 @@ class Bundler::Thor # SIGNATURE: The hook invoked by start. def dispatch(command, given_args, given_opts, config) #:nodoc: - fail NotImplementedError + raise NotImplementedError end end end diff --git a/lib/bundler/vendor/thor/lib/thor/command.rb b/lib/bundler/vendor/thor/lib/thor/command.rb index 72c8348cb6..aacf2ef719 100644 --- a/lib/bundler/vendor/thor/lib/thor/command.rb +++ b/lib/bundler/vendor/thor/lib/thor/command.rb @@ -1,9 +1,9 @@ class Bundler::Thor - class Command < Struct.new(:name, :description, :long_description, :usage, :options) + class Command < Struct.new(:name, :description, :long_description, :usage, :options, :disable_class_options) FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ - def initialize(name, description, long_description, usage, options = nil) - super(name.to_s, description, long_description, usage, options || {}) + def initialize(name, description, long_description, usage, options = nil, disable_class_options = false) + super(name.to_s, description, long_description, usage, options || {}, disable_class_options) end def initialize_copy(other) #:nodoc: @@ -33,7 +33,7 @@ class Bundler::Thor rescue ArgumentError => e handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e) rescue NoMethodError => e - handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (fail e) + handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e) end # Returns the formatted usage by injecting given required arguments @@ -50,7 +50,7 @@ class Bundler::Thor # Add usage with required arguments formatted << if klass && !klass.arguments.empty? usage.to_s.gsub(/^#{name}/) do |match| - match << " " << klass.arguments.map { |a| a.usage }.compact.join(" ") + match << " " << klass.arguments.map(&:usage).compact.join(" ") end else usage.to_s @@ -88,7 +88,7 @@ class Bundler::Thor end def sans_backtrace(backtrace, caller) #:nodoc: - saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ /^kernel\// && RUBY_ENGINE =~ /rbx/) } + saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) } saned - caller end @@ -105,7 +105,7 @@ class Bundler::Thor error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ end end - Task = Command # rubocop:disable ConstantName + Task = Command # A command that is hidden in help messages but still invocable. class HiddenCommand < Command @@ -113,7 +113,7 @@ class Bundler::Thor true end end - HiddenTask = HiddenCommand # rubocop:disable ConstantName + HiddenTask = HiddenCommand # A dynamic command that handles method missing scenarios. class DynamicCommand < Command @@ -129,5 +129,5 @@ class Bundler::Thor end end end - DynamicTask = DynamicCommand # rubocop:disable ConstantName + DynamicTask = DynamicCommand end diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb index 6cf61db812..de8c4713b4 100644 --- a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -28,6 +28,14 @@ class Bundler::Thor super(convert_key(key)) end + def fetch(key, *args) + super(convert_key(key), *args) + end + + def key?(key) + super(convert_key(key)) + end + def values_at(*indices) indices.map { |key| self[convert_key(key)] } end @@ -60,7 +68,7 @@ class Bundler::Thor # options.shebang # => "/usr/lib/local/ruby" # options.test_framework?(:rspec) # => options[:test_framework] == :rspec # - def method_missing(method, *args, &block) + def method_missing(method, *args) method = method.to_s if method =~ /^(\w+)\?$/ if args.empty? diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb index 19f3c3d43e..0f6e2e0af2 100644 --- a/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb +++ b/lib/bundler/vendor/thor/lib/thor/core_ext/io_binary_read.rb @@ -1,10 +1,12 @@ class IO #:nodoc: class << self - def binread(file, *args) - fail ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3 - File.open(file, "rb") do |f| - f.read(*args) + unless method_defined? :binread + def binread(file, *args) + raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3 + File.open(file, "rb") do |f| + f.read(*args) + end end - end unless method_defined? :binread + end end end diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb index 7e80672a07..76f1e43c65 100644 --- a/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb +++ b/lib/bundler/vendor/thor/lib/thor/core_ext/ordered_hash.rb @@ -1,96 +1,127 @@ class Bundler::Thor - module CoreExt #:nodoc: - if RUBY_VERSION >= "1.9" - class OrderedHash < ::Hash - end - else - # This class is based on the Ruby 1.9 ordered hashes. - # - # It keeps the semantics and most of the efficiency of normal hashes - # while also keeping track of the order in which elements were set. - # - class OrderedHash #:nodoc: - include Enumerable - - Node = Struct.new(:key, :value, :next, :prev) - - def initialize - @hash = {} + module CoreExt + class OrderedHash < ::Hash + if RUBY_VERSION < "1.9" + def initialize(*args, &block) + super + @keys = [] end - def [](key) - @hash[key] && @hash[key].value + def initialize_copy(other) + super + # make a deep copy of keys + @keys = other.keys end def []=(key, value) - if node = @hash[key] # rubocop:disable AssignmentInCondition - node.value = value - else - node = Node.new(key, value) - - if !defined?(@first) || @first.nil? - @first = @last = node - else - node.prev = @last - @last.next = node - @last = node - end - end - - @hash[key] = node - value + @keys << key unless key?(key) + super end def delete(key) - if node = @hash[key] # rubocop:disable AssignmentInCondition - prev_node = node.prev - next_node = node.next + if key? key + index = @keys.index(key) + @keys.delete_at index + end + super + end - next_node.prev = prev_node if next_node - prev_node.next = next_node if prev_node + def delete_if + super + sync_keys! + self + end - @first = next_node if @first == node - @last = prev_node if @last == node + alias_method :reject!, :delete_if - value = node.value - end - - @hash.delete(key) - value + def reject(&block) + dup.reject!(&block) end def keys - map { |k, v| k } + @keys.dup end def values - map { |k, v| v } + @keys.map { |key| self[key] } + end + + def to_hash + self + end + + def to_a + @keys.map { |key| [key, self[key]] } + end + + def each_key + return to_enum(:each_key) unless block_given? + @keys.each { |key| yield(key) } + self + end + + def each_value + return to_enum(:each_value) unless block_given? + @keys.each { |key| yield(self[key]) } + self end def each - return unless defined?(@first) && @first - yield [@first.key, @first.value] - node = @first - yield [node.key, node.value] while node = node.next # rubocop:disable AssignmentInCondition + return to_enum(:each) unless block_given? + @keys.each { |key| yield([key, self[key]]) } self end - def merge(other) - hash = self.class.new + def each_pair + return to_enum(:each_pair) unless block_given? + @keys.each { |key| yield(key, self[key]) } + self + end - each do |key, value| - hash[key] = value - end + alias_method :select, :find_all + + def clear + super + @keys.clear + self + end + + def shift + k = @keys.first + v = delete(k) + [k, v] + end - other.each do |key, value| - hash[key] = value + def merge!(other_hash) + if block_given? + other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v } + else + other_hash.each { |k, v| self[k] = v } end + self + end + + alias_method :update, :merge! + + def merge(other_hash, &block) + dup.merge!(other_hash, &block) + end - hash + # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not. + def replace(other) + super + @keys = other.keys + self end - def empty? - @hash.empty? + def inspect + "#<#{self.class} #{super}>" + end + + private + + def sync_keys! + @keys.delete_if { |k| !key?(k) } end end end diff --git a/lib/bundler/vendor/thor/lib/thor/error.rb b/lib/bundler/vendor/thor/lib/thor/error.rb index fc34c11268..2f816081f3 100644 --- a/lib/bundler/vendor/thor/lib/thor/error.rb +++ b/lib/bundler/vendor/thor/lib/thor/error.rb @@ -3,7 +3,7 @@ class Bundler::Thor # errors have their backtrace suppressed and are nicely shown to the user. # # Errors that are caused by the developer, like declaring a method which - # overwrites a thor keyword, it SHOULD NOT raise a Bundler::Thor::Error. This way, we + # overwrites a thor keyword, SHOULD NOT raise a Bundler::Thor::Error. This way, we # ensure that developer errors are shown with full backtrace. class Error < StandardError end @@ -11,11 +11,11 @@ class Bundler::Thor # Raised when a command was not found. class UndefinedCommandError < Error end - UndefinedTaskError = UndefinedCommandError # rubocop:disable ConstantName + UndefinedTaskError = UndefinedCommandError class AmbiguousCommandError < Error end - AmbiguousTaskError = AmbiguousCommandError # rubocop:disable ConstantName + AmbiguousTaskError = AmbiguousCommandError # Raised when a command was found, but not invoked properly. class InvocationError < Error diff --git a/lib/bundler/vendor/thor/lib/thor/group.rb b/lib/bundler/vendor/thor/lib/thor/group.rb index 13d168ad62..c95b708caa 100644 --- a/lib/bundler/vendor/thor/lib/thor/group.rb +++ b/lib/bundler/vendor/thor/lib/thor/group.rb @@ -4,7 +4,7 @@ require "bundler/vendor/thor/lib/thor/base" # is that it invokes all commands at once. It also include some methods that allows # invocations to be done at the class method, which are not available to Bundler::Thor # commands. -class Bundler::Thor::Group # rubocop:disable ClassLength +class Bundler::Thor::Group class << self # The description for this Bundler::Thor::Group. If none is provided, but a source root # exists, tries to find the USAGE one folder above it, otherwise searches @@ -53,7 +53,7 @@ class Bundler::Thor::Group # rubocop:disable ClassLength # The namespace/class given will have its options showed on the help # usage. Check invoke_from_option for more information. # - def invoke(*names, &block) # rubocop:disable MethodLength + def invoke(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} verbose = options.fetch(:verbose, true) @@ -62,7 +62,7 @@ class Bundler::Thor::Group # rubocop:disable ClassLength invocation_blocks[name] = block if block_given? class_eval <<-METHOD, __FILE__, __LINE__ - def _invoke_#{name.to_s.gsub(/\W/, "_")} + def _invoke_#{name.to_s.gsub(/\W/, '_')} klass, command = self.class.prepare_for_invocation(nil, #{name.inspect}) if klass @@ -107,21 +107,21 @@ class Bundler::Thor::Group # rubocop:disable ClassLength # invoked. The block receives two parameters, an instance of the current # class and the klass to be invoked. # - def invoke_from_option(*names, &block) # rubocop:disable MethodLength + def invoke_from_option(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} verbose = options.fetch(:verbose, :white) names.each do |name| unless class_options.key?(name) - fail ArgumentError, "You have to define the option #{name.inspect} " << - "before setting invoke_from_option." + raise ArgumentError, "You have to define the option #{name.inspect} " \ + "before setting invoke_from_option." end invocations[name] = true invocation_blocks[name] = block if block_given? class_eval <<-METHOD, __FILE__, __LINE__ - def _invoke_from_option_#{name.to_s.gsub(/\W/, "_")} + def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} return unless options[#{name.inspect}] value = options[#{name.inspect}] @@ -188,7 +188,7 @@ class Bundler::Thor::Group # rubocop:disable ClassLength group_options[human_name] ||= [] group_options[human_name] += klass.class_options.values.select do |class_option| base_options[class_option.name.to_sym].nil? && class_option.group.nil? && - !group_options.values.flatten.any? { |i| i.name == class_option.name } + !group_options.values.flatten.any? { |i| i.name == class_option.name } end yield klass if block_given? @@ -204,11 +204,11 @@ class Bundler::Thor::Group # rubocop:disable ClassLength end alias_method :printable_tasks, :printable_commands - def handle_argument_error(command, error, args, arity) #:nodoc: + def handle_argument_error(command, error, _args, arity) #:nodoc: msg = "#{basename} #{command.name} takes #{arity} argument" msg << "s" if arity > 1 msg << ", but it should not." - fail error, msg + raise error, msg end protected @@ -267,9 +267,9 @@ protected if block case block.arity when 3 - block.call(self, klass, command) + yield(self, klass, command) when 2 - block.call(self, klass) + yield(self, klass) when 1 instance_exec(klass, &block) end diff --git a/lib/bundler/vendor/thor/lib/thor/invocation.rb b/lib/bundler/vendor/thor/lib/thor/invocation.rb index 684df2c616..866d2212a7 100644 --- a/lib/bundler/vendor/thor/lib/thor/invocation.rb +++ b/lib/bundler/vendor/thor/lib/thor/invocation.rb @@ -108,8 +108,8 @@ class Bundler::Thor command, args, opts, config = args klass, command = _retrieve_class_and_command(name, command) - fail "Missing Bundler::Thor class for invoke #{name}" unless klass - fail "Expected Bundler::Thor class, got #{klass}" unless klass <= Bundler::Thor::Base + raise "Missing Bundler::Thor class for invoke #{name}" unless klass + raise "Expected Bundler::Thor class, got #{klass}" unless klass <= Bundler::Thor::Base args, opts, config = _parse_initialization_options(args, opts, config) klass.send(:dispatch, command, args, opts, config) do |instance| @@ -150,10 +150,9 @@ class Bundler::Thor # use the given name and return self as class. Otherwise, call # prepare_for_invocation in the current class. def _retrieve_class_and_command(name, sent_command = nil) #:nodoc: - case - when name.nil? + if name.nil? [self.class, nil] - when self.class.all_commands[name.to_s] + elsif self.class.all_commands[name.to_s] [self.class, name.to_s] else klass, command = self.class.prepare_for_invocation(nil, name) diff --git a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb index 84957903cd..dfe7398583 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb @@ -10,8 +10,8 @@ class Bundler::Thor type = options[:type] - fail ArgumentError, "#{class_name} name can't be nil." if name.nil? - fail ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) + raise ArgumentError, "#{class_name} name can't be nil." if name.nil? + raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) @name = name.to_s @description = options[:desc] @@ -44,11 +44,8 @@ class Bundler::Thor protected def validate! - if required? && !default.nil? - fail ArgumentError, "An argument cannot be required and have default value." - elsif @enum && !@enum.is_a?(Array) - fail ArgumentError, "An argument cannot have an enum other than an array." - end + raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? + raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array) end def valid_type?(type) diff --git a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb index c7bb648e31..1fd790f4b7 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb @@ -1,6 +1,6 @@ class Bundler::Thor class Arguments #:nodoc: # rubocop:disable ClassLength - NUMERIC = /(\d*\.\d+|\d+)/ + NUMERIC = /[-+]?(\d*\.\d+|\d+)/ # Receives an array of args and returns two arrays, one with arguments # and one with switches. @@ -24,7 +24,8 @@ class Bundler::Thor # Takes an array of Bundler::Thor::Argument objects. # def initialize(arguments = []) - @assigns, @non_assigned_required = {}, [] + @assigns = {} + @non_assigned_required = [] @switches = arguments arguments.each do |argument| @@ -49,7 +50,7 @@ class Bundler::Thor @assigns end - def remaining # rubocop:disable TrivialAccessors + def remaining @pile end @@ -73,7 +74,7 @@ class Bundler::Thor end def unshift(arg) - if arg.kind_of?(Array) + if arg.is_a?(Array) @pile = arg + @pile else @pile.unshift(arg) @@ -99,6 +100,7 @@ class Bundler::Thor while current_is_value? && peek.include?(":") key, value = shift.split(":", 2) + raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key hash[key] = value end hash @@ -128,13 +130,13 @@ class Bundler::Thor return shift if peek.is_a?(Numeric) unless peek =~ NUMERIC && $& == peek - fail MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}" + raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}" end value = $&.index(".") ? shift.to_f : shift.to_i if @switches.is_a?(Hash) && switch = @switches[name] if switch.enum && !switch.enum.include?(value) - fail MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" + raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" end end value @@ -150,9 +152,9 @@ class Bundler::Thor nil else value = shift - if @switches.is_a?(Hash) && switch = @switches[name] # rubocop:disable AssignmentInCondition + if @switches.is_a?(Hash) && switch = @switches[name] if switch.enum && !switch.enum.include?(value) - fail MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" + raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" end end value @@ -162,14 +164,12 @@ class Bundler::Thor # Raises an error if @non_assigned_required array is not empty. # def check_requirement! - unless @non_assigned_required.empty? - names = @non_assigned_required.map do |o| - o.respond_to?(:switch_name) ? o.switch_name : o.human_name - end.join("', '") - - class_name = self.class.name.split("::").last.downcase - fail RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'" - end + return if @non_assigned_required.empty? + names = @non_assigned_required.map do |o| + o.respond_to?(:switch_name) ? o.switch_name : o.human_name + end.join("', '") + class_name = self.class.name.split("::").last.downcase + raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'" end end end diff --git a/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/lib/bundler/vendor/thor/lib/thor/parser/option.rb index eb893617f4..032493075d 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/option.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/option.rb @@ -40,31 +40,33 @@ class Bundler::Thor # # By default all options are optional, unless :required is given. # - def self.parse(key, value) # rubocop:disable MethodLength + def self.parse(key, value) if key.is_a?(Array) name, *aliases = key else - name, aliases = key, [] + name = key + aliases = [] end name = name.to_s default = value type = case value - when Symbol - default = nil - if VALID_TYPES.include?(value) - value - elsif required = (value == :required) # rubocop:disable AssignmentInCondition - :string - end - when TrueClass, FalseClass - :boolean - when Numeric - :numeric - when Hash, Array, String - value.class.name.downcase.to_sym - end + when Symbol + default = nil + if VALID_TYPES.include?(value) + value + elsif required = (value == :required) # rubocop:disable AssignmentInCondition + :string + end + when TrueClass, FalseClass + :boolean + when Numeric + :numeric + when Hash, Array, String + value.class.name.downcase.to_sym + end + new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases) end @@ -86,7 +88,7 @@ class Bundler::Thor sample = "[#{sample}]" unless required? if boolean? - sample << ", [#{dasherize("no-" + human_name)}]" unless name == "force" + sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-") end if aliases.empty? @@ -107,7 +109,26 @@ class Bundler::Thor protected def validate! - fail ArgumentError, "An option cannot be boolean and required." if boolean? && required? + raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? + validate_default_type! + end + + def validate_default_type! + default_type = case @default + when nil + return + when TrueClass, FalseClass + required? ? :string : :boolean + when Numeric + :numeric + when Symbol + :string + when Hash, Array, String + @default.class.name.downcase.to_sym + end + + # TODO: This should raise an ArgumentError in a future version of Bundler::Thor + warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type end def dasherized? @@ -119,7 +140,7 @@ class Bundler::Thor end def dasherize(str) - (str.length > 1 ? "--" : "-") + str.gsub("_", "-") + (str.length > 1 ? "--" : "-") + str.tr("_", "-") end end end diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb index deac6a0c16..3ce8f55f94 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -14,7 +14,7 @@ class Bundler::Thor when true "--#{key}" when Array - "--#{key} #{value.map { |v| v.inspect }.join(' ')}" + "--#{key} #{value.map(&:inspect).join(' ')}" when Hash "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}" when nil, false @@ -40,7 +40,9 @@ class Bundler::Thor @non_assigned_required.delete(hash_options[key]) end - @shorts, @switches, @extra = {}, {}, [] + @shorts = {} + @switches = {} + @extra = [] options.each do |option| @switches[option.switch_name] = option @@ -52,7 +54,7 @@ class Bundler::Thor end end - def remaining # rubocop:disable TrivialAccessors + def remaining @extra end @@ -119,7 +121,7 @@ class Bundler::Thor def check_unknown! # an unknown option starts with - or -- and has no more --'s afterward. unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ } - fail UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty? + raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty? end protected @@ -207,7 +209,7 @@ class Bundler::Thor elsif option.lazy_default return option.lazy_default else - fail MalformattedArgumentError, "No value provided for option '#{switch}'" + raise MalformattedArgumentError, "No value provided for option '#{switch}'" end end diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index f0d7bfe2e0..4e96f7730a 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -11,10 +11,18 @@ require "pathname" class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version + def self.banner(command, all = false, subcommand = false) + "thor " + command.formatted_usage(self, all, subcommand) + end + + def self.exit_on_failure? + true + end + # Override Bundler::Thor#help so it can give information about any class and any method. # def help(meth = nil) - if meth && !self.respond_to?(meth) + if meth && !respond_to?(meth) initialize_thorfiles(meth) klass, command = Bundler::Thor::Util.find_class_and_command_by_namespace(meth) self.class.handle_no_command_error(command, false) if klass.nil? @@ -45,16 +53,18 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng # command in said directory. begin if File.directory?(File.expand_path(name)) - base, package = File.join(name, "main.thor"), :directory - contents = open(base) { |input| input.read } + base = File.join(name, "main.thor") + package = :directory + contents = open(base, &:read) else - base, package = name, :file - contents = open(name) { |input| input.read } + base = name + package = :file + contents = open(name, &:read) end rescue OpenURI::HTTPError raise Error, "Error opening URI '#{name}'" rescue Errno::ENOENT - fail Error, "Error opening file '#{name}'" + raise Error, "Error opening file '#{name}'" end say "Your Bundler::Thorfile contains:" @@ -108,9 +118,9 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng desc "uninstall NAME", "Uninstall a named Bundler::Thor module" def uninstall(name) - fail Error, "Can't find module '#{name}'" unless thor_yaml[name] + raise Error, "Can't find module '#{name}'" unless thor_yaml[name] say "Uninstalling #{name}." - FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}")) + FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s)) thor_yaml.delete(name) save_yaml(thor_yaml) @@ -120,7 +130,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng desc "update NAME", "Update a Bundler::Thor file from its original location" def update(name) - fail Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] + raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] say "Updating '#{name}' from #{thor_yaml[name][:location]}" @@ -138,9 +148,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng filename = install(thor_yaml[name][:location]) end - unless filename == old_filename - File.delete(File.join(thor_root, old_filename)) - end + File.delete(File.join(thor_root, old_filename)) unless filename == old_filename end desc "installed", "List the installed Bundler::Thor modules and commands" @@ -168,10 +176,6 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng private - def self.banner(command, all = false, subcommand = false) - "thor " + command.formatted_usage(self, all, subcommand) - end - def thor_root Bundler::Thor::Util.thor_root end @@ -198,10 +202,6 @@ private File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml } end - def self.exit_on_failure? - true - end - # Load the Bundler::Thorfiles. If relevant_to is supplied, looks for specific files # in the thor_root instead of loading them all. # @@ -263,11 +263,11 @@ private def thorfiles_relevant_to(meth) lookup = [meth, meth.split(":")[0...-1].join(":")] - files = thor_yaml.select do |k, v| + files = thor_yaml.select do |_, v| v[:namespaces] && !(v[:namespaces] & lookup).empty? end - files.map { |k, v| File.join(thor_root, "#{v[:filename]}") } + files.map { |_, v| File.join(thor_root, (v[:filename]).to_s) } end # Display information about the given klasses. If with_module is given, @@ -276,7 +276,7 @@ private def display_klasses(with_modules = false, show_internal = false, klasses = Bundler::Thor::Base.subclasses) klasses -= [Bundler::Thor, Bundler::Thor::Runner, Bundler::Thor::Group] unless show_internal - fail Error, "No Bundler::Thor commands available" if klasses.empty? + raise Error, "No Bundler::Thor commands available" if klasses.empty? show_modules if with_modules && !thor_yaml.empty? list = Hash.new { |h, k| h[k] = [] } @@ -306,8 +306,8 @@ private alias_method :display_tasks, :display_commands def show_modules #:nodoc: - info = [] - labels = %w[Modules Namespaces] + info = [] + labels = %w(Modules Namespaces) info << labels info << ["-" * labels[0].size, "-" * labels[1].size] diff --git a/lib/bundler/vendor/thor/lib/thor/shell.rb b/lib/bundler/vendor/thor/lib/thor/shell.rb index 91afdce2aa..e945549324 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell.rb @@ -9,7 +9,7 @@ class Bundler::Thor # it will use a colored log, otherwise it will use a basic one without color. # def shell - @shell ||= if ENV["THOR_SHELL"] && ENV["THOR_SHELL"].size > 0 + @shell ||= if ENV["THOR_SHELL"] && !ENV["THOR_SHELL"].empty? Bundler::Thor::Shell.const_get(ENV["THOR_SHELL"]) elsif RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ && !ENV["ANSICON"] Bundler::Thor::Shell::Basic diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index 278ffa3df0..52b6dfd225 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -3,14 +3,17 @@ require "io/console" if RUBY_VERSION > "1.9.2" class Bundler::Thor module Shell - class Basic # rubocop:disable ClassLength + class Basic attr_accessor :base attr_reader :padding # Initialize base, mute and padding to nil. # def initialize #:nodoc: - @base, @mute, @padding, @always_force = nil, false, 0, false + @base = nil + @mute = false + @padding = 0 + @always_force = false end # Mute everything that's inside given block @@ -24,7 +27,7 @@ class Bundler::Thor # Check if base is muted # - def mute? # rubocop:disable TrivialAccessors + def mute? @mute end @@ -34,6 +37,15 @@ class Bundler::Thor @padding = [0, value].max end + # Sets the output padding while executing a block and resets it. + # + def indent(count = 1) + orig_padding = padding + self.padding = padding + count + yield + self.padding = orig_padding + end + # Asks something to the user and receives a response. # # If asked to limit the correct responses, you can pass in an @@ -148,7 +160,9 @@ class Bundler::Thor def print_table(array, options = {}) # rubocop:disable MethodLength return if array.empty? - formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth] + formats = [] + indent = options[:indent].to_i + colwidth = options[:colwidth] options[:truncate] = terminal_width if options[:truncate] == true formats << "%-#{colwidth + 2}s" if colwidth @@ -161,12 +175,12 @@ class Bundler::Thor start.upto(colcount - 1) do |index| maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max maximas << maxima - if index == colcount - 1 - # Don't output 2 trailing spaces when printing the last column - formats << "%-s" - else - formats << "%-#{maxima + 2}s" - end + formats << if index == colcount - 1 + # Don't output 2 trailing spaces when printing the last column + "%-s" + else + "%-#{maxima + 2}s" + end end formats[0] = formats[0].insert(0, " " * indent) @@ -178,15 +192,15 @@ class Bundler::Thor row.each_with_index do |column, index| maxima = maximas[index] - if column.is_a?(Numeric) + f = if column.is_a?(Numeric) if index == row.size - 1 # Don't output 2 trailing spaces when printing the last column - f = "%#{maxima}s" + "%#{maxima}s" else - f = "%#{maxima}s " + "%#{maxima}s " end else - f = formats[index] + formats[index] end sentence << f % column.to_s end @@ -211,7 +225,7 @@ class Bundler::Thor paras = message.split("\n\n") paras.map! do |unwrapped| - unwrapped.strip.gsub(/\n/, " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") } + unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") } end paras.each do |para| @@ -230,7 +244,7 @@ class Bundler::Thor # destination<String>:: the destination file to solve conflicts # block<Proc>:: an optional block that returns the value to be used in diff # - def file_collision(destination) # rubocop:disable MethodLength + def file_collision(destination) return true if @always_force options = block_given? ? "[Ynaqdh]" : "[Ynaqh]" @@ -249,7 +263,7 @@ class Bundler::Thor return @always_force = true when is?(:quit) say "Aborting..." - fail SystemExit + raise SystemExit when is?(:diff) show_diff(destination, yield) if block_given? say "Retrying..." @@ -262,10 +276,10 @@ class Bundler::Thor # This code was copied from Rake, available under MIT-LICENSE # Copyright (c) 2003, 2004 Jim Weirich def terminal_width - if ENV["THOR_COLUMNS"] - result = ENV["THOR_COLUMNS"].to_i + result = if ENV["THOR_COLUMNS"] + ENV["THOR_COLUMNS"].to_i else - result = unix? ? dynamic_width : 80 + unix? ? dynamic_width : 80 end result < 10 ? 80 : result rescue @@ -284,7 +298,7 @@ class Bundler::Thor # Apply color to the given string with optional bold. Disabled in the # Bundler::Thor::Shell::Basic class. # - def set_color(string, *args) #:nodoc: + def set_color(string, *) #:nodoc: string end @@ -353,11 +367,11 @@ class Bundler::Thor end def dynamic_width_stty - %x(stty size 2>/dev/null).split[1].to_i + `stty size 2>/dev/null`.split[1].to_i end def dynamic_width_tput - %x(tput cols 2>/dev/null).to_i + `tput cols 2>/dev/null`.to_i end def unix? @@ -370,7 +384,7 @@ class Bundler::Thor if chars.length <= width chars.join else - ( chars[0, width - 3].join) + "..." + chars[0, width - 3].join + "..." end end end @@ -381,7 +395,8 @@ class Bundler::Thor end else def as_unicode - old, $KCODE = $KCODE, "U" + old = $KCODE + $KCODE = "U" yield ensure $KCODE = old @@ -391,7 +406,7 @@ class Bundler::Thor def ask_simply(statement, color, options) default = options[:default] message = [statement, ("(#{default})" if default), nil].uniq.join(" ") - message = prepare_message(message, color) + message = prepare_message(message, *color) result = Bundler::Thor::LineEditor.readline(message, options) return unless result diff --git a/lib/bundler/vendor/thor/lib/thor/shell/color.rb b/lib/bundler/vendor/thor/lib/thor/shell/color.rb index 1e2d26cfc5..da289cb50c 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/color.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/color.rb @@ -134,7 +134,7 @@ class Bundler::Thor # for diff. # def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) + return true if defined?(Diff::LCS) return @diff_lcs_loaded unless @diff_lcs_loaded.nil? @diff_lcs_loaded = begin diff --git a/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/lib/bundler/vendor/thor/lib/thor/shell/html.rb index e1ea0de599..83d2054988 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/html.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/html.rb @@ -51,13 +51,13 @@ class Bundler::Thor def set_color(string, *colors) if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } html_colors = colors.map { |color| lookup_color(color) } - "<span style=\"#{html_colors.join("; ")};\">#{string}</span>" + "<span style=\"#{html_colors.join('; ')};\">#{string}</span>" else color, bold = colors html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) styles = [html_color] styles << BOLD if bold - "<span style=\"#{styles.join("; ")};\">#{string}</span>" + "<span style=\"#{styles.join('; ')};\">#{string}</span>" end end @@ -68,7 +68,7 @@ class Bundler::Thor # # TODO: Implement #ask for Bundler::Thor::Shell::HTML def ask(statement, color = nil) - fail NotImplementedError, "Implement #ask for Bundler::Thor::Shell::HTML" + raise NotImplementedError, "Implement #ask for Bundler::Thor::Shell::HTML" end protected @@ -111,7 +111,7 @@ class Bundler::Thor # for diff. # def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) + return true if defined?(Diff::LCS) return @diff_lcs_loaded unless @diff_lcs_loaded.nil? @diff_lcs_loaded = begin diff --git a/lib/bundler/vendor/thor/lib/thor/util.rb b/lib/bundler/vendor/thor/lib/thor/util.rb index f4e98fc19f..5d03177a28 100644 --- a/lib/bundler/vendor/thor/lib/thor/util.rb +++ b/lib/bundler/vendor/thor/lib/thor/util.rb @@ -64,7 +64,7 @@ class Bundler::Thor new_constants = Bundler::Thor::Base.subclasses.dup Bundler::Thor::Base.subclasses.replace(old_constants) - new_constants.map! { |c| c.namespace } + new_constants.map!(&:namespace) new_constants.compact! new_constants end @@ -72,7 +72,7 @@ class Bundler::Thor # Returns the thor classes declared inside the given class. # def thor_classes_in(klass) - stringfied_constants = klass.constants.map { |c| c.to_s } + stringfied_constants = klass.constants.map(&:to_s) Bundler::Thor::Base.subclasses.select do |subclass| next unless subclass.name stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", "")) @@ -103,7 +103,7 @@ class Bundler::Thor # def camel_case(str) return str if str !~ /_/ && str =~ /[A-Z]+.*/ - str.split("_").map { |i| i.capitalize }.join + str.split("_").map(&:capitalize).join end # Receives a namespace and tries to retrieve a Bundler::Thor or Bundler::Thor::Group class @@ -135,7 +135,8 @@ class Bundler::Thor klass = Bundler::Thor::Util.find_by_namespace(pieces.join(":")) end unless klass # look for a Bundler::Thor::Group with the right name - klass, command = Bundler::Thor::Util.find_by_namespace(namespace), nil + klass = Bundler::Thor::Util.find_by_namespace(namespace) + command = nil end if !klass && fallback # try a command in the default namespace command = namespace @@ -163,7 +164,7 @@ class Bundler::Thor end end - def user_home # rubocop:disable MethodLength + def user_home @@user_home ||= if ENV["HOME"] ENV["HOME"] elsif ENV["USERPROFILE"] @@ -188,7 +189,7 @@ class Bundler::Thor # Returns the root where thor files are located, depending on the OS. # def thor_root - File.join(user_home, ".thor").gsub(/\\/, "/") + File.join(user_home, ".thor").tr('\\', "/") end # Returns the files in the thor root. On Windows thor_root will be something @@ -216,7 +217,7 @@ class Bundler::Thor # Return the path to the ruby interpreter taking into account multiple # installations and windows extensions. # - def ruby_command # rubocop:disable MethodLength + def ruby_command @ruby_command ||= begin ruby_name = RbConfig::CONFIG["ruby_install_name"] ruby = File.join(RbConfig::CONFIG["bindir"], ruby_name) diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb index 74b020a5ab..a6d838b103 100644 --- a/lib/bundler/vendor/thor/lib/thor/version.rb +++ b/lib/bundler/vendor/thor/lib/thor/version.rb @@ -1,3 +1,3 @@ class Bundler::Thor - VERSION = "0.19.1" + VERSION = "0.19.4" end diff --git a/lib/bundler/vendored_persistent.rb b/lib/bundler/vendored_persistent.rb index d5ae513c59..729ac6b6f5 100644 --- a/lib/bundler/vendored_persistent.rb +++ b/lib/bundler/vendored_persistent.rb @@ -6,7 +6,12 @@ begin rescue LoadError # some Ruby builds don't have OpenSSL end - -vendor = File.expand_path("../vendor", __FILE__) -$:.unshift(vendor) unless $:.include?(vendor) -require "net/http/persistent" +module Bundler + module Persistent + module Net + module HTTP + end + end + end +end +require "bundler/vendor/net-http-persistent/lib/net/http/persistent" diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 8fa8d18e84..35c3896482 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -7,5 +7,18 @@ 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.13.0.rc.2" unless defined?(::Bundler::VERSION) + VERSION = "1.14.6" unless defined?(::Bundler::VERSION) + + def self.overwrite_loaded_gem_version + begin + require "rubygems" + rescue LoadError + return + end + return unless bundler_spec = Gem.loaded_specs["bundler"] + return if bundler_spec.version == VERSION + bundler_spec.version = Bundler::VERSION + end + private_class_method :overwrite_loaded_gem_version + overwrite_loaded_gem_version end diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb index 3ef5addcee..c6b96d815a 100644 --- a/lib/bundler/worker.rb +++ b/lib/bundler/worker.rb @@ -25,11 +25,8 @@ module Bundler @request_queue = Queue.new @response_queue = Queue.new @func = func - @threads = Array.new(size) do |i| - Thread.start { process_queue(i) }.tap do |thread| - thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=) - end - end + @size = size + @threads = nil trap("INT") { abort_threads } end @@ -37,6 +34,7 @@ module Bundler # # @param obj [String] mostly it is name of spec that should be downloaded def enq(obj) + create_threads unless @threads @request_queue.enq obj end @@ -70,13 +68,38 @@ module Bundler # 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 + return unless @threads @threads.each { @request_queue.enq POISON } @threads.each(&:join) + @threads = nil end def abort_threads + return unless @threads + Bundler.ui.debug("\n#{caller.join("\n")}") @threads.each(&:exit) exit 1 end + + def create_threads + creation_errors = [] + + @threads = Array.new(@size) do |i| + begin + Thread.start { process_queue(i) }.tap do |thread| + thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=) + end + rescue ThreadError => e + creation_errors << e + nil + end + end.compact + + return if creation_errors.empty? + + message = "Failed to create threads for the #{name} worker: #{creation_errors.map(&:to_s).uniq.join(", ")}" + raise ThreadCreationError, message if @threads.empty? + Bundler.ui.info message + end end end diff --git a/man/bundle-check.ronn b/man/bundle-check.ronn new file mode 100644 index 0000000000..b299da422f --- /dev/null +++ b/man/bundle-check.ronn @@ -0,0 +1,26 @@ +bundle-check(1) -- Verifies if dependencies are satisfied by installed gems +=========================================================================== + +## SYNOPSIS + +`bundle check` [--dry-run] + [--gemfile=FILE] + [--path=PATH] + +## DESCRIPTION + +`check` searches the local machine for each of the gems requested in the +Gemfile. If 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. + +## OPTIONS + +* `--dry-run`: + Locks the `Gemfile(5)` before running the command. +* `--gemfile`: + Use the specified gemfile instead of the `Gemfile(5)` +* `--path`: + Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). + Bundler will remember this value for future installs on this machine. diff --git a/man/bundle-clean.ronn b/man/bundle-clean.ronn new file mode 100644 index 0000000000..de23991782 --- /dev/null +++ b/man/bundle-clean.ronn @@ -0,0 +1,18 @@ +bundle-clean(1) -- Cleans up unused gems in your bundler directory +================================================================== + +## SYNOPSIS + +`bundle clean` [--dry-run] [--force] + +## DESCRIPTION + +This command will remove all unused gems in your bundler directory. This is +useful when you have made many changes to your gem dependencies. + +## OPTIONS + +* `--dry-run`: + Print the changes, but do not clean the unused gems. +* `--force`: + Force a clean even if `--path` is not set. diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn index 3ea6d10973..926adcbb09 100644 --- a/man/bundle-config.ronn +++ b/man/bundle-config.ronn @@ -65,7 +65,7 @@ The options that can be configured are: 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 + `--path ...` setting will show up by calling `gem list`. Accordingly, gems installed to other locations will not get listed. * `without`: @@ -142,7 +142,7 @@ learn more about their operation in [bundle install(1)][bundle-install]. and key in PEM format. * `cache_path` (`BUNDLE_CACHE_PATH`): The directory that bundler will place cached gems in when running <code>bundle package</code>, and that bundler - will look in when installing gems. + will look in when installing gems. Defaults to `vendor/bundle`. * `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`): When set, Gemfiles containing multiple sources will produce errors instead of warnings. Use `bundle config --delete disable_multisource` to unset. @@ -155,6 +155,73 @@ learn more about their operation in [bundle install(1)][bundle-install]. The number of redirects allowed for network requests. Defaults to `5`. * `timeout` (`BUNDLE_TIMEOUT`): The seconds allowed before timing out for network requests. Defaults to `10`. +* `force_ruby_platform` (`BUNDLE_FORCE_RUBY_PLATFORM`): + Ignore the current machine's platform and install only `ruby` platform gems. + As a result, gems with native extensions will be compiled from source. +* `specific_platform` (`BUNDLE_SPECIFIC_PLATFORM`): + Allow bundler to resolve for the specific running platform and store it in + the lockfile, instead of only using a generic platform. + A specific platform is the exact platform triple reported by + `Gem::Platform.local`, such as `x86_64-darwin-16` or `universal-java-1.8`. + On the other hand, generic platforms are those such as `ruby`, `mswin`, or + `java`. In this example, `x86_64-darwin-16` would map to `ruby` and + `universal-java-1.8` to `java`. +* `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`): + Allow installing gems even if they do not match the checksum provided by + RubyGems. +* `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`): + Stop Bundler from checking if a newer Bundler version is available on + rubygems.org. +* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): + Allow Bundler to use cached data when installing without network access. +* `auto_install` (`BUNDLE_AUTO_INSTALL`): + Automatically run `bundle install` when gems are missing. +* `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`): + Cache gems for all platforms. +* `cache_all` (`BUNDLE_CACHE_ALL`): + Cache all gems, including path and git gems. +* `clean` (`BUNDLE_CLEAN`): + Whether Bundler should run `bundle clean` automatically after + `bundle install`. +* `console` (`BUNDLE_CONSOLE`): + The console that `bundle console` starts. Defaults to `irb`. +* `disable_exec_load` (`BUNDLE_DISABLE_EXEC_LOAD`): + Stop Bundler from using `load` to launch an executable in-process in + `bundle exec`. +* `disable_local_branch_check` (`BUNDLE_DISABLE_LOCAL_BRANCH_CHECK`): + Allow Bundler to use a local git override without a branch specified in the + Gemfile. +* `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`): + Stop Bundler from accessing gems installed to RubyGems' normal location. +* `jobs` (`BUNDLE_JOBS`): + The number of gems Bundler can install in parallel. Defaults to 1. +* `major_deprecations` (`BUNDLE_MAJOR_DEPRECATIONS`): + Whether Bundler should print deprecation warnings for behavior that will + be changed in the next major version. +* `no_install` (`BUNDLE_NO_INSTALL`): + Whether `bundle package` should skip installing gems. +* `no_prune` (`BUNDLE_NO_PRUNE`): + Whether Bundler should leave outdated gems unpruned when caching. +* `only_update_to_newer_versions` (`BUNDLE_ONLY_UPDATE_TO_NEWER_VERSIONS`): + During `bundle update`, only resolve to newer versions of the gems in the + lockfile. +* `plugins` (`BUNDLE_PLUGINS`): + Enable Bundler's experimental plugin system. +* `shebang` (`BUNDLE_SHEBANG`): + The program name that should be invoked for generated binstubs. Defaults to + the ruby install name used to generate the binstub. +* `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): + Silence the warning Bundler prints when installing gems as root. +* `ssl_verify_mode` (`BUNDLE_SSL_VERIFY_MODE`): + The SSL verification mode Bundler uses when making HTTPS requests. + Defaults to verify peer. +* `system_bindir` (`BUNDLE_SYSTEM_BINDIR`): + The location where RubyGems installs binstubs. Defaults to `Gem.bindir`. +* `user_agent` (`BUNDLE_USER_AGENT`): + The custom user agent fragment Bundler includes in API requests. +* `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`): + Sets the `--key` paramter for `gem push` when using the `rake release` + command with a private gemstash server. In general, you should set these settings per-application by using the applicable flag to the [bundle install(1)][bundle-install] or [bundle package(1)][bundle-package] command. @@ -204,10 +271,23 @@ mirror to fetch gems. bundle config mirror.SOURCE_URL MIRROR_URL -For example, to use a mirror of rubygems.org hosted at +For example, to use a mirror of rubygems.org hosted at rubygems-mirror.org: bundle config mirror.http://rubygems.org http://rubygems-mirror.org +Each mirror also provides a fallback timeout setting. If the mirror does not +respond within the fallback timeout, Bundler will try to use the original +server instead of the mirror. + + bundle config mirror.SOURCE_URL.fallback_timeout TIMEOUT + +For example, to fall back to rubygems.org after 3 seconds: + + bundle config mirror.https://rubygems.org.fallback_timeout 3 + +The default fallback timeout is 0.1 seconds, but the setting can currently +only accept whole seconds (for example, 1, 15, or 30). + ## CREDENTIALS FOR GEM SOURCES Bundler allows you to configure credentials for any gem source, which allows @@ -223,3 +303,16 @@ For example, to save the credentials of user `claudette` for the gem source at Or you can set the credentials as an environment variable like this: export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" + +For gems with a git source with HTTP(S) URL you can specify credentials like so: + + bundle config https://github.com/bundler/bundler.git username:password + +Or you can set the credentials as an environment variable like so: + + export BUNDLE_GITHUB__COM=username:password + +This is especially useful for private repositories on hosts such as Github, +where you can use personal OAuth tokens: + + export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x-oauth-basic diff --git a/man/bundle-exec.ronn b/man/bundle-exec.ronn index ba6844c5c2..c9ab2309e4 100644 --- a/man/bundle-exec.ronn +++ b/man/bundle-exec.ronn @@ -63,6 +63,15 @@ It also modifies Rubygems: making system executables work * Add all gems in the bundle into Gem.loaded_specs +### Loading + +By default, when attempting to `bundle exec` to a file with a ruby shebang, +Bundler will `Kernel.load` that file instead of using `Kernel.exec`. For the +vast majority of cases, this is a performance improvement. In a rare few cases, +this could cause some subtle side-effects (such as dependence on the exact +contents of `$0` or `__FILE__`) and the optimization can be disabled by enabling +the `disable_exec_load` setting. + ### Shelling out Any Ruby code that opens a subshell (like `system`, backticks, or `%x{}`) will diff --git a/man/bundle-info.ronn b/man/bundle-info.ronn new file mode 100644 index 0000000000..47e457aa3c --- /dev/null +++ b/man/bundle-info.ronn @@ -0,0 +1,17 @@ +bundle-info(1) -- Show information for the given gem in your bundle +========================================================================= + +## SYNOPSIS + +`bundle info` [GEM] + [--path] + +## DESCRIPTION + +Print the basic information about the provided GEM such as homepage, version, +path and summary. + +## OPTIONS + +* `--path`: +Print the path of the given gem diff --git a/man/bundle-init.ronn b/man/bundle-init.ronn new file mode 100644 index 0000000000..e5dfd420e9 --- /dev/null +++ b/man/bundle-init.ronn @@ -0,0 +1,18 @@ +bundle-init(1) -- Generates a Gemfile into the current working directory +======================================================================== + +## SYNOPSIS + +`bundle init` [--gemspec=FILE] + +## DESCRIPTION + +Init generates a default `Gemfile(5)` in the current working directory. When +adding a `Gemfile(5)` to a gem with a gemspec, the `--gemspec` option will +automatically add each dependency listed in the gemspec file to the newly +created `Gemfile(5)`. + +## OPTIONS + +* `--gemspec`: + Use the specified .gemspec to create the `Gemfile(5)` diff --git a/man/bundle-inject.ronn b/man/bundle-inject.ronn new file mode 100644 index 0000000000..02f5dd8bd2 --- /dev/null +++ b/man/bundle-inject.ronn @@ -0,0 +1,22 @@ +bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile +========================================================================= + +## SYNOPSIS + +`bundle inject` [GEM] [VERSION] + +## DESCRIPTION + +Adds the named gem(s) with their version requirements to the resolved +`Gemfile(5)`. + +This command will add the gem to both your `Gemfile(5)` and Gemfile.lock if it +isn't listed yet. + +Example: + + bundle install + bundle inject 'rack' '> 0' + +This will inject the 'rack' gem with a version greater than 0 in your +`Gemfile(5)` and Gemfile.lock diff --git a/man/bundle-install.ronn b/man/bundle-install.ronn index 92d1d8d42c..3a03090512 100644 --- a/man/bundle-install.ronn +++ b/man/bundle-install.ronn @@ -110,7 +110,7 @@ time `bundle install` is run, use `bundle config` (see bundle-config(1)). 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 + `--path ...` setting will show up by calling `gem list`. Accordingly, gems installed to other locations will not get listed. * `--quiet`: diff --git a/man/bundle-lock.ronn b/man/bundle-lock.ronn index 8afc50af8a..31ecd3c877 100644 --- a/man/bundle-lock.ronn +++ b/man/bundle-lock.ronn @@ -7,6 +7,14 @@ bundle-lock(1) -- Creates / Updates a lockfile without installing [--local] [--print] [--lockfile=PATH] + [--full-index] + [--add-platform] + [--remove-platform] + [--patch] + [--minor] + [--major] + [--strict] + [--conservative] ## DESCRIPTION @@ -30,6 +38,31 @@ Lock the gems specified in Gemfile. * `--lockfile=<path>`: The path where the lockfile should be written to. +* `--full-index`: + Fall back to using the single-file index of all gems. + +* `--add-platform`: + Add a new platform to the lockfile, re-resolving for the addition of that + platform. + +* `--remove-platform`: + Remove a platform from the lockfile. + +* `--patch`: + If updating, prefer updating only to next patch version. + +* `--minor`: + If updating, prefer updating only to next minor version. + +* `--major`: + If updating, prefer updating to next major version (default). + +* `--strict`: + If updating, do not allow any gem to be updated past latest --patch | --minor | --major. + +* `--conservative`: + If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated. + ## UPDATING ALL GEMS If you run `bundle lock` with `--update` option without list of gems, bundler will @@ -45,3 +78,17 @@ For instance, you only want to update `nokogiri`, run `bundle lock --update noko Bundler will update `nokogiri` and any of its dependencies, but leave the rest of the gems that you specified locked to the versions in the `Gemfile.lock`. + +## SUPPORTING OTHER PLATFORMS + +If you want your bundle to support platforms other than the one you're running +locally, you can run `bundle lock --add-platform PLATFORM` to add PLATFORM to +the lockfile, force bundler to re-resolve and consider the new platform when +picking gems, all without needing to have a machine that matches PLATFORM handy +to install those platform-specific gems on. + +For a full explanation of gem platforms, see `gem help platform`. + +## PATCH LEVEL OPTIONS + +See [bundle update(1)][bundle-update] for details. diff --git a/man/bundle-open.ronn b/man/bundle-open.ronn new file mode 100644 index 0000000000..497beac93f --- /dev/null +++ b/man/bundle-open.ronn @@ -0,0 +1,19 @@ +bundle-open(1) -- Opens the source directory for a gem in your bundle +===================================================================== + +## SYNOPSIS + +`bundle open` [GEM] + +## DESCRIPTION + +Opens the source directory of the provided GEM in your editor. + +For this to work the `EDITOR` or `BUNDLER_EDITOR` environment variable has to +be set. + +Example: + + bundle open 'rack' + +Will open the source directory for the 'rack' gem in your bundle. diff --git a/man/bundle-outdated.ronn b/man/bundle-outdated.ronn new file mode 100644 index 0000000000..47792e61b2 --- /dev/null +++ b/man/bundle-outdated.ronn @@ -0,0 +1,107 @@ +bundle-outdated(1) -- List installed gems with newer versions available +======================================================================= + +## SYNOPSIS + +`bundle outdated` [GEM] [--local] + [--pre] + [--source] + [--strict] + [--parseable | --porcelain] + [--group=GROUP] + [--groups] + [--update-strict] + [--patch|--minor|--major] + [--filter-major] + [--filter-minor] + [--filter-patch] + +## DESCRIPTION + +Outdated lists the names and versions of gems that have a newer version available +in the given source. Calling outdated with [GEM [GEM]] will only check for newer +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. + +## OPTIONS + +* `--local`: + Do not attempt to fetch gems remotely and use the gem cache instead. + +* `--pre`: + Check for newer pre-release gems. + +* `--source`: + Check against a specific source. + +* `--strict`: + Only list newer versions allowed by your Gemfile requirements. + +* `--parseable`: + Use minimal formatting for more parseable output. + +* `--group`: + List gems from a specific group. + +* `--groups`: + List gems organized by groups. + +* `--update-strict`: + Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor| --major. + +* `--minor`: + Prefer updating only to next minor version. + +* `--major`: + Prefer updating to next major version (default). + +* `--patch`: + Prefer updating only to next patch version. + +* `--filter-major`: + Only list major newer versions. + +* `--filter-minor`: + Only list minor newer versions. + +* `--filter-patch`: + Only list patch newer versions. + +## PATCH LEVEL OPTIONS + +See [bundle update(1)][bundle-update] for details. + +One difference between the patch level options in `bundle update` and here is the `--strict` option. +`--strict` was already an option on outdated before the patch level options were added. `--strict` +wasn't altered, and the `--update-strict` option on `outdated` reflects what `--strict` does on +`bundle update`. + +## FILTERING OUTPUT + +The 3 filtering options do not affect the resolution of versions, merely what versions are shown +in the output. + +If the regular output shows the following: + + * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default" + * headless (newest 2.3.1, installed 2.2.3) in groups "test" + +`--filter-major` would only show: + + * hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default" + +`--filter-minor` would only show: + + * headless (newest 2.3.1, installed 2.2.3) in groups "test" + +`--filter-patch` would only show: + + * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + +Filter options can be combined. `--filter-minor` and `--filter-patch` would show: + + * faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test" + * headless (newest 2.3.1, installed 2.2.3) in groups "test" + +Combining all three `filter` options would be the same result as providing none of them. diff --git a/man/bundle-show.ronn b/man/bundle-show.ronn new file mode 100644 index 0000000000..714b6067aa --- /dev/null +++ b/man/bundle-show.ronn @@ -0,0 +1,20 @@ +bundle-show(1) -- Shows all the gems in your bundle, or the path to a gem +========================================================================= + +## SYNOPSIS + +`bundle show` [GEM] + [--paths] + +## DESCRIPTION + +Without the [GEM] option, `show` will print a list of the names and versions of +all gems that are required by your `Gemfile(5)`. + +Calling show with [GEM] will list the exact location of that gem on your +machine. + +## OPTIONS + +* `--paths`: + List the paths of all gems that are required by your `Gemfile(5)`. diff --git a/man/bundle-update.ronn b/man/bundle-update.ronn index 47d70f661c..7986a17bdc 100644 --- a/man/bundle-update.ronn +++ b/man/bundle-update.ronn @@ -3,7 +3,18 @@ bundle-update(1) -- Update your gems to the latest available versions ## SYNOPSIS -`bundle update` <*gems> [--group=NAME] [--source=NAME] [--local] [--ruby] +`bundle update` <*gems> [--group=NAME] + [--source=NAME] + [--local] + [--ruby] + [--bundler[=VERSION]] + [--full-index] + [--jobs=JOBS] + [--quiet] + [--force] + [--patch|--minor|--major] + [--strict] + [--conservative] ## DESCRIPTION @@ -37,6 +48,33 @@ gem. * `--bundler`: Update the locked version of bundler to the invoked bundler version. +* `--full-index`: + Fall back to using the single-file index of all gems. + +* `--jobs`: + Specify the number of jobs to run in parallel. + +* `--quiet`: + Only output warnings and errors. + +* `--force`: + Force downloading every gem. + +* `--patch`: + Prefer updating only to next patch version. + +* `--minor`: + Prefer updating only to next minor version. + +* `--major`: + Prefer updating to next major version (default). + +* `--strict`: + Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`. + +* `--conservative`: + Use bundle install conservative update behavior and do not allow shared dependencies to be updated. + ## UPDATING ALL GEMS If you run `bundle update` with no parameters, bundler will ignore @@ -146,14 +184,125 @@ In this case, the two gems have their own set of dependencies, but they share `bundle update thin` will update `rack` even though it's _also_ a dependency of `rack-perftools_profiler`. -`In short`, when you update a gem using `bundle update`, bundler will update all -dependencies of that gem, including those that are also dependencies of another gem. +In short, by default, when you update a gem using `bundle update`, bundler will +update all dependencies of that gem, including those that are also dependencies +of another gem. + +To prevent updating shared dependencies, prior to version 1.14 the only option +was the `CONSERVATIVE UPDATING` behavior in [bundle install(1)][bundle-install]: In this scenario, updating the `thin` version manually in the Gemfile(5), and then running [bundle install(1)][bundle-install] will only update `daemons` and `eventmachine`, but not `rack`. For more information, see the `CONSERVATIVE UPDATING` section of [bundle install(1)][bundle-install]. +Starting with 1.14, specifying the `--conservative` option will also prevent shared +dependencies from being updated. + +## PATCH LEVEL OPTIONS + +Version 1.14 introduced 4 patch-level options that will influence how gem +versions are resolved. One of the following options can be used: `--patch`, +`--minor` or `--major`. `--strict` can be added to further influence resolution. + +* `--patch`: + Prefer updating only to next patch version. + +* `--minor`: + Prefer updating only to next minor version. + +* `--major`: + Prefer updating to next major version (default). + +* `--strict`: + Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`. + +When Bundler is resolving what versions to use to satisfy declared +requirements in the Gemfile or in parent gems, it looks up all +available versions, filters out any versions that don't satisfy +the requirement, and then, by default, sorts them from newest to +oldest, considering them in that order. + +Providing one of the patch level options (e.g. `--patch`) changes the +sort order of the satisfying versions, causing Bundler to consider the +latest `--patch` or `--minor` version available before other versions. +Note that versions outside the stated patch level could still be +resolved to if necessary to find a suitable dependency graph. + +For example, if gem 'foo' is locked at 1.0.2, with no gem requirement +defined in the Gemfile, and versions 1.0.3, 1.0.4, 1.1.0, 1.1.1, 2.0.0 +all exist, the default order of preference by default (`--major`) will +be "2.0.0, 1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2". + +If the `--patch` option is used, the order of preference will change to +"1.0.4, 1.0.3, 1.0.2, 1.1.1, 1.1.0, 2.0.0". + +If the `--minor` option is used, the order of preference will change to +"1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2, 2.0.0". + +Combining the `--strict` option with any of the patch level options +will remove any versions beyond the scope of the patch level option, +to ensure that no gem is updated that far. + +To continue the previous example, if both `--patch` and `--strict` +options are used, the available versions for resolution would be +"1.0.4, 1.0.3, 1.0.2". If `--minor` and `--strict` are used, it would +be "1.1.1, 1.1.0, 1.0.4, 1.0.3, 1.0.2". + +Gem requirements as defined in the Gemfile will still be the first +determining factor for what versions are available. If the gem +requirement for `foo` in the Gemfile is '~> 1.0', that will accomplish +the same thing as providing the `--minor` and `--strict` options. + +## PATCH LEVEL EXAMPLES + +Given the following gem specifications: + + foo 1.4.3, requires: ~> bar 2.0 + foo 1.4.4, requires: ~> bar 2.0 + foo 1.4.5, requires: ~> bar 2.1 + foo 1.5.0, requires: ~> bar 2.1 + foo 1.5.1, requires: ~> bar 3.0 + bar with versions 2.0.3, 2.0.4, 2.1.0, 2.1.1, 3.0.0 + +Gemfile: + + gem 'foo' + +Gemfile.lock: + + foo (1.4.3) + bar (~> 2.0) + bar (2.0.3) + +Cases: + + # Command Line Result + ------------------------------------------------------------ + 1 bundle update --patch 'foo 1.4.5', 'bar 2.1.1' + 2 bundle update --patch foo 'foo 1.4.5', 'bar 2.1.1' + 3 bundle update --minor 'foo 1.5.1', 'bar 3.0.0' + 4 bundle update --minor --strict 'foo 1.5.0', 'bar 2.1.1' + 5 bundle update --patch --strict 'foo 1.4.4', 'bar 2.0.4' + +In case 1, bar is upgraded to 2.1.1, a minor version increase, because +the dependency from foo 1.4.5 required it. + +In case 2, only foo is requested to be unlocked, but bar is also +allowed to move because it's not a declared dependency in the Gemfile. + +In case 3, bar goes up a whole major release, because a minor increase +is preferred now for foo, and when it goes to 1.5.1, it requires 3.0.0 +of bar. + +In case 4, foo is preferred up to a minor version, but 1.5.1 won't work +because the --strict flag removes bar 3.0.0 from consideration since +it's a major increment. + +In case 5, both foo and bar have any minor or major increments removed +from consideration because of the --strict flag, so the most they can +move is up to 1.4.4 and 2.0.4. + ## RECOMMENDED WORKFLOW In general, when working with an application managed with bundler, you should diff --git a/man/bundle-viz.ronn b/man/bundle-viz.ronn new file mode 100644 index 0000000000..865bbf6ee3 --- /dev/null +++ b/man/bundle-viz.ronn @@ -0,0 +1,30 @@ +bundle-viz(1) -- Generates a visual dependency graph for your Gemfile +===================================================================== + +## SYNOPSIS + +`bundle viz` [--file=FILE] + [--format=FORMAT] + [--requirements] + [--version] + [--without=GROUP GROUP] + +## DESCRIPTION + +`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph. +`viz` requires the ruby-graphviz gem (and its dependencies). + +The associated gems must also be installed via `bundle install(1)`. + +## OPTIONS + +* `--file`: + The name to use for the generated file. See `--format` option +* `--format`: + This is output format option. Supported format is png, jpg, svg, dot ... +* `--requirements`: + Set to show the version of each required dependency. +* `--version`: + Set to show each gem version. +* `--without`: + Exclude gems that are part of the specified named group. diff --git a/man/bundle.ronn b/man/bundle.ronn index 72faac048b..59fed42304 100644 --- a/man/bundle.ronn +++ b/man/bundle.ronn @@ -27,20 +27,20 @@ We divide `bundle` subcommands into primary commands and utilities. ## PRIMARY COMMANDS -* [bundle install(1)][bundle-install]: +* [`bundle install(1)`][bundle-install]: Install the gems specified by the `Gemfile` or `Gemfile.lock` -* [bundle update(1)][bundle-update]: +* [`bundle update(1)`][bundle-update]: Update dependencies to their latest versions -* [bundle package(1)][bundle-package]: +* [`bundle package(1)`][bundle-package]: Package the .gem files required by your application into the `vendor/cache` directory -* [bundle exec(1)][bundle-exec]: +* [`bundle exec(1)`][bundle-exec]: Execute a script in the context of the current bundle -* [bundle config(1)][bundle-config]: +* [`bundle config(1)`][bundle-config]: Specify and read configuration options for bundler * `bundle help(1)`: @@ -55,7 +55,7 @@ We divide `bundle` subcommands into primary commands and utilities. * `bundle show(1)`: Show the source location of a particular gem in the bundle -* `bundle outdated(1)`: +* [`bundle outdated(1)`][bundle-outdated]: Show all of the outdated gems in the current bundle * `bundle console(1)`: @@ -64,7 +64,7 @@ We divide `bundle` subcommands into primary commands and utilities. * `bundle open(1)`: Open an installed gem in the editor -* `bundle lock(1)`: +* [`bundle lock(1)`][bundle-lock]: Generate a lockfile for your dependencies * `bundle viz(1)`: @@ -73,10 +73,10 @@ We divide `bundle` subcommands into primary commands and utilities. * `bundle init(1)`: Generate a simple `Gemfile`, placed in the current directory -* [bundle gem(1)][bundle-gem]: +* [`bundle gem(1)`][bundle-gem]: Create a simple gem, suitable for development with bundler -* [bundle platform(1)][bundle-platform]: +* [`bundle platform(1)`][bundle-platform]: Display platform compatibility information * `bundle clean(1)`: @@ -91,6 +91,24 @@ 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. +## BUNDLER TRAMPOLINING + +Bundler includes a feature called trampolining, designed to allow a single +developer to work on multiple projects, each on different Bundler versions. +The trampoline will infer the correct version of Bundler to use for each project +and load that version instead of the version directly invoked (which is almost +always the newest version installed locally). + +Bundler by default will use the Bundler version in the current directory to +determine the version to trampoline to, reading from the `BUNDLED WITH` section. +However, if the `BUNDLER_VERSION` environment variable is set, that version will +override the lockfile inference and can be used in directories without a +lockfile. + +Until the target version is Bundler 2 or later, `BUNDLE_TRAMPOLINE_FORCE` must +be set for the trampoline to be used. Additionally, `BUNDLE_TRAMPOLINE_DISABLE` can +be set to completely disable the trampoline. + ## OBSOLETE These commands are obsolete and should no longer be used diff --git a/man/gemfile.5.ronn b/man/gemfile.5.ronn index da2157e971..dead032ced 100644 --- a/man/gemfile.5.ronn +++ b/man/gemfile.5.ronn @@ -30,7 +30,7 @@ Sources are checked for gems following the heuristics described in will print a warning after installing the gem indicating which source was used, and listing the other sources where the gem is available. A specific source can be selected for gems that need to use a non-standard repository, suppressing -this warning, by using the [`:source` option](#SOURCE-source-) or a +this warning, by using the [`:source` option](#SOURCE) or a [`source` block](#BLOCK-FORM-OF-SOURCE-GIT-PATH-GROUP-and-PLATFORMS). ### CREDENTIALS @@ -173,62 +173,38 @@ There are a number of `Gemfile` platforms: * `ruby`: C Ruby (MRI) or Rubinius, but `NOT` Windows - * `ruby_18`: - _ruby_ `AND` version 1.8 - * `ruby_19`: - _ruby_ `AND` version 1.9 - * `ruby_20`: - _ruby_ `AND` version 2.0 - * `ruby_21`: - _ruby_ `AND` version 2.1 - * `ruby_22`: - _ruby_ `AND` version 2.2 - * `ruby_23`: - _ruby_ `AND` version 2.3 * `mri`: Same as _ruby_, but not Rubinius - * `mri_18`: - _mri_ `AND` version 1.8 - * `mri_19`: - _mri_ `AND` version 1.9 - * `mri_20`: - _mri_ `AND` version 2.0 - * `mri_21`: - _mri_ `AND` version 2.1 - * `mri_22`: - _mri_ `AND` version 2.2 - * `mri_23`: - _mri_ `AND` version 2.3 + * `mingw`: + Windows 32 bit 'mingw32' platform (aka RubyInstaller) + * `x64_mingw`: + Windows 64 bit 'mingw32' platform (aka RubyInstaller x64) * `rbx`: Same as _ruby_, but only Rubinius (not MRI) * `jruby`: JRuby * `mswin`: Windows + +You can restrict further by platform and version for all platforms *except* for +`rbx`, `jruby`, and `mswin`. + +To specify a version in addition to a platform, append the version number without +the delimiter to the platform. For example, to specify that a gem should only be +used on platforms with Ruby 2.3, use: + + ruby_23 + +The full list of platforms and supported versions includes: + + * `ruby`: + 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 + * `mri`: + 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 * `mingw`: - Windows 32 bit 'mingw32' platform (aka RubyInstaller) - * `mingw_18`: - _mingw_ `AND` version 1.8 - * `mingw_19`: - _mingw_ `AND` version 1.9 - * `mingw_20`: - _mingw_ `AND` version 2.0 - * `mingw_21`: - _mingw_ `AND` version 2.1 - * `mingw_22`: - _mingw_ `AND` version 2.2 - * `mingw_23`: - _mingw_ `AND` version 2.3 + 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 * `x64_mingw`: - Windows 64 bit 'mingw32' platform (aka RubyInstaller x64) - * `x64_mingw_20`: - _x64_mingw_ `AND` version 2.0 - * `x64_mingw_21`: - _x64_mingw_ `AND` version 2.1 - * `x64_mingw_22`: - _x64_mingw_ `AND` version 2.2 - * `x64_mingw_23`: - _x64_mingw_ `AND` version 2.3 + 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 As with groups, you can specify one or more platforms: @@ -257,7 +233,7 @@ back on global sources using the ordering described in [SOURCE PRIORITY][]. Selecting a specific source repository this way also suppresses the ambiguous gem warning described above in -[GLOBAL SOURCES (#source)](#GLOBAL-SOURCES-source-). +[GLOBAL SOURCES (#source)](#GLOBAL-SOURCES). ### GIT diff --git a/man/index.txt b/man/index.txt index 9e46cebaa8..e3b38c46db 100644 --- a/man/index.txt +++ b/man/index.txt @@ -6,3 +6,10 @@ bundle-exec bundle-exec.1 bundle-config bundle-config.1 bundle-platform bundle-platform.1 bundle-gem bundle-gem.1 +bundle-clean bundle-clean.1 +bundle-check bundle-check.1 +bundle-init bundle-init.1 +bundle-inject bundle-inject.1 +bundle-open bundle-open.1 +bundle-show bundle-show.1 +bundle-viz bundle-viz.1 diff --git a/spec/bundler/bundler_spec.rb b/spec/bundler/bundler_spec.rb index 2ff9920614..268c0d99ac 100644 --- a/spec/bundler/bundler_spec.rb +++ b/spec/bundler/bundler_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" require "bundler" -describe Bundler do +RSpec.describe Bundler do describe "#load_gemspec_uncached" do let(:app_gemspec_path) { tmp("test.gemspec") } subject { Bundler.load_gemspec_uncached(app_gemspec_path) } @@ -143,12 +143,12 @@ describe Bundler do describe "configuration" do context "disable_shared_gems" do - it "should unset GEM_PATH with nil" do + it "should unset GEM_PATH with empty string" do env = {} settings = { :disable_shared_gems => true } Bundler.send(:configure_gem_path, env, settings) expect(env.keys).to include("GEM_PATH") - expect(env["GEM_PATH"]).to be_nil + expect(env["GEM_PATH"]).to eq "" end end end @@ -156,7 +156,7 @@ describe Bundler do describe "#rm_rf" do context "the directory is world writable" do let(:bundler_ui) { Bundler.ui } - it "should show a fridenly error" do + it "should raise a friendly error" do allow(File).to receive(:exist?).and_return(true) allow(FileUtils).to receive(:remove_entry_secure).and_raise(ArgumentError) allow(File).to receive(:world_writable?).and_return(true) @@ -170,4 +170,43 @@ EOF end end end + + describe "#user_home" do + context "home directory is set" do + it "should return the user home" do + path = "/home/oggy" + allow(Bundler.rubygems).to receive(:user_home).and_return(path) + allow(File).to receive(:directory?).with(path).and_return true + allow(File).to receive(:writable?).with(path).and_return true + expect(Bundler.user_home).to eq(Pathname(path)) + end + end + + context "home directory is not set" do + it "should issue warning and return a temporary user home" do + allow(Bundler.rubygems).to receive(:user_home).and_return(nil) + allow(Etc).to receive(:getlogin).and_return("USER") + allow(Dir).to receive(:tmpdir).and_return("/TMP") + allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(true) + expect(FileUtils).to receive(:mkpath).with("/TMP/bundler/home/USER") + message = <<EOF +Your home directory is not set. +Bundler will use `/TMP/bundler/home/USER' as your home directory temporarily. +EOF + expect(Bundler.ui).to receive(:warn).with(message) + expect(Bundler.user_home).to eq(Pathname("/TMP/bundler/home/USER")) + end + end + end + + describe "#tmp_home_path" do + it "should create temporary user home" do + allow(Dir).to receive(:tmpdir).and_return("/TMP") + allow(FileTest).to receive(:exist?).with("/TMP/bundler/home").and_return(false) + expect(FileUtils).to receive(:mkpath).once.ordered.with("/TMP/bundler/home") + expect(FileUtils).to receive(:mkpath).once.ordered.with("/TMP/bundler/home/USER") + expect(File).to receive(:chmod).with(0o777, "/TMP/bundler/home") + expect(Bundler.tmp_home_path("USER", "")).to eq(Pathname("/TMP/bundler/home/USER")) + end + end end diff --git a/spec/bundler/cli_spec.rb b/spec/bundler/cli_spec.rb index ac704fb41d..ec1d384851 100644 --- a/spec/bundler/cli_spec.rb +++ b/spec/bundler/cli_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/cli" -describe "bundle executable" do +RSpec.describe "bundle executable" do it "returns non-zero exit status when passed unrecognized options" do bundle "--invalid_argument" expect(exitstatus).to_not be_zero if exitstatus @@ -61,9 +61,72 @@ describe "bundle executable" do expect(out).to start_with("Running `bundle config --verbose` with bundler #{Bundler::VERSION}") end end + + describe "printing the outdated warning" do + shared_examples_for "no warning" do + it "prints no warning" do + bundle "fail" + expect(err + out).to eq("Could not find command \"fail\".") + end + end + + let(:bundler_version) { "1.1" } + let(:latest_version) { nil } + before do + simulate_bundler_version(bundler_version) + if latest_version + info_path = home(".bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/bundler") + info_path.parent.mkpath + info_path.open("w") {|f| f.write "#{latest_version}\n" } + end + end + + context "when there is no latest version" do + include_examples "no warning" + end + + context "when the latest version is equal to the current version" do + let(:latest_version) { bundler_version } + include_examples "no warning" + end + + context "when the latest version is less than the current version" do + let(:latest_version) { "0.9" } + include_examples "no warning" + end + + context "when the latest version is greater than the current version" do + let(:latest_version) { "2.0" } + it "prints the version warning" do + bundle "fail" + expect(err + out).to eq(<<-EOS.strip) +The latest bundler is #{latest_version}, but you are currently running #{bundler_version}. +To update, run `gem install bundler` +Could not find command "fail". + EOS + end + + context "and disable_version_check is set" do + before { bundle! "config disable_version_check true" } + include_examples "no warning" + end + + context "and is a pre-release" do + let(:latest_version) { "2.0.0.pre.4" } + it "prints the version warning" do + bundle "fail" + expect(err + out).to eq(<<-EOS.strip) +The latest bundler is #{latest_version}, but you are currently running #{bundler_version}. +To update, run `gem install bundler --pre` +Could not find command "fail". + EOS + end + end + end + end end -describe "bundler executable" do +RSpec.describe "bundler executable" do it "shows the bundler version just as the `bundle` executable does" do bundler "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") diff --git a/spec/bundler/compact_index_client/updater_spec.rb b/spec/bundler/compact_index_client/updater_spec.rb new file mode 100644 index 0000000000..c1cae31956 --- /dev/null +++ b/spec/bundler/compact_index_client/updater_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +require "spec_helper" +require "net/http" +require "bundler/compact_index_client" +require "bundler/compact_index_client/updater" + +RSpec.describe Bundler::CompactIndexClient::Updater do + subject(:updater) { described_class.new(fetcher) } + + let(:fetcher) { double(:fetcher) } + + context "when the ETag header is missing" do + # Regression test for https://github.com/bundler/bundler/issues/5463 + + let(:response) { double(:response, :body => "") } + let(:local_path) { Pathname("/tmp/localpath") } + let(:remote_path) { double(:remote_path) } + + it "MisMatchedChecksumError is raised" do + # Twice: #update retries on failure + expect(response).to receive(:[]).with("Content-Encoding").twice { "" } + expect(response).to receive(:[]).with("ETag").twice { nil } + expect(fetcher).to receive(:call).twice { response } + + expect do + updater.update(local_path, remote_path) + end.to raise_error(Bundler::CompactIndexClient::Updater::MisMatchedChecksumError) + end + end +end diff --git a/spec/bundler/definition_spec.rb b/spec/bundler/definition_spec.rb index c72f50f0d1..73d44a93ab 100644 --- a/spec/bundler/definition_spec.rb +++ b/spec/bundler/definition_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/definition" -describe Bundler::Definition do +RSpec.describe Bundler::Definition do describe "#lock" do before do allow(Bundler).to receive(:settings) { Bundler::Settings.new(".") } @@ -137,7 +137,7 @@ describe Bundler::Definition do describe "initialize" do context "gem version promoter" do context "with lockfile" do - before :each do + before do install_gemfile <<-G source "file://#{gem_repo1}" gem "foo" @@ -159,25 +159,119 @@ describe Bundler::Definition do end end - def mock_source_list - Class.new do - def all_sources - [] - end + context "eager unlock" do + before do + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner' - def path_sources - [] - end + gem 'shared_owner_a' + gem 'shared_owner_b' + G + + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + isolated_dep (2.0.1) + isolated_owner (1.0.1) + isolated_dep (~> 2.0) + shared_dep (5.0.1) + shared_owner_a (3.0.1) + shared_dep (~> 5.0) + shared_owner_b (4.0.1) + shared_dep (~> 5.0) + + PLATFORMS + ruby + + DEPENDENCIES + shared_owner_a + shared_owner_b + isolated_owner + + BUNDLED WITH + 1.13.0 + L + end - def rubygems_remotes - [] - end + it "should not eagerly unlock shared dependency with bundle install conservative updating behavior" do + updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"), + Bundler::Dependency.new("shared_owner_a", "3.0.2"), + Bundler::Dependency.new("shared_owner_b", ">= 0")] + unlock_hash_for_bundle_install = {} + definition = Bundler::Definition.new( + bundled_app("Gemfile.lock"), + updated_deps_in_gemfile, + Bundler::SourceList.new, + unlock_hash_for_bundle_install + ) + locked = definition.send(:converge_locked_specs).map(&:name) + expect(locked.include?("shared_dep")).to be_truthy + end - def replace_sources!(arg) - nil - end - end.new + it "should not eagerly unlock shared dependency with bundle update conservative updating behavior" do + updated_deps_in_gemfile = [Bundler::Dependency.new("isolated_owner", ">= 0"), + Bundler::Dependency.new("shared_owner_a", ">= 0"), + Bundler::Dependency.new("shared_owner_b", ">= 0")] + definition = Bundler::Definition.new( + bundled_app("Gemfile.lock"), + updated_deps_in_gemfile, + Bundler::SourceList.new, + :gems => ["shared_owner_a"], :lock_shared_dependencies => true + ) + locked = definition.send(:converge_locked_specs).map(&:name) + expect(locked).to eq %w(isolated_dep isolated_owner shared_dep shared_owner_b) + expect(locked.include?("shared_dep")).to be_truthy + end end end end + + describe "find_resolved_spec" do + it "with no platform set in SpecSet" do + ss = Bundler::SpecSet.new([build_stub_spec("a", "1.0"), build_stub_spec("b", "1.0")]) + dfn = Bundler::Definition.new(nil, [], mock_source_list, true) + dfn.instance_variable_set("@specs", ss) + found = dfn.find_resolved_spec(build_spec("a", "0.9", "ruby").first) + expect(found.name).to eq "a" + expect(found.version.to_s).to eq "1.0" + end + end + + describe "find_indexed_specs" do + it "with no platform set in indexed specs" do + index = Bundler::Index.new + %w(1.0.0 1.0.1 1.1.0).each {|v| index << build_stub_spec("foo", v) } + + dfn = Bundler::Definition.new(nil, [], mock_source_list, true) + dfn.instance_variable_set("@index", index) + found = dfn.find_indexed_specs(build_spec("foo", "0.9", "ruby").first) + expect(found.length).to eq 3 + end + end + + def build_stub_spec(name, version) + Bundler::StubSpecification.new(name, version, nil, nil) + end + + def mock_source_list + Class.new do + def all_sources + [] + end + + def path_sources + [] + end + + def rubygems_remotes + [] + end + + def replace_sources!(arg) + nil + end + end.new + end end diff --git a/spec/bundler/dsl_spec.rb b/spec/bundler/dsl_spec.rb index 00d36dd55f..0561cb7ddc 100644 --- a/spec/bundler/dsl_spec.rb +++ b/spec/bundler/dsl_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Dsl do +RSpec.describe Bundler::Dsl do before do @rubygems = double("rubygems") allow(Bundler::Source::Rubygems).to receive(:new) { @rubygems } @@ -93,8 +93,8 @@ describe Bundler::Dsl do end describe "#gem" do - [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :mri, :mri_18, :mri_19, - :mri_20, :mri_21, :mri_22, :mri_23, :jruby, :rbx].each do |platform| + [:ruby, :ruby_18, :ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24, :ruby_25, :mri, :mri_18, :mri_19, + :mri_20, :mri_21, :mri_22, :mri_23, :mri_24, :mri_25, :jruby, :rbx].each do |platform| it "allows #{platform} as a valid platform" do subject.gem("foo", :platform => platform) end diff --git a/spec/bundler/endpoint_specification_spec.rb b/spec/bundler/endpoint_specification_spec.rb index 6718b24971..0b8da840d2 100644 --- a/spec/bundler/endpoint_specification_spec.rb +++ b/spec/bundler/endpoint_specification_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::EndpointSpecification do +RSpec.describe Bundler::EndpointSpecification do let(:name) { "foo" } let(:version) { "1.0.0" } let(:platform) { Gem::Platform::RUBY } @@ -50,4 +50,17 @@ describe Bundler::EndpointSpecification do end end end + + describe "#parse_metadata" do + context "when the metadata has malformed requirements" do + let(:metadata) { { "rubygems" => ">\n" } } + it "raises a helpful error message" do + expect { subject }.to raise_error( + Bundler::GemspecError, + a_string_including("There was an error parsing the metadata for the gem foo (1.0.0)"). + and(a_string_including('The metadata was {"rubygems"=>">\n"}')) + ) + end + end + end end diff --git a/spec/bundler/env_spec.rb b/spec/bundler/env_spec.rb index 1be6cf46bc..269c323ac6 100644 --- a/spec/bundler/env_spec.rb +++ b/spec/bundler/env_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/settings" -describe Bundler::Env do +RSpec.describe Bundler::Env do let(:env) { described_class.new } let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil, nil) } @@ -49,9 +49,17 @@ describe Bundler::Env do end end + context "when there no Gemfile and print_gemfile is true" do + let(:output) { env.report(:print_gemfile => true) } + + it "prints the environment" do + expect(output).to start_with("## Environment") + end + end + context "when Gemfile contains a gemspec and print_gemspecs is true" do let(:gemspec) do - <<-GEMSPEC.gsub(/^\s+/, "") + strip_whitespace(<<-GEMSPEC) Gem::Specification.new do |gem| gem.name = "foo" gem.author = "Fumofu" @@ -68,7 +76,7 @@ describe Bundler::Env do end it "prints the gemspec" do - output = env.report(:print_gemspecs => true).gsub(/^\s+/, "") + output = env.report(:print_gemspecs => true) expect(output).to include("foo.gemspec") expect(output).to include(gemspec) diff --git a/spec/bundler/environment_preserver_spec.rb b/spec/bundler/environment_preserver_spec.rb index 496646d654..41d2650055 100644 --- a/spec/bundler/environment_preserver_spec.rb +++ b/spec/bundler/environment_preserver_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::EnvironmentPreserver do +RSpec.describe Bundler::EnvironmentPreserver do let(:preserver) { described_class.new(env, ["foo"]) } describe "#backup" do diff --git a/spec/bundler/fetcher/base_spec.rb b/spec/bundler/fetcher/base_spec.rb index bd1c03660c..38b69429bc 100644 --- a/spec/bundler/fetcher/base_spec.rb +++ b/spec/bundler/fetcher/base_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Fetcher::Base do +RSpec.describe Bundler::Fetcher::Base do let(:downloader) { double(:downloader) } let(:remote) { double(:remote) } let(:display_uri) { "http://sample_uri.com" } diff --git a/spec/bundler/fetcher/compact_index_spec.rb b/spec/bundler/fetcher/compact_index_spec.rb index f6c6ba2ee1..e653c1ea43 100644 --- a/spec/bundler/fetcher/compact_index_spec.rb +++ b/spec/bundler/fetcher/compact_index_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Fetcher::CompactIndex do +RSpec.describe Bundler::Fetcher::CompactIndex do let(:downloader) { double(:downloader) } - let(:remote) { double(:remote, :cache_slug => "lsjdf") } let(:display_uri) { URI("http://sampleuri.com") } + let(:remote) { double(:remote, :cache_slug => "lsjdf", :uri => display_uri) } let(:compact_index) { described_class.new(downloader, remote, display_uri) } before do @@ -15,7 +15,7 @@ describe Bundler::Fetcher::CompactIndex do it "has only one thread open at the end of the run" do compact_index.specs_for_names(["lskdjf"]) - thread_count = Thread.list.select {|thread| thread.status == "run" }.count + thread_count = Thread.list.count {|thread| thread.status == "run" } expect(thread_count).to eq 1 end @@ -25,6 +25,46 @@ describe Bundler::Fetcher::CompactIndex do compact_index.specs_for_names(["lskdjf"]) end + describe "#available?" do + before do + allow(compact_index).to receive(:compact_index_client). + and_return(double(:compact_index_client, :update_and_parse_checksums! => true)) + end + + it "returns true" do + expect(compact_index).to be_available + end + + context "when OpenSSL is not available" do + before do + allow(compact_index).to receive(:require).with("openssl").and_raise(LoadError) + end + + it "returns true" do + expect(compact_index).to be_available + end + end + + context "when OpenSSL is FIPS-enabled", :ruby => ">= 2.0.0" do + before { stub_const("OpenSSL::OPENSSL_FIPS", true) } + + context "when FIPS-mode is active" do + before do + allow(OpenSSL::Digest::MD5).to receive(:digest). + and_raise(OpenSSL::Digest::DigestError) + end + + it "returns false" do + expect(compact_index).to_not be_available + end + end + + it "returns true" do + expect(compact_index).to be_available + end + end + end + context "logging" do before { allow(compact_index).to receive(:log_specs).and_call_original } diff --git a/spec/bundler/fetcher/dependency_spec.rb b/spec/bundler/fetcher/dependency_spec.rb index bf7749d07a..134ca1bc57 100644 --- a/spec/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/fetcher/dependency_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Fetcher::Dependency do +RSpec.describe Bundler::Fetcher::Dependency do let(:downloader) { double(:downloader) } let(:remote) { double(:remote, :uri => URI("http://localhost:5000")) } let(:display_uri) { "http://sample_uri.com" } @@ -262,13 +262,13 @@ describe Bundler::Fetcher::Dependency do let(:uri) { URI("http://gem-api.com") } context "with gem names" do - let(:gem_names) { [%w(foo bar), %w(bundler rubocop)] } + let(:gem_names) { %w(foo bar bundler rubocop) } before { allow(subject).to receive(:fetch_uri).and_return(uri) } it "should return an api calling uri with the gems in the query" do expect(subject.dependency_api_uri(gem_names).to_s).to eq( - "http://gem-api.com/api/v1/dependencies?gems=foo%2Cbar%2Cbundler%2Crubocop" + "http://gem-api.com/api/v1/dependencies?gems=bar%2Cbundler%2Cfoo%2Crubocop" ) end end diff --git a/spec/bundler/fetcher/downloader_spec.rb b/spec/bundler/fetcher/downloader_spec.rb index 8371bcfa6c..8b26b8e415 100644 --- a/spec/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/fetcher/downloader_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Fetcher::Downloader do +RSpec.describe Bundler::Fetcher::Downloader do let(:connection) { double(:connection) } let(:redirect_limit) { 5 } let(:uri) { URI("http://www.uri-to-fetch.com/api/v2/endpoint") } diff --git a/spec/bundler/fetcher/index_spec.rb b/spec/bundler/fetcher/index_spec.rb index f81a655c24..b17e0d1727 100644 --- a/spec/bundler/fetcher/index_spec.rb +++ b/spec/bundler/fetcher/index_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Fetcher::Index do +RSpec.describe Bundler::Fetcher::Index do let(:downloader) { nil } let(:remote) { nil } let(:display_uri) { "http://sample_uri.com" } diff --git a/spec/bundler/fetcher_spec.rb b/spec/bundler/fetcher_spec.rb index 015eb81fd9..5244fc2b18 100644 --- a/spec/bundler/fetcher_spec.rb +++ b/spec/bundler/fetcher_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/fetcher" -describe Bundler::Fetcher do +RSpec.describe Bundler::Fetcher do let(:uri) { URI("https://example.com") } let(:remote) { double("remote", :uri => uri, :original_uri => nil) } diff --git a/spec/bundler/friendly_errors_spec.rb b/spec/bundler/friendly_errors_spec.rb index 1d88403c0e..19799d5495 100644 --- a/spec/bundler/friendly_errors_spec.rb +++ b/spec/bundler/friendly_errors_spec.rb @@ -4,7 +4,7 @@ require "bundler" require "bundler/friendly_errors" require "cgi" -describe Bundler, "friendly errors" do +RSpec.describe Bundler, "friendly errors" do context "with invalid YAML in .gemrc" do before do File.open(Gem.configuration.config_file_name, "w") do |f| @@ -44,14 +44,194 @@ describe Bundler, "friendly errors" do end end - it "rescues Thor::AmbiguousTaskError and raises SystemExit" do + it "calls log_error in case of exception" do + exception = Exception.new + expect(Bundler::FriendlyErrors).to receive(:exit_status).with(exception).and_return(1) expect do Bundler.with_friendly_errors do - raise Thor::AmbiguousTaskError.new("") + raise exception end end.to raise_error(SystemExit) end + it "calls exit_status on exception" do + exception = Exception.new + expect(Bundler::FriendlyErrors).to receive(:log_error).with(exception) + expect do + Bundler.with_friendly_errors do + raise exception + end + end.to raise_error(SystemExit) + end + + describe "#log_error" do + shared_examples "Bundler.ui receive error" do |error, message| + it "" do + expect(Bundler.ui).to receive(:error).with(message || error.message) + Bundler::FriendlyErrors.log_error(error) + end + end + + shared_examples "Bundler.ui receive trace" do |error| + it "" do + expect(Bundler.ui).to receive(:trace).with(error) + Bundler::FriendlyErrors.log_error(error) + end + end + + context "YamlSyntaxError" do + it_behaves_like "Bundler.ui receive error", Bundler::YamlSyntaxError.new(StandardError.new, "sample_message") + + it "Bundler.ui receive trace" do + std_error = StandardError.new + exception = Bundler::YamlSyntaxError.new(std_error, "sample_message") + expect(Bundler.ui).to receive(:trace).with(std_error) + Bundler::FriendlyErrors.log_error(exception) + end + end + + context "Dsl::DSLError, GemspecError" do + it_behaves_like "Bundler.ui receive error", Bundler::Dsl::DSLError.new("description", "dsl_path", "backtrace") + it_behaves_like "Bundler.ui receive error", Bundler::GemspecError.new + end + + context "GemRequireError" do + let(:orig_error) { StandardError.new } + let(:error) { Bundler::GemRequireError.new(orig_error, "sample_message") } + + before do + allow(orig_error).to receive(:backtrace).and_return([]) + end + + it "Bundler.ui receive error" do + expect(Bundler.ui).to receive(:error).with(error.message) + Bundler::FriendlyErrors.log_error(error) + end + + it "writes to Bundler.ui.trace" do + expect(Bundler.ui).to receive(:trace).with(orig_error, nil, true) + Bundler::FriendlyErrors.log_error(error) + end + end + + context "BundlerError" do + it "Bundler.ui receive error" do + error = Bundler::BundlerError.new + expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true) + Bundler::FriendlyErrors.log_error(error) + end + it_behaves_like "Bundler.ui receive trace", Bundler::BundlerError.new + end + + context "Thor::Error" do + it_behaves_like "Bundler.ui receive error", Bundler::Thor::Error.new + end + + context "LoadError" do + let(:error) { LoadError.new("cannot load such file -- openssl") } + + it "Bundler.ui receive error" do + expect(Bundler.ui).to receive(:error).with("\nCould not load OpenSSL.") + Bundler::FriendlyErrors.log_error(error) + end + + it "Bundler.ui receive warn" do + expect(Bundler.ui).to receive(:warn).with(any_args, :wrap => true) + Bundler::FriendlyErrors.log_error(error) + end + + it "Bundler.ui receive trace" do + expect(Bundler.ui).to receive(:trace).with(error) + Bundler::FriendlyErrors.log_error(error) + end + end + + context "Interrupt" do + it "Bundler.ui receive error" do + expect(Bundler.ui).to receive(:error).with("\nQuitting...") + Bundler::FriendlyErrors.log_error(Interrupt.new) + end + it_behaves_like "Bundler.ui receive trace", Interrupt.new + end + + context "Gem::InvalidSpecificationException" do + it "Bundler.ui receive error" do + error = Gem::InvalidSpecificationException.new + expect(Bundler.ui).to receive(:error).with(error.message, :wrap => true) + Bundler::FriendlyErrors.log_error(error) + end + end + + context "SystemExit" do + # Does nothing + end + + context "Java::JavaLang::OutOfMemoryError" do + module Java + module JavaLang + class OutOfMemoryError < StandardError; end + end + end + + it "Bundler.ui receive error" do + error = Java::JavaLang::OutOfMemoryError.new + expect(Bundler.ui).to receive(:error).with(/JVM has run out of memory/) + Bundler::FriendlyErrors.log_error(error) + end + end + + context "unexpected error" do + it "calls request_issue_report_for with error" do + error = StandardError.new + expect(Bundler::FriendlyErrors).to receive(:request_issue_report_for).with(error) + Bundler::FriendlyErrors.log_error(error) + end + end + end + + describe "#exit_status" do + it "calls status_code for BundlerError" do + error = Bundler::BundlerError.new + expect(error).to receive(:status_code).and_return("sample_status_code") + expect(Bundler::FriendlyErrors.exit_status(error)).to eq("sample_status_code") + end + + it "returns 15 for Thor::Error" do + error = Bundler::Thor::Error.new + expect(Bundler::FriendlyErrors.exit_status(error)).to eq(15) + end + + it "calls status for SystemExit" do + error = SystemExit.new + expect(error).to receive(:status).and_return("sample_status") + expect(Bundler::FriendlyErrors.exit_status(error)).to eq("sample_status") + end + + it "returns 1 in other cases" do + error = StandardError.new + expect(Bundler::FriendlyErrors.exit_status(error)).to eq(1) + end + end + + describe "#request_issue_report_for" do + it "calls relevant methods for Bundler.ui" do + expect(Bundler.ui).to receive(:info) + expect(Bundler.ui).to receive(:error) + expect(Bundler.ui).to receive(:warn) + Bundler::FriendlyErrors.request_issue_report_for(StandardError.new) + end + + it "includes error class, message and backlog" do + error = StandardError.new + allow(Bundler::FriendlyErrors).to receive(:issues_url).and_return("") + + expect(error).to receive(:class).at_least(:once) + expect(error).to receive(:message).at_least(:once) + expect(error).to receive(:backtrace).at_least(:once) + Bundler::FriendlyErrors.request_issue_report_for(error) + end + end + describe "#issues_url" do it "generates a search URL for the exception message" do exception = Exception.new("Exception message") diff --git a/spec/bundler/gem_helper_spec.rb b/spec/bundler/gem_helper_spec.rb index 4e1af32c11..498ed89447 100644 --- a/spec/bundler/gem_helper_spec.rb +++ b/spec/bundler/gem_helper_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" require "rake" require "bundler/gem_helper" -describe Bundler::GemHelper do +RSpec.describe Bundler::GemHelper do let(:app_name) { "lorem__ipsum" } let(:app_path) { bundled_app app_name } let(:app_gemspec_path) { app_path.join("#{app_name}.gemspec") } @@ -160,9 +160,10 @@ describe Bundler::GemHelper do it "gem is installed" do mock_build_message app_name, app_version mock_confirm_message "#{app_name} (#{app_version}) installed." - subject.install_gem + subject.install_gem(nil, :local) expect(app_gem_path).to exist - expect(`gem list`).to include("#{app_name} (#{app_version})") + gem_command! :list + expect(out).to include("#{app_name} (#{app_version})") end end diff --git a/spec/bundler/gem_version_promoter_spec.rb b/spec/bundler/gem_version_promoter_spec.rb index 9e5a7bb581..c7620e2620 100644 --- a/spec/bundler/gem_version_promoter_spec.rb +++ b/spec/bundler/gem_version_promoter_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::GemVersionPromoter do +RSpec.describe Bundler::GemVersionPromoter do context "conservative resolver" do def versions(result) result.flatten.map(&:version).map(&:to_s) diff --git a/spec/bundler/index_spec.rb b/spec/bundler/index_spec.rb index da8e5731ca..09b09e08fa 100644 --- a/spec/bundler/index_spec.rb +++ b/spec/bundler/index_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Index do +RSpec.describe Bundler::Index do let(:specs) { [] } subject { described_class.build {|i| i.use(specs) } } @@ -26,4 +26,12 @@ describe Bundler::Index do end end end + + context "with specs that include development dependencies" do + let(:specs) { [*build_spec("a", "1.0.0") {|s| s.development("b", "~> 1.0") }] } + + it "does not include b in #dependency_names" do + expect(subject.dependency_names).not_to include("b") + end + end end diff --git a/spec/bundler/installer/gem_installer_spec.rb b/spec/bundler/installer/gem_installer_spec.rb new file mode 100644 index 0000000000..de5189eecd --- /dev/null +++ b/spec/bundler/installer/gem_installer_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require "spec_helper" +require "bundler/installer/gem_installer" + +RSpec.describe Bundler::GemInstaller do + let(:installer) { instance_double("Installer") } + let(:spec_source) { instance_double("SpecSource") } + let(:spec) { instance_double("Specification", :name => "dummy", :version => "0.0.1", :loaded_from => "dummy", :source => spec_source) } + + subject { described_class.new(spec, installer) } + + context "spec_settings is nil" do + it "invokes install method with empty build_args", :rubygems => ">= 2" do + allow(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => []) + subject.install_from_spec + end + end + + context "spec_settings is build option" do + it "invokes install method with build_args", :rubygems => ">= 2" do + allow(Bundler.settings).to receive(:[]).with(:bin) + allow(Bundler.settings).to receive(:[]).with("build.dummy").and_return("--with-dummy-config=dummy") + allow(spec_source).to receive(:install).with(spec, :force => false, :ensure_builtin_gems_cached => false, :build_args => ["--with-dummy-config=dummy"]) + subject.install_from_spec + end + end +end diff --git a/spec/bundler/installer/parallel_installer_spec.rb b/spec/bundler/installer/parallel_installer_spec.rb new file mode 100644 index 0000000000..7d2c441399 --- /dev/null +++ b/spec/bundler/installer/parallel_installer_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +require "spec_helper" +require "bundler/installer/parallel_installer" + +RSpec.describe Bundler::ParallelInstaller do + let(:installer) { instance_double("Installer") } + let(:all_specs) { [] } + let(:size) { 1 } + let(:standalone) { false } + let(:force) { false } + + subject { described_class.new(installer, all_specs, size, standalone, force) } + + context "when dependencies that are not on the overall installation list are the only ones not installed" do + let(:all_specs) do + [ + build_spec("alpha", "1.0") {|s| s.runtime "a", "1" }, + ].flatten + end + + it "prints a warning" do + expect(Bundler.ui).to receive(:warn).with(<<-W.strip) +Your lockfile was created by an old Bundler that left some things out. +You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile. +The missing gems are: +* a depended upon by alpha + W + subject.check_for_corrupt_lockfile + end + + context "when size > 1" do + let(:size) { 500 } + + it "prints a warning and sets size to 1" do + expect(Bundler.ui).to receive(:warn).with(<<-W.strip) +Your lockfile was created by an old Bundler that left some things out. +Because of the missing DEPENDENCIES, we can only install gems one at a time, instead of installing 500 at a time. +You can fix this by adding the missing gems to your Gemfile, running bundle install, and then removing the gems from your Gemfile. +The missing gems are: +* a depended upon by alpha + W + subject.check_for_corrupt_lockfile + expect(subject.size).to eq(1) + end + end + end +end diff --git a/spec/install/parallel/spec_installation_spec.rb b/spec/bundler/installer/spec_installation_spec.rb index 7bbd2bd0e6..1e368ab7c5 100644 --- a/spec/install/parallel/spec_installation_spec.rb +++ b/spec/bundler/installer/spec_installation_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/installer/parallel_installer" -describe Bundler::ParallelInstaller::SpecInstallation do +RSpec.describe Bundler::ParallelInstaller::SpecInstallation do let!(:dep) do a_spec = Object.new def a_spec.name @@ -58,20 +58,5 @@ describe Bundler::ParallelInstaller::SpecInstallation do expect(spec.dependencies_installed?(all_specs)).to be_falsey end end - - context "when dependencies that are not on the overall installation list are the only ones not installed" do - it "raises an error" do - dependencies = [] - dependencies << instance_double("SpecInstallation", :spec => "alpha", :name => "alpha", :installed? => true, :all_dependencies => [], :type => :production) - all_specs = dependencies + [instance_double("SpecInstallation", :spec => "gamma", :name => "gamma", :installed? => false, :all_dependencies => [], :type => :production)] - # Add dependency which is not in all_specs - dependencies << instance_double("SpecInstallation", :spec => "beta", :name => "beta", :installed? => false, :all_dependencies => [], :type => :production) - dependencies << instance_double("SpecInstallation", :spec => "delta", :name => "delta", :installed? => false, :all_dependencies => [], :type => :production) - spec = described_class.new(dep) - allow(spec).to receive(:all_dependencies).and_return(dependencies) - expect { spec.dependencies_installed?(all_specs) }. - to raise_error(Bundler::LockfileError, /Your Gemfile.lock is corrupt\. The following.*'beta' 'delta'/) - end - end end end diff --git a/spec/bundler/lockfile_parser_spec.rb b/spec/bundler/lockfile_parser_spec.rb index 98d7b68c6e..17bb447194 100644 --- a/spec/bundler/lockfile_parser_spec.rb +++ b/spec/bundler/lockfile_parser_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/lockfile_parser" -describe Bundler::LockfileParser do +RSpec.describe Bundler::LockfileParser do let(:lockfile_contents) { strip_whitespace(<<-L) } GIT remote: https://github.com/alloy/peiji-san.git diff --git a/spec/bundler/mirror_spec.rb b/spec/bundler/mirror_spec.rb index eb0ccf0bdf..9051a80465 100644 --- a/spec/bundler/mirror_spec.rb +++ b/spec/bundler/mirror_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/mirror" -describe Bundler::Settings::Mirror do +RSpec.describe Bundler::Settings::Mirror do let(:mirror) { Bundler::Settings::Mirror.new } it "returns zero when fallback_timeout is not set" do @@ -131,10 +131,20 @@ describe Bundler::Settings::Mirror do end end end + + describe "#==" do + it "returns true if uri and fallback timeout are the same" do + uri = "https://ruby.taobao.org" + mirror = Bundler::Settings::Mirror.new(uri, 1) + another_mirror = Bundler::Settings::Mirror.new(uri, 1) + + expect(mirror == another_mirror).to be true + end + end end end -describe Bundler::Settings::Mirrors do +RSpec.describe Bundler::Settings::Mirrors do let(:localhost_uri) { URI("http://localhost:9292") } context "with a just created mirror" do @@ -283,7 +293,7 @@ describe Bundler::Settings::Mirrors do end end -describe Bundler::Settings::TCPSocketProbe do +RSpec.describe Bundler::Settings::TCPSocketProbe do let(:probe) { Bundler::Settings::TCPSocketProbe.new } context "with a listening TCP Server" do diff --git a/spec/bundler/plugin/api/source_spec.rb b/spec/bundler/plugin/api/source_spec.rb index d62127a604..4dbb993b89 100644 --- a/spec/bundler/plugin/api/source_spec.rb +++ b/spec/bundler/plugin/api/source_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin::API::Source do +RSpec.describe Bundler::Plugin::API::Source do let(:uri) { "uri://to/test" } let(:type) { "spec_type" } diff --git a/spec/bundler/plugin/api_spec.rb b/spec/bundler/plugin/api_spec.rb index 0eba52301a..e40b9adb0f 100644 --- a/spec/bundler/plugin/api_spec.rb +++ b/spec/bundler/plugin/api_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin::API do +RSpec.describe Bundler::Plugin::API do context "plugin declarations" do before do stub_const "UserPluginClass", Class.new(Bundler::Plugin::API) diff --git a/spec/bundler/plugin/dsl_spec.rb b/spec/bundler/plugin/dsl_spec.rb index 9a694833ff..cd15b6ea9d 100644 --- a/spec/bundler/plugin/dsl_spec.rb +++ b/spec/bundler/plugin/dsl_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin::DSL do +RSpec.describe Bundler::Plugin::DSL do DSL = Bundler::Plugin::DSL subject(:dsl) { Bundler::Plugin::DSL.new } diff --git a/spec/bundler/plugin/index_spec.rb b/spec/bundler/plugin/index_spec.rb index 5a754f355c..24b9a408ff 100644 --- a/spec/bundler/plugin/index_spec.rb +++ b/spec/bundler/plugin/index_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin::Index do +RSpec.describe Bundler::Plugin::Index do Index = Bundler::Plugin::Index before do @@ -31,7 +31,7 @@ describe Bundler::Plugin::Index do expect(new_index.plugin_path(plugin_name)).to eq(lib_path(plugin_name)) end - it "load_paths are persistant" do + it "load_paths are persistent" do new_index = Index.new expect(new_index.load_paths(plugin_name)).to eq([lib_path(plugin_name).join("lib").to_s]) end @@ -95,7 +95,7 @@ describe Bundler::Plugin::Index do allow(File).to receive(:open).and_yield(file) end - it "should not save it with next registed hook" do + it "should not save it with next registered hook" do expect(file).to receive(:puts) do |content| expect(content).not_to include("not-there") end diff --git a/spec/bundler/plugin/installer_spec.rb b/spec/bundler/plugin/installer_spec.rb index 9d6eb1c55b..2454ef00f9 100644 --- a/spec/bundler/plugin/installer_spec.rb +++ b/spec/bundler/plugin/installer_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin::Installer do +RSpec.describe Bundler::Plugin::Installer do subject(:installer) { Bundler::Plugin::Installer.new } describe "cli install" do diff --git a/spec/bundler/plugin/source_list_spec.rb b/spec/bundler/plugin/source_list_spec.rb index 774156b27c..86cc4ac4ed 100644 --- a/spec/bundler/plugin/source_list_spec.rb +++ b/spec/bundler/plugin/source_list_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin::SourceList do +RSpec.describe Bundler::Plugin::SourceList do SourceList = Bundler::Plugin::SourceList before do diff --git a/spec/bundler/plugin_spec.rb b/spec/bundler/plugin_spec.rb index 540278036c..5bbb7384c8 100644 --- a/spec/bundler/plugin_spec.rb +++ b/spec/bundler/plugin_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Plugin do +RSpec.describe Bundler::Plugin do Plugin = Bundler::Plugin let(:installer) { double(:installer) } diff --git a/spec/bundler/psyched_yaml_spec.rb b/spec/bundler/psyched_yaml_spec.rb index 4b7715b482..18e40d6b5a 100644 --- a/spec/bundler/psyched_yaml_spec.rb +++ b/spec/bundler/psyched_yaml_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/psyched_yaml" -describe "Bundler::YamlLibrarySyntaxError" do +RSpec.describe "Bundler::YamlLibrarySyntaxError" do it "is raised on YAML parse errors" do expect { YAML.parse "{foo" }.to raise_error(Bundler::YamlLibrarySyntaxError) end diff --git a/spec/bundler/remote_specification_spec.rb b/spec/bundler/remote_specification_spec.rb index 28d78a82c7..69a2ab3d67 100644 --- a/spec/bundler/remote_specification_spec.rb +++ b/spec/bundler/remote_specification_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::RemoteSpecification do +RSpec.describe Bundler::RemoteSpecification do let(:name) { "foo" } let(:version) { Gem::Version.new("1.0.0") } let(:platform) { Gem::Platform::RUBY } @@ -128,8 +128,8 @@ describe Bundler::RemoteSpecification do end describe "#__swap__" do - let(:spec) { double(:spec) } - let(:new_spec) { double(:new_spec) } + let(:spec) { double(:spec, :dependencies => []) } + let(:new_spec) { double(:new_spec, :runtime_dependencies => []) } before { subject.instance_variable_set(:@_remote_specification, spec) } @@ -158,32 +158,30 @@ describe Bundler::RemoteSpecification do describe "method missing" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec) } + let(:remote_spec) { double(:remote_spec, :authors => "abcd") } before do - allow_any_instance_of(Gem::Specification).to receive(:respond_to?).and_return(true) allow(subject).to receive(:_remote_specification).and_return(remote_spec) + expect(subject.methods.map(&:to_sym)).not_to include(:authors) end it "should send through to Gem::Specification" do - expect(remote_spec).to receive(:send).with(:missing_method_call).once - subject.missing_method_call + expect(subject.authors).to eq("abcd") end end end describe "respond to missing?" do context "and is present in Gem::Specification" do - let(:remote_spec) { double(:remote_spec) } + let(:remote_spec) { double(:remote_spec, :authors => "abcd") } before do - allow_any_instance_of(Gem::Specification).to receive(:respond_to?).and_return(false) allow(subject).to receive(:_remote_specification).and_return(remote_spec) + expect(subject.methods.map(&:to_sym)).not_to include(:authors) end it "should send through to Gem::Specification" do - expect(remote_spec).to receive(:respond_to?).with(:missing_method_call, false).once - subject.respond_to?(:missing_method_call) + expect(subject.respond_to?(:authors)).to be_truthy end end end diff --git a/spec/bundler/retry_spec.rb b/spec/bundler/retry_spec.rb index 665ba9f2df..525f05d327 100644 --- a/spec/bundler/retry_spec.rb +++ b/spec/bundler/retry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Retry do +RSpec.describe Bundler::Retry do it "return successful result if no errors" do attempts = 0 result = Bundler::Retry.new(nil, nil, 3).attempt do @@ -65,10 +65,10 @@ describe Bundler::Retry do end end - context "with debugging on" do + context "with debugging off" do it "print error message with newlines" do allow(Bundler.ui).to receive(:debug?).and_return(false) - expect(Bundler.ui).to receive(:info).with("") + expect(Bundler.ui).to receive(:info).with("").twice expect(Bundler.ui).to receive(:warn).with(failure_message, false) expect do diff --git a/spec/bundler/ruby_dsl_spec.rb b/spec/bundler/ruby_dsl_spec.rb index ac73747ac7..3e0ec9d7f0 100644 --- a/spec/bundler/ruby_dsl_spec.rb +++ b/spec/bundler/ruby_dsl_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/ruby_dsl" -describe Bundler::RubyDsl do +RSpec.describe Bundler::RubyDsl do class MockDSL include Bundler::RubyDsl diff --git a/spec/bundler/ruby_version_spec.rb b/spec/bundler/ruby_version_spec.rb index abcd0303d0..f77ec606fc 100644 --- a/spec/bundler/ruby_version_spec.rb +++ b/spec/bundler/ruby_version_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/ruby_version" -describe "Bundler::RubyVersion and its subclasses" do +RSpec.describe "Bundler::RubyVersion and its subclasses" do let(:version) { "2.0.0" } let(:patchlevel) { "645" } let(:engine) { "jruby" } @@ -35,6 +35,14 @@ describe "Bundler::RubyVersion and its subclasses" do end end + context "with engine in symbol" do + let(:engine) { :jruby } + + it "should coerce engine to string" do + expect(subject.engine).to eq("jruby") + end + end + context "is called with multiple requirements" do let(:version) { ["<= 2.0.0", "> 1.9.3"] } let(:engine_version) { nil } diff --git a/spec/bundler/rubygems_integration_spec.rb b/spec/bundler/rubygems_integration_spec.rb index fbc49c414c..37eb499e38 100644 --- a/spec/bundler/rubygems_integration_spec.rb +++ b/spec/bundler/rubygems_integration_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::RubygemsIntegration do +RSpec.describe Bundler::RubygemsIntegration do it "uses the same chdir lock as rubygems", :rubygems => "2.1" do expect(Bundler.rubygems.ext_lock).to eq(Gem::Ext::Builder::CHDIR_MONITOR) end diff --git a/spec/bundler/settings_spec.rb b/spec/bundler/settings_spec.rb index 0f7d2a0138..020897882c 100644 --- a/spec/bundler/settings_spec.rb +++ b/spec/bundler/settings_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/settings" -describe Bundler::Settings do +RSpec.describe Bundler::Settings do subject(:settings) { described_class.new(bundled_app) } describe "#set_local" do @@ -51,9 +51,41 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end] expect(loaded).to eq(expected) end + + context "when BUNDLE_IGNORE_CONFIG is set" do + before { ENV["BUNDLE_IGNORE_CONFIG"] = "TRUE" } + + it "ignores the config" do + loaded = settings.send(:load_config, bundled_app("config")) + expect(loaded).to eq({}) + end + end + end + + describe "#global_config_file" do + context "when $HOME is not accessible" do + context "when $TMPDIR is not writable" do + it "does not raise" do + expect(Bundler.rubygems).to receive(:user_home).twice.and_return(nil) + expect(FileUtils).to receive(:mkpath).twice.with(File.join(Dir.tmpdir, "bundler", "home")).and_raise(Errno::EROFS, "Read-only file system @ dir_s_mkdir - /tmp/bundler") + + expect(subject.send(:global_config_file)).to be_nil + end + end + end end describe "#[]" do + context "when the local config file is not found" do + subject(:settings) { described_class.new } + + it "does not raise" do + expect do + subject["foo"] + end.not_to raise_error + end + end + context "when not set" do context "when default value present" do it "retrieves value" do @@ -96,6 +128,18 @@ that would suck --ehhh=oh geez it looks like i might have broken bundler somehow end end + describe "#temporary" do + it "reset after used" do + Bundler.settings[:no_install] = true + + Bundler.settings.temporary(:no_install => false) do + expect(Bundler.settings[:no_install]).to eq false + end + + expect(Bundler.settings[:no_install]).to eq true + end + end + describe "#set_global" do context "when it's not possible to write to the file" do it "raises an PermissionError with explanation" do diff --git a/spec/bundler/shared_helpers_spec.rb b/spec/bundler/shared_helpers_spec.rb index 4c0d61cf0a..8bd7215666 100644 --- a/spec/bundler/shared_helpers_spec.rb +++ b/spec/bundler/shared_helpers_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::SharedHelpers do +RSpec.describe Bundler::SharedHelpers do let(:ext_lock_double) { double(:ext_lock) } before do @@ -234,7 +234,9 @@ describe Bundler::SharedHelpers do shared_examples_for "ENV['RUBYLIB'] gets set correctly" do let(:ruby_lib_path) { "stubbed_ruby_lib_dir" } - before { allow(File).to receive(:expand_path).and_return(ruby_lib_path) } + before do + allow(Bundler::SharedHelpers).to receive(:bundler_ruby_lib).and_return(ruby_lib_path) + end it "ensures bundler's ruby version lib path is in ENV['RUBYLIB']" do subject.set_bundle_environment @@ -324,7 +326,6 @@ describe Bundler::SharedHelpers do let(:ruby_lib_path) { "stubbed_ruby_lib_dir" } before do - allow(File).to receive(:expand_path).and_return(ruby_lib_path) ENV["RUBYLIB"] = ruby_lib_path end @@ -387,6 +388,27 @@ describe Bundler::SharedHelpers do ) end end + + context "system throws Errno::ENOSPC" do + let(:file_op_block) { proc {|_path| raise Errno::ENOSPC } } + + it "raises a NoSpaceOnDeviceError" do + expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error( + Bundler::NoSpaceOnDeviceError + ) + end + end + + context "system throws an unhandled SystemCallError" do + let(:error) { SystemCallError.new("Shields down", 1337) } + let(:file_op_block) { proc {|_path| raise error } } + + it "raises a GenericSystemCallError" do + expect { subject.filesystem_access("/path", &file_op_block) }.to raise_error( + Bundler::GenericSystemCallError, /error accessing.+underlying.+Shields down/m + ) + end + end end describe "#const_get_safely" do diff --git a/spec/bundler/source/git/git_proxy_spec.rb b/spec/bundler/source/git/git_proxy_spec.rb index ce6b79b2b2..34fe21e9fb 100644 --- a/spec/bundler/source/git/git_proxy_spec.rb +++ b/spec/bundler/source/git/git_proxy_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Source::Git::GitProxy do +RSpec.describe Bundler::Source::Git::GitProxy do let(:uri) { "https://github.com/bundler/bundler.git" } subject { described_class.new(Pathname("path"), uri, "HEAD") } diff --git a/spec/bundler/source/rubygems/remote_spec.rb b/spec/bundler/source/rubygems/remote_spec.rb index a541bf1468..54394fc0ca 100644 --- a/spec/bundler/source/rubygems/remote_spec.rb +++ b/spec/bundler/source/rubygems/remote_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/source/rubygems/remote" -describe Bundler::Source::Rubygems::Remote do +RSpec.describe Bundler::Source::Rubygems::Remote do def remote(uri) Bundler::Source::Rubygems::Remote.new(uri) end diff --git a/spec/bundler/source/rubygems_spec.rb b/spec/bundler/source/rubygems_spec.rb index 9fb4bf0ad3..b8f9f09c20 100644 --- a/spec/bundler/source/rubygems_spec.rb +++ b/spec/bundler/source/rubygems_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Source::Rubygems do +RSpec.describe Bundler::Source::Rubygems do before do allow(Bundler).to receive(:root) { Pathname.new("root") } end diff --git a/spec/bundler/source_list_spec.rb b/spec/bundler/source_list_spec.rb index 3657ac0927..6a23c8bcbf 100644 --- a/spec/bundler/source_list_spec.rb +++ b/spec/bundler/source_list_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::SourceList do +RSpec.describe Bundler::SourceList do before do allow(Bundler).to receive(:root) { Pathname.new "./tmp/bundled_app" } diff --git a/spec/bundler/source_spec.rb b/spec/bundler/source_spec.rb index 4e99411a17..08d1698fcd 100644 --- a/spec/bundler/source_spec.rb +++ b/spec/bundler/source_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::Source do +RSpec.describe Bundler::Source do class ExampleSource < Bundler::Source end @@ -61,7 +61,7 @@ describe Bundler::Source do before { Bundler.ui = Bundler::UI::Shell.new } it "should return a string with the spec name and version and locked spec version" do - expect(subject.version_message(spec)).to eq("nokogiri >= 1.6 (\e[32mwas < 1.5\e[0m)") + expect(subject.version_message(spec)).to eq("nokogiri >= 1.6\e[32m (was < 1.5)\e[0m") end end @@ -71,6 +71,32 @@ describe Bundler::Source do end end end + + context "with a more recent version" do + let(:spec) { double(:spec, :name => "nokogiri", :version => "1.6.1", :platform => rb) } + let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") } + + context "with color" do + before { Bundler.ui = Bundler::UI::Shell.new } + + it "should return a string with the locked spec version in yellow" do + expect(subject.version_message(spec)).to eq("nokogiri 1.6.1\e[33m (was 1.7.0)\e[0m") + end + end + end + + context "with an older version" do + let(:spec) { double(:spec, :name => "nokogiri", :version => "1.7.1", :platform => rb) } + let(:locked_gem) { double(:locked_gem, :name => "nokogiri", :version => "1.7.0") } + + context "with color" do + before { Bundler.ui = Bundler::UI::Shell.new } + + it "should return a string with the locked spec version in green" do + expect(subject.version_message(spec)).to eq("nokogiri 1.7.1\e[32m (was 1.7.0)\e[0m") + end + end + end end context "that do not contain the relevant gem spec" do diff --git a/spec/bundler/spec_set_spec.rb b/spec/bundler/spec_set_spec.rb index 29d81cb30c..8f7c27f065 100644 --- a/spec/bundler/spec_set_spec.rb +++ b/spec/bundler/spec_set_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::SpecSet do +RSpec.describe Bundler::SpecSet do let(:specs) do [ build_spec("a", "1.0"), diff --git a/spec/bundler/ssl_certs/certificate_manager_spec.rb b/spec/bundler/ssl_certs/certificate_manager_spec.rb index cc8c8a49a1..51552f7234 100644 --- a/spec/bundler/ssl_certs/certificate_manager_spec.rb +++ b/spec/bundler/ssl_certs/certificate_manager_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/ssl_certs/certificate_manager" -describe Bundler::SSLCerts::CertificateManager do +RSpec.describe Bundler::SSLCerts::CertificateManager do let(:rubygems_path) { root } let(:stub_cert) { File.join(root.to_s, "lib", "rubygems", "ssl_certs", "rubygems.org", "ssl-cert.pem") } let(:rubygems_certs_dir) { File.join(root.to_s, "lib", "rubygems", "ssl_certs", "rubygems.org") } diff --git a/spec/bundler/ui_spec.rb b/spec/bundler/ui_spec.rb new file mode 100644 index 0000000000..41c037527b --- /dev/null +++ b/spec/bundler/ui_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe Bundler::UI do + describe Bundler::UI::Silent do + it "has the same instance methods as Shell", :ruby => ">= 1.9" do + shell = Bundler::UI::Shell + methods = proc do |cls| + cls.instance_methods.map do |i| + m = shell.instance_method(i) + [i, m.parameters] + end.sort_by(&:first) + end + expect(methods.call(described_class)).to eq(methods.call(shell)) + end + + it "has the same instance class as Shell", :ruby => ">= 1.9" do + shell = Bundler::UI::Shell + methods = proc do |cls| + cls.methods.map do |i| + m = shell.method(i) + [i, m.parameters] + end.sort_by(&:first) + end + expect(methods.call(described_class)).to eq(methods.call(shell)) + end + end +end diff --git a/spec/bundler/uri_credentials_filter_spec.rb b/spec/bundler/uri_credentials_filter_spec.rb index 70f71cecac..1dd01b4be0 100644 --- a/spec/bundler/uri_credentials_filter_spec.rb +++ b/spec/bundler/uri_credentials_filter_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe Bundler::URICredentialsFilter do +RSpec.describe Bundler::URICredentialsFilter do subject { described_class } describe "#credential_filtered_uri" do diff --git a/spec/bundler/worker_spec.rb b/spec/bundler/worker_spec.rb new file mode 100644 index 0000000000..fbfe6ddab3 --- /dev/null +++ b/spec/bundler/worker_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +require "spec_helper" +require "bundler/worker" + +RSpec.describe Bundler::Worker do + let(:size) { 5 } + let(:name) { "Spec Worker" } + let(:function) { proc {|object, worker_number| [object, worker_number] } } + subject { described_class.new(size, name, function) } + + after { subject.stop } + + describe "#initialize" do + context "when Thread.start raises ThreadError" do + it "raises when no threads can be created" do + allow(Thread).to receive(:start).and_raise(ThreadError, "error creating thread") + + expect { subject.enq "a" }.to raise_error(Bundler::ThreadCreationError, "Failed to create threads for the Spec Worker worker: error creating thread") + end + end + end +end diff --git a/spec/bundler/yaml_serializer_spec.rb b/spec/bundler/yaml_serializer_spec.rb index bf86d2a076..361159e0da 100644 --- a/spec/bundler/yaml_serializer_spec.rb +++ b/spec/bundler/yaml_serializer_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/yaml_serializer" -describe Bundler::YAMLSerializer do +RSpec.describe Bundler::YAMLSerializer do subject(:serializer) { Bundler::YAMLSerializer } describe "#dump" do diff --git a/spec/cache/cache_path_spec.rb b/spec/cache/cache_path_spec.rb index 594162886a..ec6d6e312a 100644 --- a/spec/cache/cache_path_spec.rb +++ b/spec/cache/cache_path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle package" do +RSpec.describe "bundle package" do before do gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/cache/gems_spec.rb b/spec/cache/gems_spec.rb index 474233a83c..7828c87fec 100644 --- a/spec/cache/gems_spec.rb +++ b/spec/cache/gems_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle cache" do +RSpec.describe "bundle cache" do describe "when there are only gemsources" do before :each do gemfile <<-G @@ -238,7 +238,7 @@ describe "bundle cache" do gem "platform_specific" G - expect(cached_gem("platform_specific-1.0-#{Gem::Platform.local}")).to exist + expect(cached_gem("platform_specific-1.0-#{Bundler.local_platform}")).to exist expect(cached_gem("platform_specific-1.0-java")).to exist end diff --git a/spec/cache/git_spec.rb b/spec/cache/git_spec.rb index c15ee26c25..b256c8d90f 100644 --- a/spec/cache/git_spec.rb +++ b/spec/cache/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "git base name" do +RSpec.describe "git base name" do it "base_name should strip private repo uris" do source = Bundler::Source::Git.new("uri" => "git@github.com:bundler.git") expect(source.send(:base_name)).to eq("bundler") @@ -14,7 +14,7 @@ describe "git base name" do end %w(cache package).each do |cmd| - describe "bundle #{cmd} with git" do + RSpec.describe "bundle #{cmd} with git" do it "copies repository to vendor cache and uses it" do git = build_git "foo" ref = git.ref_for("master", 11) diff --git a/spec/cache/path_spec.rb b/spec/cache/path_spec.rb index 4233b3e88b..bbce448759 100644 --- a/spec/cache/path_spec.rb +++ b/spec/cache/path_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" %w(cache package).each do |cmd| - describe "bundle #{cmd} with path" do + RSpec.describe "bundle #{cmd} with path" do it "is no-op when the path is within the bundle" do build_lib "foo", :path => bundled_app("lib/foo") diff --git a/spec/cache/platform_spec.rb b/spec/cache/platform_spec.rb index 259eff0e15..ed80c949aa 100644 --- a/spec/cache/platform_spec.rb +++ b/spec/cache/platform_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle cache with multiple platforms" do +RSpec.describe "bundle cache with multiple platforms" do before :each do gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/commands/binstubs_spec.rb b/spec/commands/binstubs_spec.rb index 0f8c5af391..cb0999348e 100644 --- a/spec/commands/binstubs_spec.rb +++ b/spec/commands/binstubs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle binstubs <gem>" do +RSpec.describe "bundle binstubs <gem>" do context "when the gem exists in the lockfile" do it "sets up the binstub" do install_gemfile <<-G @@ -157,7 +157,7 @@ describe "bundle binstubs <gem>" do it "includes the standalone path" do bundle "binstubs rack --standalone" standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip - expect(standalone_line).to eq "$:.unshift File.expand_path '../../bundle', path.realpath" + expect(standalone_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) end end diff --git a/spec/commands/check_spec.rb b/spec/commands/check_spec.rb index 42d6459760..532be07c3f 100644 --- a/spec/commands/check_spec.rb +++ b/spec/commands/check_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle check" do +RSpec.describe "bundle check" do it "returns success when the Gemfile is satisfied" do install_gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/commands/clean_spec.rb b/spec/commands/clean_spec.rb index 72d422c3a2..8fd4b6fbe8 100644 --- a/spec/commands/clean_spec.rb +++ b/spec/commands/clean_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle clean" do +RSpec.describe "bundle clean" do def should_have_gems(*gems) gems.each do |g| expect(vendored_gems("gems/#{g}")).to exist @@ -567,7 +567,7 @@ describe "bundle clean" do expect(exitstatus).to eq(0) if exitstatus end - it "doesn't remove gems in dry-run mode" do + it "doesn't remove gems in dry-run mode with path set" do gemfile <<-G source "file://#{gem_repo1}" @@ -595,6 +595,36 @@ describe "bundle clean" do expect(vendored_gems("bin/rackup")).to exist end + it "doesn't remove gems in dry-run mode with no path set" 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" + G + + bundle :install + + bundle "configuration --delete path" + + bundle "clean --dry-run" + + expect(out).not_to include("Removing foo (1.0)") + expect(out).to include("Would have removed foo (1.0)") + + should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0" + + expect(vendored_gems("bin/rackup")).to exist + end + it "doesn't store dry run as a config setting" do gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/commands/config_spec.rb b/spec/commands/config_spec.rb index 15c1c365c8..640ec06e17 100644 --- a/spec/commands/config_spec.rb +++ b/spec/commands/config_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe ".bundle/config" do +RSpec.describe ".bundle/config" do before :each do gemfile <<-G source "file://#{gem_repo1}" @@ -319,7 +319,7 @@ E end end -describe "setting gemfile via config" do +RSpec.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| diff --git a/spec/commands/console_spec.rb b/spec/commands/console_spec.rb index 131b47368b..de14b6db5f 100644 --- a/spec/commands/console_spec.rb +++ b/spec/commands/console_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle console" do +RSpec.describe "bundle console" do before :each do install_gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/commands/doctor_spec.rb b/spec/commands/doctor_spec.rb index 8debeb55e4..7c6e48ce19 100644 --- a/spec/commands/doctor_spec.rb +++ b/spec/commands/doctor_spec.rb @@ -4,7 +4,7 @@ require "stringio" require "bundler/cli" require "bundler/cli/doctor" -describe "bundle doctor" do +RSpec.describe "bundle doctor" do before(:each) do @stdout = StringIO.new diff --git a/spec/commands/exec_spec.rb b/spec/commands/exec_spec.rb index 42afbd24ba..3c5f2ca1dd 100644 --- a/spec/commands/exec_spec.rb +++ b/spec/commands/exec_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle exec" do +RSpec.describe "bundle exec" do + let(:system_gems_to_install) { %w(rack-1.0.0 rack-0.9.1) } before :each do - system_gems "rack-1.0.0", "rack-0.9.1" + system_gems(system_gems_to_install) end it "activates the correct gem" do @@ -271,35 +272,35 @@ describe "bundle exec" do with_fake_man do bundle "#{exec} --help cat" end - expect(out).to include(%(["#{root}/lib/bundler/man/bundle-exec"])) + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) end it "shows bundle-exec's man page when --help is before exec" do with_fake_man do bundle "--help #{exec}" end - expect(out).to include(%(["#{root}/lib/bundler/man/bundle-exec"])) + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) end it "shows bundle-exec's man page when -h is before exec" do with_fake_man do bundle "-h #{exec}" end - expect(out).to include(%(["#{root}/lib/bundler/man/bundle-exec"])) + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) end it "shows bundle-exec's man page when --help is after exec" do with_fake_man do bundle "#{exec} --help" end - expect(out).to include(%(["#{root}/lib/bundler/man/bundle-exec"])) + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) end it "shows bundle-exec's man page when -h is after exec" do with_fake_man do bundle "#{exec} -h" end - expect(out).to include(%(["#{root}/lib/bundler/man/bundle-exec"])) + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) end end end @@ -513,6 +514,21 @@ describe "bundle exec" do end end + context "the executable is empty" do + let(:executable) { "" } + + let(:exit_code) { 0 } + let(:expected) { "#{path} is empty" } + let(:expected_err) { "" } + if LessThanProc.with(RUBY_VERSION).call("1.9") + # Kernel#exec in ruby < 1.9 will raise Errno::ENOEXEC if the command content is empty, + # even if the command is set as an executable. + pending "Kernel#exec is different" + else + it_behaves_like "it runs" + end + end + context "the executable raises" do let(:executable) { super() << "\nraise 'ERROR'" } let(:exit_code) { 1 } @@ -539,7 +555,7 @@ describe "bundle exec" do let(:exit_code) { Bundler::GemNotFound.new.status_code } let(:expected) { <<-EOS.strip } -\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile or available on this machine.\e[0m +\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m \e[33mRun `bundle install` to install missing gems.\e[0m EOS @@ -627,4 +643,68 @@ __FILE__: #{path.to_s.inspect} end end end + + context "nested bundle exec" do + let(:system_gems_to_install) { super() << :bundler } + + context "with shared gems disabled" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle :install, :system_bundler => true, :path => "vendor/bundler" + end + + it "overrides disable_shared_gems so bundler can be found" do + file = bundled_app("file_that_bundle_execs.rb") + create_file(file, <<-RB) + #!#{Gem.ruby} + puts `bundle exec echo foo` + RB + file.chmod(0o777) + bundle! "exec #{file}", :system_bundler => true + expect(out).to eq("foo") + end + end + + context "with a system gem that shadows a default gem" do + let(:openssl_version) { "2.0.3" } + let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", :artifice => nil } + + it "only leaves the default gem in the stdlib available" do + skip "openssl isn't a default gem" if expected.empty? + + install_gemfile! "" # must happen before installing the broken system gem + + build_repo4 do + build_gem "openssl", openssl_version do |s| + s.write("lib/openssl.rb", <<-RB) + raise "custom openssl should not be loaded, it's not in the gemfile!" + RB + end + end + + system_gems(:bundler, "openssl-#{openssl_version}", :gem_repo => gem_repo4) + + file = bundled_app("require_openssl.rb") + create_file(file, <<-RB) + #!/usr/bin/env ruby + require "openssl" + puts OpenSSL::VERSION + RB + file.chmod(0o777) + + aggregate_failures do + expect(bundle!("exec #{file}", :system_bundler => true, :artifice => nil)).to eq(expected) + expect(bundle!("exec bundle exec #{file}", :system_bundler => true, :artifice => nil)).to eq(expected) + expect(bundle!("exec ruby #{file}", :system_bundler => true, :artifice => nil)).to eq(expected) + end + + # sanity check that we get the newer, custom version without bundler + sys_exec(file.to_s) + expect(err).to include("custom openssl should not be loaded") + end + end + end end diff --git a/spec/commands/help_spec.rb b/spec/commands/help_spec.rb index d59346f615..790d26fda4 100644 --- a/spec/commands/help_spec.rb +++ b/spec/commands/help_spec.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle help" do +RSpec.describe "bundle help" do # Rubygems 1.4+ no longer load gem plugins so this test is no longer needed - rubygems_under_14 = Gem::Requirement.new("< 1.4").satisfied_by?(Gem::Version.new(Gem::VERSION)) - it "complains if older versions of bundler are installed", :if => rubygems_under_14 do + it "complains if older versions of bundler are installed", :rubygems => "< 1.4" do system_gems "bundler-0.8.1" bundle "help" @@ -16,14 +15,14 @@ describe "bundle help" do with_fake_man do bundle "help gemfile" end - expect(out).to eq(%(["#{root}/lib/bundler/man/gemfile.5"])) + expect(out).to eq(%(["#{root}/man/gemfile.5"])) end it "prefixes bundle commands with bundle- when finding the groff files" do with_fake_man do bundle "help install" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle-install"])) + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) end it "simply outputs the txt file when there is no man on the path" do @@ -34,8 +33,8 @@ describe "bundle help" do end it "still outputs the old help for commands that do not have man pages yet" do - bundle "help check" - expect(out).to include("Check searches the local machine") + bundle "help version" + expect(out).to include("Prints the bundler's version information") end it "looks for a binary and executes it with --help option if it's named bundler-<task>" do @@ -55,28 +54,28 @@ describe "bundle help" do with_fake_man do bundle "install --help" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle-install"])) + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) end it "is called when the --help flag is used before the command" do with_fake_man do bundle "--help install" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle-install"])) + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) end it "is called when the -h flag is used before the command" do with_fake_man do bundle "-h install" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle-install"])) + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) end it "is called when the -h flag is used after the command" do with_fake_man do bundle "install -h" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle-install"])) + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) end it "has helpful output when using --help flag for a non-existent command" do @@ -90,11 +89,11 @@ describe "bundle help" do with_fake_man do bundle "--help" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle"])) + expect(out).to eq(%(["#{root}/man/bundle.1"])) with_fake_man do bundle "-h" end - expect(out).to eq(%(["#{root}/lib/bundler/man/bundle"])) + expect(out).to eq(%(["#{root}/man/bundle.1"])) end end diff --git a/spec/commands/info_spec.rb b/spec/commands/info_spec.rb new file mode 100644 index 0000000000..b37de765e4 --- /dev/null +++ b/spec/commands/info_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe "bundle info" do + context "info from specific gem in gemfile" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + end + + it "prints information about the current gem" do + bundle "info rails" + expect(out).to include "* rails (2.3.2) +\tSummary: This is just a fake gem for testing +\tHomepage: http://example.com" + expect(out).to match(%r{Path\: .*\/rails\-2\.3\.2}) + end + + context "given a gem that is not installed" do + it "prints missing gem error" do + bundle "info foo" + expect(out).to eq "Could not find gem 'foo'." + end + end + + context "given a default gem shippped in ruby" do + it "prints information about the default gem", :if => (RUBY_VERSION >= "2.0") do + bundle "info rdoc" + expect(out).to include("* rdoc") + expect(out).to include("Default Gem: yes") + end + end + + context "when gem does not have homepage" do + before do + build_repo1 do + build_gem "rails", "2.3.2" do |s| + s.executables = "rails" + s.summary = "Just another test gem" + end + end + end + + it "excludes the homepage field from the output" do + expect(out).to_not include("Homepage:") + end + end + + context "given --path option" do + it "prints the path to the gem" do + bundle "info rails" + expect(out).to match(%r{.*\/rails\-2\.3\.2}) + end + end + end +end diff --git a/spec/commands/init_spec.rb b/spec/commands/init_spec.rb index 70d1143055..03a46a8bfa 100644 --- a/spec/commands/init_spec.rb +++ b/spec/commands/init_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle init" do +RSpec.describe "bundle init" do it "generates a Gemfile" do bundle :init expect(bundled_app("Gemfile")).to exist diff --git a/spec/commands/inject_spec.rb b/spec/commands/inject_spec.rb index d0112915ca..dd5e22498b 100644 --- a/spec/commands/inject_spec.rb +++ b/spec/commands/inject_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle inject" do +RSpec.describe "bundle inject" do before :each do gemfile <<-G source "file://#{gem_repo1}" @@ -42,6 +42,16 @@ describe "bundle inject" do end end + context "incorrect arguments" do + it "fails when more than 2 arguments are passed" do + bundle "inject gem_name 1 v" + expect(out).to eq(<<-E.strip) +ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"] +Usage: "bundle inject GEM VERSION" + E + end + end + context "when frozen" do before do bundle "install" diff --git a/spec/commands/install_spec.rb b/spec/commands/install_spec.rb index eb78ced86e..49eee01910 100644 --- a/spec/commands/install_spec.rb +++ b/spec/commands/install_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with gem sources" do +RSpec.describe "bundle install with gem sources" do describe "the simple case" do it "prints output and returns if no dependencies are specified" do gemfile <<-G @@ -216,7 +216,7 @@ describe "bundle install with gem sources" do G run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" - expect(out).to eq("1.0.0 #{Gem::Platform.local}") + expect(out).to eq("1.0.0 #{Bundler.local_platform}") end it "falls back on plain ruby" do @@ -314,14 +314,14 @@ describe "bundle install with gem sources" do end it "gracefully handles error when rubygems server is unavailable" do - install_gemfile <<-G + install_gemfile <<-G, :artifice => nil source "file://#{gem_repo1}" source "http://localhost:9384" gem 'foo' G - bundle :install + bundle :install, :artifice => nil expect(out).to include("Could not fetch specs from http://localhost:9384/") expect(out).not_to include("file://") end diff --git a/spec/commands/licenses_spec.rb b/spec/commands/licenses_spec.rb index dd9f261eb3..0ee1a46945 100644 --- a/spec/commands/licenses_spec.rb +++ b/spec/commands/licenses_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle licenses" do +RSpec.describe "bundle licenses" do before :each do install_gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/commands/lock_spec.rb b/spec/commands/lock_spec.rb index fc605dc5a6..52c281a6ae 100644 --- a/spec/commands/lock_spec.rb +++ b/spec/commands/lock_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle lock" do +RSpec.describe "bundle lock" do def strip_lockfile(lockfile) strip_whitespace(lockfile).sub(/\n\Z/, "") end @@ -10,9 +10,11 @@ describe "bundle lock" do strip_lockfile bundled_app(file).read end + let(:repo) { gem_repo1 } + before :each do gemfile <<-G - source "file://#{gem_repo1}" + source "file://#{repo}" gem "rails" gem "with_license" gem "foo" @@ -20,7 +22,7 @@ describe "bundle lock" do @lockfile = strip_lockfile <<-L GEM - remote: file:#{gem_repo1}/ + remote: file:#{repo}/ specs: actionmailer (2.3.2) activesupport (= 2.3.2) @@ -77,7 +79,7 @@ describe "bundle lock" do it "writes a lockfile when there is an outdated lockfile using --update" do lockfile @lockfile.gsub("2.3.2", "2.3.1") - bundle "lock --update" + bundle! "lock --update" expect(read_lockfile).to eq(@lockfile) end @@ -85,7 +87,7 @@ describe "bundle lock" do it "does not fetch remote specs when using the --local option" do bundle "lock --update --local" - expect(out).to include("available on this machine.") + expect(out).to include("sources listed in your Gemfile") end it "writes to a custom location using --lockfile" do @@ -104,6 +106,54 @@ describe "bundle lock" do expect(read_lockfile).to eq(@lockfile) end + # see update_spec for more coverage on same options. logic is shared so it's not necessary + # to repeat coverage here. + context "conservative updates" do + before do + build_repo4 do + build_gem "foo", %w(1.4.3 1.4.4) do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w(1.4.5 1.5.0) do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w(1.5.1) do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0) + build_gem "qux", %w(1.0.0 1.0.1 1.1.0 2.0.0) + end + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G + end + + it "single gem updates dependent gem to minor" do + bundle "lock --update foo --patch" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(foo-1.4.5 bar-2.1.1 qux-1.0.0).sort) + end + + it "minor preferred with strict" do + bundle "lock --update --minor --strict" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w(foo-1.5.0 bar-2.1.1 qux-1.1.0).sort) + end + end + it "supports adding new platforms" do bundle! "lock --add-platform java x86-mingw32" @@ -111,6 +161,12 @@ describe "bundle lock" do expect(lockfile.platforms).to eq([java, local, mingw]) end + it "supports adding the `ruby` platform" do + bundle! "lock --add-platform ruby" + lockfile = Bundler::LockfileParser.new(read_lockfile) + expect(lockfile.platforms).to eq([local, "ruby"].uniq) + end + it "warns when adding an unknown platform" do bundle "lock --add-platform foobarbaz" expect(out).to include("The platform `foobarbaz` is unknown to RubyGems and adding it will likely lead to resolution errors") @@ -223,4 +279,28 @@ describe "bundle lock" do #{Bundler::VERSION} G end + + context "when an update is available" do + let(:repo) { gem_repo2 } + + before do + lockfile(@lockfile) + build_repo2 do + build_gem "foo", "2.0" + end + end + + it "does not implicitly update" do + bundle! "lock" + + expect(read_lockfile).to eq(@lockfile) + end + + it "accounts for changes in the gemfile" do + gemfile gemfile.gsub('"foo"', '"foo", "2.0"') + bundle! "lock" + + expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)")) + end + end end diff --git a/spec/commands/newgem_spec.rb b/spec/commands/newgem_spec.rb index 6e80aa7a60..6ce19124f5 100644 --- a/spec/commands/newgem_spec.rb +++ b/spec/commands/newgem_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle gem" do +RSpec.describe "bundle gem" do def reset! super global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false" @@ -17,7 +17,7 @@ describe "bundle gem" do end def execute_bundle_gem(gem_name, flag = "", to_remove_push_guard = true) - bundle "gem #{gem_name} #{flag}" + bundle! "gem #{gem_name} #{flag}" remove_push_guard(gem_name) if to_remove_push_guard # reset gemspec cache for each test because of commit 3d4163a Bundler.clear_gemspec_cache @@ -25,6 +25,7 @@ describe "bundle gem" do def gem_skeleton_assertions(gem_name) expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist + expect(bundled_app("#{gem_name}/README.md")).to exist expect(bundled_app("#{gem_name}/Gemfile")).to exist expect(bundled_app("#{gem_name}/Rakefile")).to exist expect(bundled_app("#{gem_name}/lib/test/gem.rb")).to exist @@ -36,6 +37,8 @@ describe "bundle gem" do [user] name = "Bundler User" email = user@example.com + [github] + user = bundleuser EOF @git_config_location = ENV["GIT_CONFIG"] path = "#{File.expand_path("../../tmp", File.dirname(__FILE__))}/test_git_config.txt" @@ -100,6 +103,13 @@ describe "bundle gem" do gem_skeleton_assertions(gem_name) expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to exist end + + describe "README additions" do + it "generates the README with a section for the Code of Conduct" do + expect(bundled_app("test-gem/README.md").read).to include("## Code of Conduct") + expect(bundled_app("test-gem/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md") + end + end end shared_examples_for "--no-coc flag" do @@ -110,25 +120,33 @@ describe "bundle gem" do gem_skeleton_assertions(gem_name) expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to_not exist end + + describe "README additions" do + it "generates the README without a section for the Code of Conduct" do + expect(bundled_app("test-gem/README.md").read).not_to include("## Code of Conduct") + expect(bundled_app("test-gem/README.md").read).not_to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md") + end + end end context "README.md" do let(:gem_name) { "test_gem" } let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) } - context "git config user.name present" do + context "git config github.user present" do before do execute_bundle_gem(gem_name) end it "contribute URL set to git username" do expect(bundled_app("test_gem/README.md").read).not_to include("[USERNAME]") + expect(bundled_app("test_gem/README.md").read).to include("github.com/bundleuser") end end - context "git config user.name is absent" do + context "git config github.user is absent" do before do - `git config --unset user.name` + sys_exec("git config --unset github.user") reset! in_app_root bundle "gem #{gem_name}" @@ -137,10 +155,42 @@ describe "bundle gem" do it "contribute URL set to [USERNAME]" do expect(bundled_app("test_gem/README.md").read).to include("[USERNAME]") + expect(bundled_app("test_gem/README.md").read).not_to include("github.com/bundleuser") end end end + it "creates a new git repository" do + in_app_root + bundle "gem test_gem" + expect(bundled_app("test_gem/.git")).to exist + end + + context "when git is not avaiable" do + let(:gem_name) { "test_gem" } + + # This spec cannot have `git` avaiable in the test env + before do + bundle_bin = File.expand_path("../../../exe/bundle", __FILE__) + load_paths = [lib, spec] + load_path_str = "-I#{load_paths.join(File::PATH_SEPARATOR)}" + + sys_exec "PATH=\"\" #{Gem.ruby} #{load_path_str} #{bundle_bin} gem #{gem_name}" + end + + it "creates the gem without the need for git" do + expect(bundled_app("#{gem_name}/README.md")).to exist + end + + it "doesn't create a git repo" do + expect(bundled_app("#{gem_name}/.git")).to_not exist + end + + it "doesn't create a .gitignore file" do + expect(bundled_app("#{gem_name}/.gitignore")).to_not exist + end + end + it "generates a valid gemspec" do system_gems ["rake-10.0.2"] @@ -407,11 +457,11 @@ describe "bundle gem" do end it "requires 'test-gem'" do - expect(bundled_app("test_gem/test/test_helper.rb").read).to include("require 'test_gem'") + expect(bundled_app("test_gem/test/test_helper.rb").read).to include(%(require "test_gem")) end it "requires 'minitest_helper'" do - expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include("require 'test_helper'") + expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include(%(require "test_helper")) end it "creates a default test which fails" do @@ -435,7 +485,7 @@ describe "bundle gem" do Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" - t.test_files = FileList['test/**/*_test.rb'] + t.test_files = FileList["test/**/*_test.rb"] end task :default => :test @@ -648,11 +698,11 @@ describe "bundle gem" do end it "requires 'test/gem'" do - expect(bundled_app("test-gem/test/test_helper.rb").read).to match(%r{require 'test/gem'}) + expect(bundled_app("test-gem/test/test_helper.rb").read).to match(%r{require "test/gem"}) end it "requires 'test_helper'" do - expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/require 'test_helper'/) + expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/require "test_helper"/) end it "creates a default test which fails" do @@ -667,7 +717,7 @@ describe "bundle gem" do Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" - t.test_files = FileList['test/**/*_test.rb'] + t.test_files = FileList["test/**/*_test.rb"] end task :default => :test @@ -733,6 +783,29 @@ describe "bundle gem" do expect(bundled_app("a--a/a--a.gemspec")).to exist end + + it "fails gracefully with a ." do + bundle "gem foo.gemspec" + expect(out).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name") + end + + it "fails gracefully with a ^" do + bundle "gem ^" + expect(out).to end_with("Invalid gem name ^ -- `^` is an invalid constant name") + end + + it "fails gracefully with a space" do + bundle "gem 'foo bar'" + expect(out).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name") + end + + it "fails gracefully when multiple names are passed" do + bundle "gem foo bar baz" + expect(out).to eq(<<-E.strip) +ERROR: "bundle gem" was called with arguments ["foo", "bar", "baz"] +Usage: "bundle gem GEM [OPTIONS]" + E + end end describe "#ensure_safe_gem_name" do @@ -812,4 +885,26 @@ describe "bundle gem" do expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist end end + + context "on conflicts with a previously created file" do + it "should fail gracefully" do + in_app_root do + FileUtils.touch("conflict-foobar") + end + output = bundle "gem conflict-foobar" + expect(output).to include("Errno::EEXIST") + expect(exitstatus).to eql(32) if exitstatus + end + end + + context "on conflicts with a previously created directory" do + it "should fail gracefully" do + in_app_root do + FileUtils.mkdir_p("conflict-foobar/Gemfile") + end + output = bundle "gem conflict-foobar" + expect(output).to include("Errno::EISDIR") + expect(exitstatus).to eql(32) if exitstatus + end + end end diff --git a/spec/commands/open_spec.rb b/spec/commands/open_spec.rb index 5507398382..6872e859d2 100644 --- a/spec/commands/open_spec.rb +++ b/spec/commands/open_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle open" do +RSpec.describe "bundle open" do before :each do install_gemfile <<-G source "file://#{gem_repo1}" @@ -67,6 +67,13 @@ describe "bundle open" do expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/) end + it "allows selecting exit from many match gems" do + env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle! "open active", :env => env do |input, _, _| + input.puts "0" + end + end + it "performs an automatic bundle install" do gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/commands/outdated_spec.rb b/spec/commands/outdated_spec.rb index 6420c28ac7..b927a0be2b 100644 --- a/spec/commands/outdated_spec.rb +++ b/spec/commands/outdated_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle outdated" do +RSpec.describe "bundle outdated" do before :each do build_repo2 do build_git "foo", :path => lib_path("foo") @@ -72,7 +72,136 @@ describe "bundle outdated" do end end + describe "with --group option" do + def test_group_option(group = nil, gems_list_size = 1) + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem "duradura", '7.0' + gem 'activesupport', '2.3.5' + end + G + + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "terranova", "9" + build_gem "duradura", "8.0" + end + + bundle "outdated --group #{group}" + + # Gem names are one per-line, between "*" and their parenthesized version. + gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact + expect(gem_list).to eq(gem_list.sort) + expect(gem_list.size).to eq gems_list_size + end + + it "not outdated gems" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + + bundle "outdated --group" + expect(out).to include("Bundle up to date!") + end + + it "returns a sorted list of outdated gems from one group => 'default'" do + test_group_option("default") + + expect(out).to include("===== Group default =====") + expect(out).to include("terranova (") + + expect(out).not_to include("===== Group development, test =====") + expect(out).not_to include("activesupport") + expect(out).not_to include("duradura") + end + + it "returns a sorted list of outdated gems from one group => 'development'" do + test_group_option("development", 2) + + expect(out).not_to include("===== Group default =====") + expect(out).not_to include("terranova (") + + expect(out).to include("===== Group development, test =====") + expect(out).to include("activesupport") + expect(out).to include("duradura") + end + end + + describe "with --groups option" do + it "not outdated gems" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + + bundle "outdated --groups" + expect(out).to include("Bundle up to date!") + end + + it "returns a sorted list of outdated gems by groups" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "terranova", "9" + build_gem "duradura", "8.0" + end + + bundle "outdated --groups" + expect(out).to include("===== Group default =====") + expect(out).to include("terranova (newest 9, installed 8, requested = 8)") + expect(out).to include("===== Group development, test =====") + expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)") + expect(out).to include("duradura (newest 8.0, installed 7.0, requested = 7.0)") + + expect(out).not_to include("weakling (") + + # TODO: check gems order inside the group + end + end + describe "with --local option" do + it "uses local cache to return a list of outdated gems" do + update_repo2 do + build_gem "activesupport", "2.3.4" + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.4" + G + + bundle "outdated --local" + + expect(out).to include("activesupport (newest 2.3.5, installed 2.3.4, requested = 2.3.4)") + end + it "doesn't hit repo2" do FileUtils.rm_rf(gem_repo2) @@ -195,6 +324,62 @@ describe "bundle outdated" do expect(out).to_not include("rack (1.2") end + + describe "and filter options" do + it "only reports gems that match requirement and patch filter level" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "~> 2.3" + gem "weakling", ">= 0.0.1" + G + + update_repo2 do + build_gem "activesupport", %w(2.4.0 3.0.0) + build_gem "weakling", "0.0.5" + end + + bundle "outdated --strict --filter-patch" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 0.0.5, installed 0.0.3") + end + + it "only reports gems that match requirement and minor filter level" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "~> 2.3" + gem "weakling", ">= 0.0.1" + G + + update_repo2 do + build_gem "activesupport", %w(2.3.9) + build_gem "weakling", "0.1.5" + end + + bundle "outdated --strict --filter-minor" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 0.1.5, installed 0.0.3") + end + + it "only reports gems that match requirement and major filter level" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "~> 2.3" + gem "weakling", ">= 0.0.1" + G + + update_repo2 do + build_gem "activesupport", %w(2.4.0 2.5.0) + build_gem "weakling", "1.1.5" + end + + bundle "outdated --strict --filter-major" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 1.1.5, installed 0.0.3") + end + end end describe "with invalid gem name" do @@ -257,6 +442,33 @@ describe "bundle outdated" do end end + context "update available for a gem on the same platform while multiple platforms used for gem" do + it "reports that updates are available if the Ruby platform is used" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] + G + + bundle "outdated" + expect(out).to include("Bundle up to date!") + end + + it "reports that updates are available if the JRuby platform is used" do + simulate_ruby_engine "jruby", "1.6.7" do + simulate_platform "jruby" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] + G + + bundle "outdated" + expect(out).to include("Outdated gems included in the bundle:") + expect(out).to include("laduradura (newest 5.15.3, installed 5.15.2, requested = 5.15.2)") + end + end + end + end + shared_examples_for "version update is detected" do it "reports that a gem has a newer version" do subject @@ -302,7 +514,7 @@ describe "bundle outdated" do shared_examples_for "no version updates are detected" do it "does not detect any version updates" do subject - expect(out).to include("Bundle up to date!") + expect(out).to include("updates to display.") expect(out).to_not include("ERROR REPORT TEMPLATE") expect(out).to_not include("activesupport (newest") expect(out).to_not include("weakling (newest") @@ -342,59 +554,163 @@ describe "bundle outdated" do it_behaves_like "no version updates are detected" end - describe "with --major option" do - subject { bundle "outdated --major" } + describe "with --filter-major option" do + subject { bundle "outdated --filter-major" } it_behaves_like "major version updates are detected" it_behaves_like "minor version is ignored" it_behaves_like "patch version is ignored" end - describe "with --minor option" do - subject { bundle "outdated --minor" } + describe "with --filter-minor option" do + subject { bundle "outdated --filter-minor" } it_behaves_like "minor version updates are detected" it_behaves_like "major version is ignored" it_behaves_like "patch version is ignored" end - describe "with --patch option" do - subject { bundle "outdated --patch" } + describe "with --filter-patch option" do + subject { bundle "outdated --filter-patch" } it_behaves_like "patch version updates are detected" it_behaves_like "major version is ignored" it_behaves_like "minor version is ignored" end - describe "with --minor --patch options" do - subject { bundle "outdated --minor --patch" } + describe "with --filter-minor --filter-patch options" do + subject { bundle "outdated --filter-minor --filter-patch" } it_behaves_like "minor version updates are detected" it_behaves_like "patch version updates are detected" it_behaves_like "major version is ignored" end - describe "with --major --minor options" do - subject { bundle "outdated --major --minor" } + describe "with --filter-major --filter-minor options" do + subject { bundle "outdated --filter-major --filter-minor" } it_behaves_like "major version updates are detected" it_behaves_like "minor version updates are detected" it_behaves_like "patch version is ignored" end - describe "with --major --patch options" do - subject { bundle "outdated --major --patch" } + describe "with --filter-major --filter-patch options" do + subject { bundle "outdated --filter-major --filter-patch" } it_behaves_like "major version updates are detected" it_behaves_like "patch version updates are detected" it_behaves_like "minor version is ignored" end - describe "with --major --minor --patch options" do - subject { bundle "outdated --major --minor --patch" } + describe "with --filter-major --filter-minor --filter-patch options" do + subject { bundle "outdated --filter-major --filter-minor --filter-patch" } it_behaves_like "major version updates are detected" it_behaves_like "minor version updates are detected" it_behaves_like "patch version updates are detected" end + + context "conservative updates" do + context "without update-strict" do + before do + build_repo4 do + build_gem "patch", %w(1.0.0 1.0.1) + build_gem "minor", %w(1.0.0 1.0.1 1.1.0) + build_gem "major", %w(1.0.0 1.0.1 1.1.0 2.0.0) + end + + # establish a lockfile set to 1.0.0 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'patch', '1.0.0' + gem 'minor', '1.0.0' + gem 'major', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'patch' + gem 'minor' + gem 'major' + G + end + + it "shows nothing when patching and filtering to minor" do + bundle "outdated --patch --filter-minor" + + expect(out).to include("No minor updates to display.") + expect(out).not_to include("patch (newest") + expect(out).not_to include("minor (newest") + expect(out).not_to include("major (newest") + end + + it "shows all gems when patching and filtering to patch" do + bundle "outdated --patch --filter-patch" + + expect(out).to include("patch (newest 1.0.1") + expect(out).to include("minor (newest 1.0.1") + expect(out).to include("major (newest 1.0.1") + end + + it "shows minor and major when updating to minor and filtering to patch and minor" do + bundle "outdated --minor --filter-minor" + + expect(out).not_to include("patch (newest") + expect(out).to include("minor (newest 1.1.0") + expect(out).to include("major (newest 1.1.0") + end + + it "shows minor when updating to major and filtering to minor with parseable" do + bundle "outdated --major --filter-minor --parseable" + + expect(out).not_to include("patch (newest") + expect(out).to include("minor (newest") + expect(out).not_to include("major (newest") + end + end + + context "with update-strict" do + before do + build_repo4 do + build_gem "foo", %w(1.4.3 1.4.4) do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w(1.4.5 1.5.0) do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w(1.5.1) do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0) + build_gem "qux", %w(1.0.0 1.1.0 2.0.0) + end + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G + end + + it "shows gems with update-strict updating to patch and filtering to patch" do + bundle "outdated --patch --update-strict --filter-patch" + + expect(out).to include("foo (newest 1.4.4") + expect(out).to include("bar (newest 2.0.5") + expect(out).not_to include("qux (newest") + end + end + end end diff --git a/spec/commands/package_spec.rb b/spec/commands/package_spec.rb index a72b94a0b9..86c09db3ca 100644 --- a/spec/commands/package_spec.rb +++ b/spec/commands/package_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle package" do +RSpec.describe "bundle package" do context "with --gemfile" do it "finds the gemfile" do gemfile bundled_app("NotGemfile"), <<-G @@ -222,7 +222,7 @@ describe "bundle package" do end end -describe "bundle install with gem sources" do +RSpec.describe "bundle install with gem sources" do describe "when cached and locked" do it "does not hit the remote at all" do build_repo2 diff --git a/spec/commands/show_spec.rb b/spec/commands/show_spec.rb index 84352c5427..45af035959 100644 --- a/spec/commands/show_spec.rb +++ b/spec/commands/show_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle show" do +RSpec.describe "bundle show" do context "with a standard Gemfile" do before :each do install_gemfile <<-G @@ -161,4 +161,31 @@ describe "bundle show" do expect(out).to include("Could not find gem '#{invalid_regexp}'.") end end + + context "--outdated option" do + # Regression test for https://github.com/bundler/bundler/issues/5375 + before do + build_repo2 + end + + it "doesn't update gems to newer versions" do + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rails" + G + + expect(the_bundle).to include_gem("rails 2.3.2") + + update_repo2 do + build_gem "rails", "3.0.0" do |s| + s.executables = "rails" + end + end + + bundle! "show --outdated" + + bundle! "install" + expect(the_bundle).to include_gem("rails 2.3.2") + end + end end diff --git a/spec/commands/update_spec.rb b/spec/commands/update_spec.rb index 8a9867d1e9..4992e428da 100644 --- a/spec/commands/update_spec.rb +++ b/spec/commands/update_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle update" do +RSpec.describe "bundle update" do before :each do build_repo2 @@ -159,8 +159,16 @@ describe "bundle update" do bundle "update" expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m) + expect(out).to match(/freeze \nby running `bundle install --no-deployment`./m) expect(exitstatus).not_to eq(0) if exitstatus end + + it "should suggest different command when frozen is set globally" do + bundler "config --global frozen 1" + bundle "update" + expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m) + expect(out).to match(/freeze \nby running `bundle config --delete frozen`./m) + end end describe "with --source option" do @@ -254,7 +262,7 @@ describe "bundle update" do end end -describe "bundle update in more complicated situations" do +RSpec.describe "bundle update in more complicated situations" do before :each do build_repo2 end @@ -295,7 +303,7 @@ describe "bundle update in more complicated situations" do end end -describe "bundle update without a Gemfile.lock" do +RSpec.describe "bundle update without a Gemfile.lock" do it "should not explode" do build_repo2 @@ -311,7 +319,7 @@ describe "bundle update without a Gemfile.lock" do end end -describe "bundle update when a gem depends on a newer version of bundler" do +RSpec.describe "bundle update when a gem depends on a newer version of bundler" do before(:each) do build_repo2 do build_gem "rails", "3.0.1" do |s| @@ -338,7 +346,7 @@ describe "bundle update when a gem depends on a newer version of bundler" do end end -describe "bundle update" do +RSpec.describe "bundle update" do it "shows the previous version of the gem when updated from rubygems source" do build_repo2 @@ -370,7 +378,7 @@ describe "bundle update" do end end -describe "bundle update --ruby" do +RSpec.describe "bundle update --ruby" do before do install_gemfile <<-G ::RUBY_VERSION = '2.1.3' @@ -480,92 +488,170 @@ describe "bundle update --ruby" do end # these specs are slow and focus on integration and therefore are not exhaustive. unit specs elsewhere handle that. -describe "bundle update conservative" do - before do - build_repo4 do - build_gem "foo", %w(1.4.3 1.4.4) do |s| - s.add_dependency "bar", "~> 2.0" - end - build_gem "foo", %w(1.4.5 1.5.0) do |s| - s.add_dependency "bar", "~> 2.1" - end - build_gem "foo", %w(1.5.1) do |s| - s.add_dependency "bar", "~> 3.0" +RSpec.describe "bundle update conservative" do + context "patch and minor options" do + before do + build_repo4 do + build_gem "foo", %w(1.4.3 1.4.4) do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w(1.4.5 1.5.0) do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w(1.5.1) do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0) + build_gem "qux", %w(1.0.0 1.0.1 1.1.0 2.0.0) end - build_gem "bar", %w(2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0) - build_gem "qux", %w(1.0.0 1.0.1 1.1.0 2.0.0) + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G end - # establish a lockfile set to 1.4.3 - install_gemfile <<-G - source "file://#{gem_repo4}" - gem 'foo', '1.4.3' - gem 'bar', '2.0.3' - gem 'qux', '1.0.0' - G + context "patch preferred" do + it "single gem updates dependent gem to minor" do + bundle "update --patch foo" - # remove 1.4.3 requirement and bar altogether - # to setup update specs below - gemfile <<-G - source "file://#{gem_repo4}" - gem 'foo' - gem 'qux' - G - end + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0" + end - context "patch preferred" do - it "single gem updates dependent gem to minor" do - bundle "update --patch foo" + it "update all" do + bundle "update --patch" - expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0" + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" + end end - it "update all" do - bundle "update --patch" + context "minor preferred" do + it "single gem updates dependent gem to major" do + bundle "update --minor foo" - expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" + expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0" + end end - it "warns on minor or major increment elsewhere" ## include in prior test + context "strict" do + it "patch preferred" do + bundle "update --patch foo bar --strict" + + expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0" + end + + it "minor preferred" do + bundle "update --minor --strict" + + expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" + end + end end - context "minor preferred" do - it "single gem updates dependent gem to major" do - bundle "update --minor foo" + context "eager unlocking" do + before do + build_repo4 do + build_gem "isolated_owner", %w(1.0.1 1.0.2) do |s| + s.add_dependency "isolated_dep", "~> 2.0" + end + build_gem "isolated_dep", %w(2.0.1 2.0.2) + + build_gem "shared_owner_a", %w(3.0.1 3.0.2) do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_owner_b", %w(4.0.1 4.0.2) do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_dep", %w(5.0.1 5.0.2) + end + + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner' - expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0" + gem 'shared_owner_a' + gem 'shared_owner_b' + G + + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + isolated_dep (2.0.1) + isolated_owner (1.0.1) + isolated_dep (~> 2.0) + shared_dep (5.0.1) + shared_owner_a (3.0.1) + shared_dep (~> 5.0) + shared_owner_b (4.0.1) + shared_dep (~> 5.0) + + PLATFORMS + ruby + + DEPENDENCIES + shared_owner_a + shared_owner_b + isolated_owner + + BUNDLED WITH + 1.13.0 + L end - it "warns on major increment elsewhere" ## include in prior test + it "should eagerly unlock isolated dependency" do + bundle "update isolated_owner" - it "warns when something unlocked doesn't update at all" - end + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.1", "shared_owner_b 4.0.1" + end + + it "should eagerly unlock shared dependency" do + bundle "update shared_owner_a" + + expect(the_bundle).to include_gems "isolated_owner 1.0.1", "isolated_dep 2.0.1", "shared_dep 5.0.2", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end - context "strict" do - it "patch preferred" do - bundle "update --patch foo bar --strict" + it "should not eagerly unlock with --conservative" do + bundle "update --conservative shared_owner_a isolated_owner" - expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0" + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" end - it "minor preferred" do - bundle "update --minor --strict" + it "should match bundle install conservative update behavior when not eagerly unlocking" do + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner', '1.0.2' + + gem 'shared_owner_a', '3.0.2' + gem 'shared_owner_b' + G + + bundle "install" - expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" end end context "error handling" do + before do + gemfile "" + end + it "raises if too many flags are provided" do bundle "update --patch --minor" expect(out).to eq "Provide only one of the following options: minor, patch" end end - - context "other commands" do - it "Installer could support --dry-run flag for install and update" - - it "outdated should conform its flags to the resolver flags" - end end diff --git a/spec/commands/viz_spec.rb b/spec/commands/viz_spec.rb index f3fab9272c..123e7bef62 100644 --- a/spec/commands/viz_spec.rb +++ b/spec/commands/viz_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle viz", :ruby => "1.9.3", :if => Bundler.which("dot") do +RSpec.describe "bundle viz", :ruby => "1.9.3", :if => Bundler.which("dot") do let(:graphviz_lib) do graphviz_glob = base_system_gems.join("gems/ruby-graphviz*/lib") Dir[graphviz_glob].first diff --git a/spec/install/allow_offline_install_spec.rb b/spec/install/allow_offline_install_spec.rb index 44100ca97b..1bca055c9f 100644 --- a/spec/install/allow_offline_install_spec.rb +++ b/spec/install/allow_offline_install_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with :allow_offline_install" do +RSpec.describe "bundle install with :allow_offline_install" do before do bundle "config allow_offline_install true" end diff --git a/spec/install/binstubs_spec.rb b/spec/install/binstubs_spec.rb index a7e0b847cc..a1a9ab167d 100644 --- a/spec/install/binstubs_spec.rb +++ b/spec/install/binstubs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do describe "when system_bindir is set" do # On OS X, Gem.bindir defaults to /usr/bin, so system_bindir is useful if # you want to avoid sudo installs for system gems with OS X's default ruby diff --git a/spec/install/bundler_spec.rb b/spec/install/bundler_spec.rb index 9f06a48086..c1ce57e60e 100644 --- a/spec/install/bundler_spec.rb +++ b/spec/install/bundler_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do describe "with bundler dependencies" do before(:each) do build_repo2 do @@ -44,17 +44,14 @@ describe "bundle install" do In Gemfile: bundler (= 0.9.2) - rails (= 3.0) was resolved to 3.0, which depends on - bundler (>= 0.9.0.pre) - Current Bundler version: bundler (#{Bundler::VERSION}) This Gemfile requires a different version of Bundler. Perhaps you need to update Bundler by running `gem install bundler`? - Could not find gem 'bundler (= 0.9.2)', which is required by gem 'rails (= 3.0)', in any of the sources. + Could not find gem 'bundler (= 0.9.2)' in any of the sources E - expect(out).to include(nice_error) + expect(out).to eq(nice_error) end it "works for gems with multiple versions in its dependencies" do diff --git a/spec/install/deploy_spec.rb b/spec/install/deploy_spec.rb index c73cff5e80..4a4f6e912f 100644 --- a/spec/install/deploy_spec.rb +++ b/spec/install/deploy_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "install with --deployment or --frozen" do +RSpec.describe "install with --deployment or --frozen" do before do gemfile <<-G source "file://#{gem_repo1}" @@ -22,15 +22,15 @@ describe "install with --deployment or --frozen" do it "disallows --deployment --system" do bundle "install --deployment --system" expect(out).to include("You have specified both --deployment") - expect(out).to include("Please choose.") + expect(out).to include("Please choose only one option") expect(exitstatus).to eq(15) if exitstatus end it "disallows --deployment --path --system" do bundle "install --deployment --path . --system" - expect(out).to include("You have specified both a path to install your gems to") - expect(out).to include("You have specified both --deployment") - expect(out).to include("Please choose.") + expect(out).to include("You have specified both --path") + expect(out).to include("as well as --system") + expect(out).to include("Please choose only one option") expect(exitstatus).to eq(15) if exitstatus end @@ -77,10 +77,8 @@ describe "install with --deployment or --frozen" do gem "bar", :path => "#{lib_path("nested")}" G - bundle :install - bundle "install --deployment" - - expect(exitstatus).to eq(0) if exitstatus + bundle! :install + bundle! "install --deployment" end it "works when there are credentials in the source URL" do @@ -267,22 +265,21 @@ describe "install with --deployment or --frozen" do context "with path in Gemfile and packed" do it "works fine after bundle package and bundle install --local" do build_lib "foo", :path => lib_path("foo") - install_gemfile <<-G - gem "foo", :path => "#{lib_path("foo")}" + install_gemfile! <<-G + gem "foo", :path => "#{lib_path("foo")}" G - bundle :install + bundle! :install expect(the_bundle).to include_gems "foo 1.0" - bundle "package --all" + bundle! "package --all" expect(bundled_app("vendor/cache/foo")).to be_directory - bundle "install --local" + bundle! "install --local" expect(out).to include("Using foo 1.0 from source at") expect(out).to include("vendor/cache/foo") - expect(exitstatus).to eq(0) if exitstatus simulate_new_machine - bundle "install --deployment" + bundle! "install --deployment --verbose" expect(out).not_to include("You are trying to install in deployment mode after changing your Gemfile") expect(out).not_to include("You have added to the Gemfile") expect(out).not_to include("You have deleted from the Gemfile") diff --git a/spec/install/failure_spec.rb b/spec/install/failure_spec.rb new file mode 100644 index 0000000000..738b2cf1bd --- /dev/null +++ b/spec/install/failure_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe "bundle install" do + context "installing a gem fails" do + it "prints out why that gem was being installed" do + build_repo2 do + build_gem "activesupport", "2.3.2" do |s| + s.extensions << "Rakefile" + s.write "Rakefile", <<-RUBY + task :default do + abort "make installing activesupport-2.3.2 fail" + end + RUBY + end + end + + install_gemfile <<-G + source "file:#{gem_repo2}" + gem "rails" + G + expect(out).to end_with(<<-M.strip) +An error occurred while installing activesupport (2.3.2), and Bundler cannot continue. +Make sure that `gem install activesupport -v '2.3.2'` succeeds before bundling. + +In Gemfile: + rails was resolved to 2.3.2, which depends on + actionmailer was resolved to 2.3.2, which depends on + activesupport + M + end + end +end diff --git a/spec/install/force_spec.rb b/spec/install/force_spec.rb index 2027660cba..6d852b3bf1 100644 --- a/spec/install/force_spec.rb +++ b/spec/install/force_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do describe "with --force" do before :each do gemfile <<-G diff --git a/spec/install/gemfile/eval_gemfile_spec.rb b/spec/install/gemfile/eval_gemfile_spec.rb index 2660ac98c2..4f5a109e32 100644 --- a/spec/install/gemfile/eval_gemfile_spec.rb +++ b/spec/install/gemfile/eval_gemfile_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with gemfile that uses eval_gemfile" do +RSpec.describe "bundle install with gemfile that uses eval_gemfile" do before do build_lib("gunks", :path => bundled_app.join("gems/gunks")) do |s| s.name = "gunks" @@ -26,6 +26,24 @@ describe "bundle install with gemfile that uses eval_gemfile" do end end + context "eval-ed Gemfile has relative-path gems" do + before do + build_lib("a", :path => "gems/a") + create_file "nested/Gemfile-nested", <<-G + gem "a", :path => "../gems/a" + G + + gemfile <<-G + eval_gemfile "nested/Gemfile-nested" + G + end + + it "installs the path gem" do + bundle! :install + expect(the_bundle).to include_gem("a 1.0") + end + end + context "Gemfile uses gemspec paths after eval-ing a Gemfile" do before { create_file "other/Gemfile-other" } diff --git a/spec/install/gemfile/gemspec_spec.rb b/spec/install/gemfile/gemspec_spec.rb index 9a6bd5f1e8..e2534be1ad 100644 --- a/spec/install/gemfile/gemspec_spec.rb +++ b/spec/install/gemfile/gemspec_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install from an existing gemspec" do +RSpec.describe "bundle install from an existing gemspec" do before(:each) do build_gem "bar", :to_system => true build_gem "bar-dev", :to_system => true @@ -131,6 +131,55 @@ describe "bundle install from an existing gemspec" do end end + it "should match a lockfile without needing to re-resolve" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "rack" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}' + G + + bundle! "install", :verbose => true + expect(out).to include("Found no changes, using resolution from the lockfile") + end + + it "should match a lockfile without needing to re-resolve with development dependencies" do + simulate_platform java + + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "rack" + s.add_development_dependency "thin" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}' + G + + bundle! "install", :verbose => true + expect(out).to include("Found no changes, using resolution from the lockfile") + end + + it "should match a lockfile on non-ruby platforms with a transitive platform dependency" do + simulate_platform java + simulate_ruby_engine "jruby" + + build_lib("foo", :path => tmp.join("foo")) do |s| + s.add_dependency "platform_specific" + end + + install_gem "platform_specific-1.0-java" + + install_gemfile! <<-G + gemspec :path => '#{tmp.join("foo")}' + G + + bundle! "update --bundler", :verbose => true + expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA" + end + it "should evaluate the gemspec in its directory" do build_lib("foo", :path => tmp.join("foo")) File.open(tmp.join("foo/foo.gemspec"), "w") do |s| @@ -143,6 +192,26 @@ describe "bundle install from an existing gemspec" do expect(@err).not_to match(/ahh/) end + it "allows the gemspec to activate other gems" do + # see https://github.com/bundler/bundler/issues/5409 + # + # issue was caused by rubygems having an unresolved gem during a require, + # so emulate that + system_gems %w(rack-1.0.0 rack-0.9.1 rack-obama-1.0) + + build_lib("foo", :path => bundled_app) + gemspec = bundled_app("foo.gemspec").read + bundled_app("foo.gemspec").open("w") do |f| + f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack-obama' }" + end + + install_gemfile! <<-G + gemspec + G + + expect(the_bundle).to include_gem "foo 1.0" + end + it "allows conflicts" do build_lib("foo", :path => tmp.join("foo")) do |s| s.version = "1.0.0" @@ -162,6 +231,31 @@ describe "bundle install from an existing gemspec" do expect(the_bundle).to include_gems "foo 1.0.0" end + context "in deployment mode" do + context "when the lockfile was not updated after a change to the gemspec's dependencies" do + it "reports that installation failed" do + build_lib "cocoapods", :path => bundled_app do |s| + s.add_dependency "activesupport", ">= 1" + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec + G + + expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5") + + build_lib "cocoapods", :path => bundled_app do |s| + s.add_dependency "activesupport", ">= 1.0.1" + end + + bundle "install --deployment" + + expect(out).to include("changed") + end + end + end + context "when child gemspecs conflict with a released gemspec" do before do # build the "parent" gem that depends on another gem in the same repo @@ -409,4 +503,37 @@ describe "bundle install from an existing gemspec" do end end end + + context "with multiple platforms" do + before do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.version = "1.0.0" + s.add_development_dependency "rack" + s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby + end + end + + it "installs the ruby platform gemspec" do + simulate_platform "ruby" + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0" + end + + it "installs the ruby platform gemspec and skips dev deps with --without development" do + simulate_platform "ruby" + + install_gemfile! <<-G, :without => "development" + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + expect(the_bundle).to include_gem "foo 1.0.0" + expect(the_bundle).not_to include_gem "rack" + end + end end diff --git a/spec/install/gemfile/git_spec.rb b/spec/install/gemfile/git_spec.rb index f70ea5b43a..d2d46279aa 100644 --- a/spec/install/gemfile/git_spec.rb +++ b/spec/install/gemfile/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with git sources" do +RSpec.describe "bundle install with git sources" do describe "when floating on master" do before :each do build_git "foo" do |s| @@ -209,6 +209,94 @@ describe "bundle install with git sources" do end end + describe "when specifying a branch" do + let(:branch) { "branch" } + let(:repo) { build_git("foo").path } + before(:each) do + update_git("foo", :path => repo, :branch => branch) + end + + it "works" do + install_gemfile <<-G + git "#{repo}", :branch => #{branch.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + context "when the branch starts with a `#`" do + let(:branch) { "#149/redirect-url-fragment" } + it "works" do + install_gemfile <<-G + git "#{repo}", :branch => #{branch.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + + context "when the branch includes quotes" do + let(:branch) { %('") } + it "works" do + install_gemfile <<-G + git "#{repo}", :branch => #{branch.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + end + + describe "when specifying a tag" do + let(:tag) { "tag" } + let(:repo) { build_git("foo").path } + before(:each) do + update_git("foo", :path => repo, :tag => tag) + end + + it "works" do + install_gemfile <<-G + git "#{repo}", :tag => #{tag.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + + context "when the tag starts with a `#`" do + let(:tag) { "#149/redirect-url-fragment" } + it "works" do + install_gemfile <<-G + git "#{repo}", :tag => #{tag.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + + context "when the tag includes quotes" do + let(:tag) { %('") } + it "works" do + install_gemfile <<-G + git "#{repo}", :tag => #{tag.dump} do + gem "foo" + end + G + + expect(the_bundle).to include_gems("foo 1.0") + end + end + end + describe "when specifying local override" do it "uses the local repository instead of checking a new one out" do # We don't generate it because we actually don't need it @@ -997,7 +1085,12 @@ describe "bundle install with git sources" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - expect(out).to include("An error occurred while installing foo (1.0)") + expect(out).to end_with(<<-M.strip) +An error occurred while installing foo (1.0), and Bundler cannot continue. + +In Gemfile: + foo + M expect(out).not_to include("gem install foo") end @@ -1022,20 +1115,20 @@ describe "bundle install with git sources" do gem "foo", :git => "#{lib_path("foo-1.0")}" G - run <<-R + run! <<-R require 'foo' puts FOO R installed_time = out - expect(installed_time).to match(/\d+\.\d+/) + expect(installed_time).to match(/\A\d+\.\d+\z/) install_gemfile <<-G source "file://#{gem_repo1}" gem "foo", :git => "#{lib_path("foo-1.0")}" G - run <<-R + run! <<-R require 'foo' puts FOO R diff --git a/spec/install/gemfile/groups_spec.rb b/spec/install/gemfile/groups_spec.rb index cb052b6c7a..a3a5eeefdf 100644 --- a/spec/install/gemfile/groups_spec.rb +++ b/spec/install/gemfile/groups_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with groups" do +RSpec.describe "bundle install with groups" do describe "installing with no options" do before :each do install_gemfile <<-G diff --git a/spec/install/gemfile/path_spec.rb b/spec/install/gemfile/path_spec.rb index 0a73335225..a1c41aebbb 100644 --- a/spec/install/gemfile/path_spec.rb +++ b/spec/install/gemfile/path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with explicit source paths" do +RSpec.describe "bundle install with explicit source paths" do it "fetches gems" do build_lib "foo" diff --git a/spec/install/gemfile/platform_spec.rb b/spec/install/gemfile/platform_spec.rb index 58129bb313..c6eaec7ca6 100644 --- a/spec/install/gemfile/platform_spec.rb +++ b/spec/install/gemfile/platform_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install across platforms" do +RSpec.describe "bundle install across platforms" do it "maintains the same lockfile if all gems are compatible across platforms" do lockfile <<-G GEM @@ -88,6 +88,32 @@ describe "bundle install across platforms" do expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3" end + it "works with gems that have extra platform-specific runtime dependencies" do + simulate_platform x64_mac + + update_repo2 do + build_gem "facter", "2.4.6" + build_gem "facter", "2.4.6" do |s| + s.platform = "universal-darwin" + s.add_runtime_dependency "CFPropertyList" + end + build_gem "CFPropertyList" + end + + install_gemfile! <<-G + source "file://#{gem_repo2}" + + gem "facter" + G + + expect(out).to include "Unable to use the platform-specific (universal-darwin) version of facter (2.4.6) " \ + "because it has different dependencies from the ruby version. " \ + "To use the platform-specific version of the gem, run `bundle config specific_platform true` and install again." + + expect(the_bundle).to include_gem "facter 2.4.6" + expect(the_bundle).not_to include_gem "CFPropertyList" + end + it "fetches gems again after changing the version of Ruby" do gemfile <<-G source "file://#{gem_repo1}" @@ -105,7 +131,7 @@ describe "bundle install across platforms" do end end -describe "bundle install with platform conditionals" do +RSpec.describe "bundle install with platform conditionals" do it "installs gems tagged w/ the current platforms" do install_gemfile <<-G source "file://#{gem_repo1}" @@ -184,7 +210,7 @@ describe "bundle install with platform conditionals" do gemfile <<-G source "file://#{gem_repo1}" - gem "some_gem", platform: :rbx + gem "some_gem", :platform => :rbx G bundle "install --local" @@ -204,9 +230,26 @@ describe "bundle install with platform conditionals" do bundle "install --local" expect(out).not_to match(/Could not find gem 'some_gem/) end + + it "prints a helpful warning when a dependency is unused on any platform" do + simulate_platform "ruby" + simulate_ruby_engine "ruby" + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", :platform => [:mingw, :mswin, :x64_mingw, :jruby] + G + + bundle! "install" + + expect(out).to include <<-O.strip +The dependency #{Gem::Dependency.new("rack", ">= 0")} will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. + O + end end -describe "when a gem has no architecture" do +RSpec.describe "when a gem has no architecture" do it "still installs correctly" do simulate_platform mswin @@ -216,7 +259,7 @@ describe "when a gem has no architecture" do gem "rcov" G - bundle :install, :fakeweb => "windows" + bundle :install, :artifice => "windows" expect(the_bundle).to include_gems "rcov 1.0.0" end end diff --git a/spec/install/gemfile/ruby_spec.rb b/spec/install/gemfile/ruby_spec.rb index 1adbf10833..b9d9683758 100644 --- a/spec/install/gemfile/ruby_spec.rb +++ b/spec/install/gemfile/ruby_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "ruby requirement" do +RSpec.describe "ruby requirement" do def locked_ruby_version Bundler::RubyVersion.from_string(Bundler::LockfileParser.new(lockfile).ruby_version) end @@ -86,4 +86,24 @@ describe "ruby requirement" do expect(the_bundle).to include_gems "rack 1.0.0" expect(locked_ruby_version.versions).to eq(["5100"]) end + + it "allows requirements with trailing whitespace" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "#{RUBY_VERSION}\\n \t\\n" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "fails gracefully with malformed requirements" do + install_gemfile <<-G + source "file://#{gem_repo1}" + ruby ">= 0", "-.\\0" + gem "rack" + G + + expect(out).to include("There was an error parsing") # i.e. DSL error, not error template + end end diff --git a/spec/install/gemfile/sources_spec.rb b/spec/install/gemfile/sources_spec.rb index fd11c3feab..c5375b4abf 100644 --- a/spec/install/gemfile/sources_spec.rb +++ b/spec/install/gemfile/sources_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with gems on multiple sources" do +RSpec.describe "bundle install with gems on multiple sources" do # repo1 is built automatically before all of the specs run # it contains rack-obama 1.0.0 and rack 0.9.1 & 1.0.0 amongst other gems @@ -449,4 +449,70 @@ describe "bundle install with gems on multiple sources" do end end end + + context "when a gem is installed to system gems" do + before do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + context "and the gemfile changes" do + it "is still able to find that gem from remote sources" do + source_uri = "file://#{gem_repo1}" + second_uri = "file://#{gem_repo4}" + + build_repo4 do + build_gem "rack", "2.0.1.1.forked" + build_gem "thor", "0.19.1.1.forked" + end + + # When this gemfile is installed... + gemfile <<-G + source "#{source_uri}" + + source "#{second_uri}" do + gem "rack", "2.0.1.1.forked" + gem "thor" + end + gem "rack-obama" + G + + # It creates this lockfile. + lockfile <<-L + GEM + remote: #{source_uri}/ + remote: #{second_uri}/ + specs: + rack (2.0.1.1.forked) + rack-obama (1.0) + rack + thor (0.19.1.1.forked) + + PLATFORMS + ruby + + DEPENDENCIES + rack (= 2.0.1.1.forked)! + rack-obama + thor! + L + + # Then we change the Gemfile by adding a version to thor + gemfile <<-G + source "#{source_uri}" + + source "#{second_uri}" do + gem "rack", "2.0.1.1.forked" + gem "thor", "0.19.1.1.forked" + end + gem "rack-obama" + G + + # But we should still be able to find rack 2.0.1.1.forked and install it + bundle! :install + end + end + end end diff --git a/spec/install/gemfile/specific_platform_spec.rb b/spec/install/gemfile/specific_platform_spec.rb index 3e12f94c86..cc6c82c0ff 100644 --- a/spec/install/gemfile/specific_platform_spec.rb +++ b/spec/install/gemfile/specific_platform_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with specific_platform enabled" do +RSpec.describe "bundle install with specific_platform enabled" do before do bundle "config specific_platform true" @@ -39,6 +39,13 @@ describe "bundle install with specific_platform enabled" do build_gem("google-protobuf", "3.0.0.alpha.2.0") build_gem("google-protobuf", "3.0.0.alpha.1.1") build_gem("google-protobuf", "3.0.0.alpha.1.0") + + build_gem("facter", "2.4.6") + build_gem("facter", "2.4.6") do |s| + s.platform = "universal-darwin" + s.add_runtime_dependency "CFPropertyList" + end + build_gem("CFPropertyList") end end @@ -67,6 +74,19 @@ describe "bundle install with specific_platform enabled" do to all(exist) end + it "uses the platform-specific gem with extra dependencies" do + install_gemfile! <<-G + source "file:#{gem_repo2}" + gem "facter" + G + + expect(the_bundle.locked_gems.platforms).to eq([pl("ruby"), pl("x86_64-darwin-15")]) + expect(the_bundle).to include_gems("facter 2.4.6 universal-darwin", "CFPropertyList 1.0") + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(["CFPropertyList-1.0", + "facter-2.4.6", + "facter-2.4.6-universal-darwin"]) + end + context "when adding a platform via lock --add_platform" do it "adds the foreign platform" do install_gemfile!(google_protobuf) diff --git a/spec/install/gemfile_spec.rb b/spec/install/gemfile_spec.rb index 98abc30c86..bc49053081 100644 --- a/spec/install/gemfile_spec.rb +++ b/spec/install/gemfile_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do context "with duplicated gems" do it "will display a warning" do install_gemfile <<-G @@ -66,4 +66,33 @@ describe "bundle install" do expect(out).to match(/You passed :lib as an option for gem 'rack', but it is invalid/) end end + + context "with engine specified in symbol" do + it "does not raise any error parsing Gemfile" do + simulate_ruby_version "2.3.0" do + simulate_ruby_engine "jruby", "9.1.2.0" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0" + G + + expect(out).to match(/Bundle complete!/) + end + end + end + + it "installation succeeds" do + simulate_ruby_version "2.3.0" do + simulate_ruby_engine "jruby", "9.1.2.0" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + ruby "2.3.0", :engine => :jruby, :engine_version => "9.1.2.0" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + end + end end diff --git a/spec/install/gems/compact_index_spec.rb b/spec/install/gems/compact_index_spec.rb index a800a6ad7b..3ba858ed08 100644 --- a/spec/install/gems/compact_index_spec.rb +++ b/spec/install/gems/compact_index_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "compact index api" do +RSpec.describe "compact index api" do let(:source_hostname) { "localgemserver.test" } let(:source_uri) { "http://#{source_hostname}" } @@ -144,7 +144,7 @@ describe "compact index api" do gem "rcov" G - bundle! :install, :fakeweb => "windows" + bundle! :install, :artifice => "windows" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rcov 1.0.0" end @@ -696,18 +696,36 @@ The checksum of /versions does not match the checksum provided by the server! So expect(the_bundle).to include_gems "rack 1.0.0" end + it "fails gracefully when the source URI has an invalid scheme" do + install_gemfile <<-G + source "htps://rubygems.org" + gem "rack" + G + expect(exitstatus).to eq(15) if exitstatus + expect(out).to end_with(<<-E.strip) + The request uri `htps://index.rubygems.org/versions` has an invalid scheme (`htps`). Did you mean `http` or `https`? + E + end + describe "checksum validation", :rubygems => ">= 2.3.0" do it "raises when the checksum does not match" do install_gemfile <<-G, :artifice => "compact_index_wrong_gem_checksum" source "#{source_uri}" gem "rack" G + expect(exitstatus).to eq(19) if exitstatus expect(out). - to include("The checksum for the downloaded `rack-1.0.0.gem` did not match the checksum given by the API."). - and include("This means that the contents of the gem appear to be different from what was uploaded, and could be an indicator of a security issue."). - and match(/\(The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/). - and include("Bundler cannot continue installing rack (1.0.0).") + to include("Bundler cannot continue installing rack (1.0.0)."). + and include("The checksum for the downloaded `rack-1.0.0.gem` does not match the checksum given by the server."). + and include("This means the contents of the downloaded gem is different from what was uploaded to the server, and could be a potential security issue."). + and include("To resolve this issue:"). + and include("1. delete the downloaded gem located at: `#{system_gem_path}/gems/rack-1.0.0/rack-1.0.0.gem`"). + and include("2. run `bundle install`"). + and include("If you wish to continue installing the downloaded gem, and are certain it does not pose a security issue despite the mismatching checksum, do the following:"). + and include("1. run `bundle config disable_checksum_validation true` to turn off checksum verification"). + and include("2. run `bundle install`"). + and match(/\(More info: The expected SHA256 checksum was "#{"ab" * 22}", but the checksum for the downloaded gem was ".+?"\.\)/) end it "raises when the checksum is the wrong length" do @@ -727,4 +745,28 @@ The checksum of /versions does not match the checksum provided by the server! So G end end + + it "works when cache dir is world-writable" do + install_gemfile! <<-G, :artifice => "compact_index" + File.umask(0000) + source "#{source_uri}" + gem "rack" + G + end + + it "doesn't explode when the API dependencies are wrong" do + install_gemfile <<-G, :artifice => "compact_index_wrong_dependencies", :env => { "DEBUG" => "true" } + source "#{source_uri}" + gem "rails" + G + deps = [Gem::Dependency.new("rake", "= 10.0.2"), + Gem::Dependency.new("actionpack", "= 2.3.2"), + Gem::Dependency.new("activerecord", "= 2.3.2"), + Gem::Dependency.new("actionmailer", "= 2.3.2"), + Gem::Dependency.new("activeresource", "= 2.3.2")] + expect(out).to include(<<-E.strip).and include("rails-2.3.2 from rubygems remote at #{source_uri}/ has corrupted API dependencies") +Downloading rails-2.3.2 revealed dependencies not in the API (#{deps.join(", ")}). +Installing with `--full-index` should fix the problem. + E + end end diff --git a/spec/install/gems/dependency_api_spec.rb b/spec/install/gems/dependency_api_spec.rb index a7c1aedea3..d2de0d358b 100644 --- a/spec/install/gems/dependency_api_spec.rb +++ b/spec/install/gems/dependency_api_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "gemcutter's dependency API" do +RSpec.describe "gemcutter's dependency API" do let(:source_hostname) { "localgemserver.test" } let(:source_uri) { "http://#{source_hostname}" } @@ -124,7 +124,7 @@ describe "gemcutter's dependency API" do gem "rcov" G - bundle :install, :fakeweb => "windows" + bundle :install, :artifice => "windows" expect(out).to include("Fetching source index from #{source_uri}") expect(the_bundle).to include_gems "rcov 1.0.0" end diff --git a/spec/install/gems/env_spec.rb b/spec/install/gems/env_spec.rb index d7d3a3230e..9b1d8e5424 100644 --- a/spec/install/gems/env_spec.rb +++ b/spec/install/gems/env_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with ENV conditionals" do +RSpec.describe "bundle install with ENV conditionals" do describe "when just setting an ENV key as a string" do before :each do gemfile <<-G diff --git a/spec/install/gems/flex_spec.rb b/spec/install/gems/flex_spec.rb index 2f900893cd..2c2d3c16a1 100644 --- a/spec/install/gems/flex_spec.rb +++ b/spec/install/gems/flex_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle flex_install" do +RSpec.describe "bundle flex_install" do it "installs the gems as expected" do install_gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/install/gems/mirror_spec.rb b/spec/install/gems/mirror_spec.rb index e7b4317f05..798156fb12 100644 --- a/spec/install/gems/mirror_spec.rb +++ b/spec/install/gems/mirror_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with a mirror configured" do +RSpec.describe "bundle install with a mirror configured" do describe "when the mirror does not match the gem source" do before :each do gemfile <<-G diff --git a/spec/install/gems/native_extensions_spec.rb b/spec/install/gems/native_extensions_spec.rb index 6134663fcc..7531768b5f 100644 --- a/spec/install/gems/native_extensions_spec.rb +++ b/spec/install/gems/native_extensions_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "installing a gem with native extensions" do +RSpec.describe "installing a gem with native extensions" do it "installs" do build_repo2 do build_gem "c_extension" do |s| @@ -47,4 +47,46 @@ describe "installing a gem with native extensions" do run "Bundler.require; puts CExtension.new.its_true" expect(out).to eq("true") end + + it "installs from git" do + build_git "c_extension" do |s| + s.extensions = ["ext/extconf.rb"] + s.write "ext/extconf.rb", <<-E + require "mkmf" + name = "c_extension_bundle" + dir_config(name) + raise "OMG" unless with_config("c_extension") == "hello" + create_makefile(name) + E + + s.write "ext/c_extension.c", <<-C + #include "ruby.h" + + VALUE c_extension_true(VALUE self) { + return Qtrue; + } + + void Init_c_extension_bundle() { + VALUE c_Extension = rb_define_class("CExtension", rb_cObject); + rb_define_method(c_Extension, "its_true", c_extension_true, 0); + } + C + + s.write "lib/c_extension.rb", <<-C + require "c_extension_bundle" + C + end + + bundle! "config build.c_extension --with-c_extension=hello" + + install_gemfile! <<-G + gem "c_extension", :git => #{lib_path("c_extension-1.0").to_s.dump} + G + + expect(out).not_to include("extconf.rb failed") + expect(out).to include("Using c_extension 1.0") + + run! "Bundler.require; puts CExtension.new.its_true" + expect(out).to eq("true") + end end diff --git a/spec/install/gems/post_install_spec.rb b/spec/install/gems/post_install_spec.rb index 596fa6412f..c3ea3e7c51 100644 --- a/spec/install/gems/post_install_spec.rb +++ b/spec/install/gems/post_install_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do context "with gem sources" do context "when gems include post install messages" do it "should display the post-install messages after installing" do diff --git a/spec/install/gems/resolving_spec.rb b/spec/install/gems/resolving_spec.rb index 0204a222f9..c227993448 100644 --- a/spec/install/gems/resolving_spec.rb +++ b/spec/install/gems/resolving_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with install-time dependencies" do +RSpec.describe "bundle install with install-time dependencies" do it "installs gems with implicit rake dependencies" do install_gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/install/gems/standalone_spec.rb b/spec/install/gems/standalone_spec.rb index 7a6749dea9..496844ae14 100644 --- a/spec/install/gems/standalone_spec.rb +++ b/spec/install/gems/standalone_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -shared_examples "bundle install --standalone" do +RSpec.shared_examples "bundle install --standalone" do shared_examples "common functionality" do it "still makes the gems available to normal bundler" do args = expected_gems.map {|k, v| "#{k} #{v}" } @@ -298,16 +298,16 @@ shared_examples "bundle install --standalone" do it "creates stubs with the correct load path" do extension_line = File.read(bundled_app("bin/rails")).each_line.find {|line| line.include? "$:.unshift" }.strip - expect(extension_line).to eq "$:.unshift File.expand_path '../../bundle', path.realpath" + expect(extension_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) end end end -describe "bundle install --standalone" do +RSpec.describe "bundle install --standalone" do include_examples("bundle install --standalone") end -describe "bundle install --standalone run in a subdirectory" do +RSpec.describe "bundle install --standalone run in a subdirectory" do before do subdir = bundled_app("bob") FileUtils.mkdir_p(subdir) diff --git a/spec/install/gems/sudo_spec.rb b/spec/install/gems/sudo_spec.rb index 66b9901831..13abffc14e 100644 --- a/spec/install/gems/sudo_spec.rb +++ b/spec/install/gems/sudo_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "when using sudo", :sudo => true do +RSpec.describe "when using sudo", :sudo => true do describe "and BUNDLE_PATH is writable" do context "but BUNDLE_PATH/build_info is not writable" do before do diff --git a/spec/install/gems/win32_spec.rb b/spec/install/gems/win32_spec.rb index 69ea035f5e..cdad9a8821 100644 --- a/spec/install/gems/win32_spec.rb +++ b/spec/install/gems/win32_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install with win32-generated lockfile" do +RSpec.describe "bundle install with win32-generated lockfile" do it "should read lockfile" do File.open(bundled_app("Gemfile.lock"), "wb") do |f| f << "GEM\r\n" diff --git a/spec/install/gemspecs_spec.rb b/spec/install/gemspecs_spec.rb index 8f719bf601..97eaf149c1 100644 --- a/spec/install/gemspecs_spec.rb +++ b/spec/install/gemspecs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do describe "when a gem has a YAML gemspec" do before :each do build_repo2 do diff --git a/spec/install/git_spec.rb b/spec/install/git_spec.rb index f35a543509..a555822012 100644 --- a/spec/install/git_spec.rb +++ b/spec/install/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do context "git sources" do it "displays the revision hash of the gem repository" do build_git "foo", "1.0", :path => lib_path("foo") diff --git a/spec/install/path_spec.rb b/spec/install/path_spec.rb index 03c42f008c..ad6071d29f 100644 --- a/spec/install/path_spec.rb +++ b/spec/install/path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do describe "with --path" do before :each do build_gem "rack", "1.0.0", :to_system => true do |s| @@ -38,7 +38,7 @@ describe "bundle install" do it "disallows --path vendor/bundle --system" do bundle "install --path vendor/bundle --system" - expect(out).to include("Please choose.") + expect(out).to include("Please choose only one option.") expect(exitstatus).to eq(15) if exitstatus end @@ -128,6 +128,34 @@ describe "bundle install" do expect(vendored_gems("gems/rack-1.0.0")).to be_directory expect(the_bundle).to include_gems "rack 1.0.0" end + + it "re-installs gems whose extensions have been deleted", :rubygems => ">= 2.3" do + build_lib "very_simple_binary", "1.0.0", :to_system => true do |s| + s.write "lib/very_simple_binary.rb", "raise 'FAIL'" + end + + gemfile <<-G + source "file://#{gem_repo1}" + gem "very_simple_binary" + G + + bundle "install --path ./vendor/bundle" + + expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory + expect(vendored_gems("extensions")).to be_directory + expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + + vendored_gems("extensions").rmtree + + run "require 'very_simple_binary_c'" + expect(err).to include("Bundler::GemNotFound") + + bundle "install --path ./vendor/bundle" + + expect(vendored_gems("gems/very_simple_binary-1.0")).to be_directory + expect(vendored_gems("extensions")).to be_directory + expect(the_bundle).to include_gems "very_simple_binary 1.0", :source => "remote1" + end end describe "to a file" do diff --git a/spec/install/post_bundle_message_spec.rb b/spec/install/post_bundle_message_spec.rb index 10c71f0a51..4453e4190f 100644 --- a/spec/install/post_bundle_message_spec.rb +++ b/spec/install/post_bundle_message_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "post bundle message" do +RSpec.describe "post bundle message" do before :each do gemfile <<-G source "file://#{gem_repo1}" @@ -14,7 +14,7 @@ describe "post bundle message" do G end - let(:bundle_show_message) { "Use `bundle show [gemname]` to see where a bundled gem is installed." } + let(:bundle_show_message) { "Use `bundle info [gemname]` to see where a bundled gem is installed." } let(:bundle_deployment_message) { "Bundled gems are installed into ./vendor" } let(:bundle_complete_message) { "Bundle complete!" } let(:bundle_updated_message) { "Bundle updated!" } @@ -104,7 +104,22 @@ describe "post bundle message" do gem "rack" gem "not-a-gem", :group => :development G - expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile or available on this machine.") + expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile.") + end + + it "should report a helpful error message with reference to cache if available" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle :cache + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "not-a-gem", :group => :development + G + expect(out).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile or in gems cached in vendor/cache.") end end end diff --git a/spec/install/prereleases_spec.rb b/spec/install/prereleases_spec.rb index cbf99ca96f..6c32094d90 100644 --- a/spec/install/prereleases_spec.rb +++ b/spec/install/prereleases_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle install" do +RSpec.describe "bundle install" do describe "when prerelease gems are available" do it "finds prereleases" do install_gemfile <<-G diff --git a/spec/install/security_policy_spec.rb b/spec/install/security_policy_spec.rb index 7e2c320b80..ab531bdad6 100644 --- a/spec/install/security_policy_spec.rb +++ b/spec/install/security_policy_spec.rb @@ -5,7 +5,7 @@ require "rubygems/security" # unfortunately, testing signed gems with a provided CA is extremely difficult # as 'gem cert' is currently the only way to add CAs to the system. -describe "policies with unsigned gems" do +RSpec.describe "policies with unsigned gems" do before do build_security_repo gemfile <<-G @@ -44,7 +44,7 @@ describe "policies with unsigned gems" do end end -describe "policies with signed gems and no CA" do +RSpec.describe "policies with signed gems and no CA" do before do build_security_repo gemfile <<-G diff --git a/spec/install/yanked_spec.rb b/spec/install/yanked_spec.rb index ab96d4fcee..d42978ce4c 100644 --- a/spec/install/yanked_spec.rb +++ b/spec/install/yanked_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -context "when installing a bundle that includes yanked gems" do +RSpec.context "when installing a bundle that includes yanked gems" do before(:each) do build_repo4 do build_gem "foo", "9.0.0" @@ -41,3 +41,32 @@ context "when installing a bundle that includes yanked gems" do expect(out).to include("Could not find gem 'foo (= 10.0.0)' in any of the gem sources") end end + +RSpec.context "when using gem before installing" do + it "does not suggest the author has yanked the gem" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + G + + lockfile <<-L + GEM + remote: file://#{gem_repo1} + specs: + rack (0.9.1) + + PLATFORMS + ruby + + DEPENDENCIES + rack (= 0.9.1) + L + + bundle :list + + expect(out).to include("Could not find rack-0.9.1 in any of the sources") + expect(out).to_not include("Your bundle is locked to rack (0.9.1), but that version could not be found in any of the sources listed in your Gemfile.") + expect(out).to_not include("If you haven't changed sources, that means the author of rack (0.9.1) has removed it.") + expect(out).to_not include("You'll need to update your bundle to a different version of rack (0.9.1) that hasn't been removed in order to install.") + end +end diff --git a/spec/lock/git_spec.rb b/spec/lock/git_spec.rb index 93473db052..b36f61338d 100644 --- a/spec/lock/git_spec.rb +++ b/spec/lock/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle lock with git gems" do +RSpec.describe "bundle lock with git gems" do before :each do build_git "foo" diff --git a/spec/lock/lockfile_spec.rb b/spec/lock/lockfile_spec.rb index e6810b405f..7d768e337c 100644 --- a/spec/lock/lockfile_spec.rb +++ b/spec/lock/lockfile_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "the lockfile format" do +RSpec.describe "the lockfile format" do include Bundler::GemHelpers it "generates a simple lockfile for a single source, gem" do @@ -600,6 +600,36 @@ describe "the lockfile format" do G end + it "serializes pinned path sources to the lockfile even when packaging" do + build_lib "foo" + + install_gemfile! <<-G + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + + bundle! "package --all" + bundle! "install --local" + + lockfile_should_be <<-G + PATH + remote: #{lib_path("foo-1.0")} + specs: + foo (1.0) + + GEM + specs: + + PLATFORMS + #{generic_local_platform} + + DEPENDENCIES + foo! + + BUNDLED WITH + #{Bundler::VERSION} + G + end + it "sorts serialized sources by type" do build_lib "foo" bar = build_git "bar" diff --git a/spec/other/bundle_ruby_spec.rb b/spec/other/bundle_ruby_spec.rb index 4b2ebf4cfd..09fa2c223b 100644 --- a/spec/other/bundle_ruby_spec.rb +++ b/spec/other/bundle_ruby_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle_ruby" do +RSpec.describe "bundle_ruby" do context "without patchlevel" do it "returns the ruby version" do gemfile <<-G diff --git a/spec/other/cli_dispatch_spec.rb b/spec/other/cli_dispatch_spec.rb index 05dac51559..8b34a457ef 100644 --- a/spec/other/cli_dispatch_spec.rb +++ b/spec/other/cli_dispatch_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle command names" do +RSpec.describe "bundle command names" do it "work when given fully" do bundle "install" expect(err).to lack_errors diff --git a/spec/other/ext_spec.rb b/spec/other/ext_spec.rb index 7e2e712827..2d6ab941b8 100644 --- a/spec/other/ext_spec.rb +++ b/spec/other/ext_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Gem::Specification#match_platform" do +RSpec.describe "Gem::Specification#match_platform" do it "does not match platforms other than the gem platform" do darwin = gem "lol", "1.0", "platform_specific-1.0-x86-darwin-10" expect(darwin.match_platform(pl("java"))).to eq(false) @@ -16,7 +16,7 @@ describe "Gem::Specification#match_platform" do end end -describe "Bundler::GemHelpers#generic" do +RSpec.describe "Bundler::GemHelpers#generic" do include Bundler::GemHelpers it "converts non-windows platforms into ruby" do @@ -47,9 +47,7 @@ describe "Bundler::GemHelpers#generic" do end end -describe "Gem::SourceIndex#refresh!" do - rubygems_1_7 = Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.7.0") - +RSpec.describe "Gem::SourceIndex#refresh!" do before do install_gemfile <<-G source "file://#{gem_repo1}" @@ -57,12 +55,12 @@ describe "Gem::SourceIndex#refresh!" do G end - it "does not explode when called", :if => rubygems_1_7 do + it "does not explode when called", :rubygems => "1.7" do run "Gem.source_index.refresh!" run "Gem::SourceIndex.new([]).refresh!" end - it "does not explode when called", :unless => rubygems_1_7 do + it "does not explode when called", :rubygems => "< 1.7" do run "Gem.source_index.refresh!" run "Gem::SourceIndex.from_gems_in([]).refresh!" end diff --git a/spec/other/major_deprecation_spec.rb b/spec/other/major_deprecation_spec.rb index 6505023d13..465d769538 100644 --- a/spec/other/major_deprecation_spec.rb +++ b/spec/other/major_deprecation_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "major deprecations" do +RSpec.describe "major deprecations" do let(:warnings) { out } # change to err in 2.0 context "in a .99 version" do @@ -97,7 +97,7 @@ describe "major deprecations" do G bundle :install, :binstubs => true - expect(warnings).to have_major_deprecation a_string_including("the --binstubs option will be removed") + expect(warnings).to have_major_deprecation a_string_including("The --binstubs option will be removed") end end end @@ -120,7 +120,7 @@ describe "major deprecations" do gem "rack" G - expect(warnings).to have_major_deprecation("gems.rb and gems.locked will be prefered to Gemfile and Gemfile.lock.") + expect(warnings).to have_major_deprecation("gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock.") end context "with flags" do @@ -154,7 +154,7 @@ describe "major deprecations" do Bundler.setup RUBY - expect(warnings).to have_major_deprecation("gems.rb and gems.locked will be prefered to Gemfile and Gemfile.lock.") + expect(warnings).to have_major_deprecation("gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock.") end end diff --git a/spec/other/platform_spec.rb b/spec/other/platform_spec.rb index 7aa0d0f8ea..6adbcef111 100644 --- a/spec/other/platform_spec.rb +++ b/spec/other/platform_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle platform" do +RSpec.describe "bundle platform" do context "without flags" do it "returns all the output" do gemfile <<-G diff --git a/spec/other/ssl_cert_spec.rb b/spec/other/ssl_cert_spec.rb index e1a134c919..2de4dfdd0c 100644 --- a/spec/other/ssl_cert_spec.rb +++ b/spec/other/ssl_cert_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "bundler/ssl_certs/certificate_manager" -describe "SSL Certificates", :rubygems_master do +RSpec.describe "SSL Certificates", :rubygems_master do hosts = %w( rubygems.org index.rubygems.org diff --git a/spec/other/trampoline_spec.rb b/spec/other/trampoline_spec.rb index 9a8e0a4a5d..39de7048c7 100644 --- a/spec/other/trampoline_spec.rb +++ b/spec/other/trampoline_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require "spec_helper" -describe "bundler version trampolining" do +RSpec.describe "bundler version trampolining" do before do - ENV["BUNDLE_DISABLE_POSTIT"] = nil - ENV["BUNDLE_ENABLE_TRAMPOLINE"] = "true" + ENV["BUNDLE_TRAMPOLINE_DISABLE"] = nil + ENV["BUNDLE_TRAMPOLINE_FORCE"] = "true" FileUtils.rm_rf(system_gem_path) FileUtils.cp_r(base_system_gems, system_gem_path) end @@ -59,8 +59,8 @@ describe "bundler version trampolining" do end end - context "without BUNDLE_ENABLE_TRAMPOLINE" do - before { ENV["BUNDLE_ENABLE_TRAMPOLINE"] = nil } + context "without BUNDLE_TRAMPOLINE_FORCE" do + before { ENV["BUNDLE_TRAMPOLINE_FORCE"] = nil } context "when the version is >= 2" do let(:version) { "2.7182818285" } diff --git a/spec/plugins/command_spec.rb b/spec/plugins/command_spec.rb index 71e87a5b01..6ad782b758 100644 --- a/spec/plugins/command_spec.rb +++ b/spec/plugins/command_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "command plugins" do +RSpec.describe "command plugins" do before do build_repo2 do build_plugin "command-mah" do |s| diff --git a/spec/plugins/hook_spec.rb b/spec/plugins/hook_spec.rb index bafe688d5e..9850d850ac 100644 --- a/spec/plugins/hook_spec.rb +++ b/spec/plugins/hook_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "hook plugins" do +RSpec.describe "hook plugins" do before do build_repo2 do build_plugin "before-install-plugin" do |s| diff --git a/spec/plugins/install_spec.rb b/spec/plugins/install_spec.rb index a24f318156..e2d351181c 100644 --- a/spec/plugins/install_spec.rb +++ b/spec/plugins/install_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundler plugin install" do +RSpec.describe "bundler plugin install" do before do build_repo2 do build_plugin "foo" @@ -70,6 +70,17 @@ describe "bundler plugin install" do context "malformatted plugin" do it "fails when plugins.rb is missing" do + update_repo2 do + build_plugin "foo", "1.1" + build_plugin "kung-foo", "1.1" + end + + bundle "plugin install foo kung-foo --version '1.0' --source file://#{gem_repo2}" + + expect(out).to include("Installing foo 1.0") + expect(out).to include("Installing kung-foo 1.0") + plugin_should_be_installed("foo", "kung-foo") + build_repo2 do build_gem "charlie" end @@ -80,6 +91,7 @@ describe "bundler plugin install" do expect(global_plugin_gem("charlie-1.0")).not_to be_directory + plugin_should_be_installed("foo", "kung-foo") plugin_should_not_be_installed("charlie") end diff --git a/spec/plugins/source/example_spec.rb b/spec/plugins/source/example_spec.rb index 520e1b9db4..2ae34caf73 100644 --- a/spec/plugins/source/example_spec.rb +++ b/spec/plugins/source/example_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "real source plugins" do +RSpec.describe "real source plugins" do context "with a minimal source plugin" do before do build_repo2 do diff --git a/spec/plugins/source_spec.rb b/spec/plugins/source_spec.rb index 92e948c190..0448bc409a 100644 --- a/spec/plugins/source_spec.rb +++ b/spec/plugins/source_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundler source plugin" do +RSpec.describe "bundler source plugin" do describe "plugins dsl eval for #source with :type option" do before do update_repo2 do diff --git a/spec/quality_spec.rb b/spec/quality_spec.rb index 5f73228b4a..c2a4ae536b 100644 --- a/spec/quality_spec.rb +++ b/spec/quality_spec.rb @@ -6,7 +6,7 @@ if defined?(Encoding) && Encoding.default_external.name != "UTF-8" Encoding.default_external = Encoding.find("UTF-8") end -describe "The library itself" do +RSpec.describe "The library itself" do def check_for_spec_defs_with_single_quotes(filename) failing_lines = [] @@ -191,6 +191,38 @@ describe "The library itself" do expect(error_messages.compact).to be_well_formed end + it "documents all used settings" do + exemptions = %w( + gem.coc + gem.mit + warned_version + ) + + all_settings = Hash.new {|h, k| h[k] = [] } + documented_settings = exemptions + + Bundler::Settings::BOOL_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::BOOL_KEYS" } + Bundler::Settings::NUMBER_KEYS.each {|k| all_settings[k] << "in Bundler::Settings::NUMBER_KEYS" } + + Dir.chdir(File.expand_path("../../lib", __FILE__)) do + key_pattern = /([a-z\._-]+)/i + `git ls-files -z`.split("\x0").each do |filename| + File.readlines(filename).each_with_index do |line, number| + line.scan(/Bundler\.settings\[:#{key_pattern}\]/).flatten.each {|s| all_settings[s] << "referenced at `lib/#{filename}:#{number.succ}`" } + end + end + documented_settings = File.read("../man/bundle-config.ronn").scan(/^\* `#{key_pattern}`/).flatten + end + + documented_settings.each {|s| all_settings.delete(s) } + exemptions.each {|s| all_settings.delete(s) } + error_messages = all_settings.map do |setting, refs| + "The `#{setting}` setting is undocumented\n\t- #{refs.join("\n\t- ")}\n" + end + + expect(error_messages.sort).to be_well_formed + end + it "can still be built" do Dir.chdir(root) do begin diff --git a/spec/realworld/dependency_api_spec.rb b/spec/realworld/dependency_api_spec.rb index 9823cf8c76..468fa3644c 100644 --- a/spec/realworld/dependency_api_spec.rb +++ b/spec/realworld/dependency_api_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "gemcutter's dependency API", :realworld => true do +RSpec.describe "gemcutter's dependency API", :realworld => true do context "when Gemcutter API takes too long to respond" do before do require_rack @@ -26,6 +26,7 @@ describe "gemcutter's dependency API", :realworld => true do end after do + Artifice.deactivate @t.kill @t.join end diff --git a/spec/realworld/edgecases_spec.rb b/spec/realworld/edgecases_spec.rb index 7a78a114b4..302fd57cf0 100644 --- a/spec/realworld/edgecases_spec.rb +++ b/spec/realworld/edgecases_spec.rb @@ -1,7 +1,21 @@ # frozen_string_literal: true require "spec_helper" -describe "real world edgecases", :realworld => true, :sometimes => true do +RSpec.describe "real world edgecases", :realworld => true, :sometimes => true do + def rubygems_version(name, requirement) + require "bundler/source/rubygems/remote" + require "bundler/fetcher" + source = Bundler::Source::Rubygems::Remote.new(URI("https://rubygems.org")) + fetcher = Bundler::Fetcher.new(source) + index = fetcher.specs([name], nil) + rubygem = index.search(Gem::Dependency.new(name, requirement)).last + if rubygem.nil? + raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \ + "Found specs:\n#{index.send(:specs).inspect}" + end + "#{name} (#{rubygem.version})" + end + # there is no rbx-relative-require gem that will install on 1.9 it "ignores extra gems with bad platforms", :ruby => "~> 1.8.7" do gemfile <<-G @@ -15,7 +29,7 @@ describe "real world edgecases", :realworld => true, :sometimes => true do # https://github.com/bundler/bundler/issues/1202 it "bundle cache works with rubygems 1.3.7 and pre gems", - :ruby => "~> 1.8.7", "https://rubygems.org" => "~> 1.3.7" do + :ruby => "~> 1.8.7", :rubygems => "~> 1.3.7" do install_gemfile <<-G source "https://rubygems.org" gem "rack", "1.3.0.beta2" @@ -32,8 +46,8 @@ describe "real world edgecases", :realworld => true, :sometimes => true do source "https://rubygems.org" gem 'i18n', '~> 0.6.0' - gem 'activesupport', '~> 3.0' - gem 'activerecord', '~> 3.0' + gem 'activesupport', '~> 3.0.5' + gem 'activerecord', '~> 3.0.5' gem 'builder', '~> 2.1.2' G bundle :lock @@ -48,8 +62,8 @@ describe "real world edgecases", :realworld => true, :sometimes => true do gem 'capybara', '~> 2.2.0' gem 'rack-cache', '1.2.0' # last version that works on Ruby 1.9 G - bundle :lock - expect(lockfile).to include("rails (3.2.22.4)") + bundle! :lock + expect(lockfile).to include(rubygems_version("rails", "~> 3.0")) expect(lockfile).to include("capybara (2.2.1)") end @@ -76,8 +90,144 @@ describe "real world edgecases", :realworld => true, :sometimes => true do gem "builder", "~> 2.1.2" G bundle :lock - expect(lockfile).to include("i18n (0.6.11)") - expect(lockfile).to include("activesupport (3.0.5)") + expect(lockfile).to include(rubygems_version("i18n", "~> 0.6.0")) + expect(lockfile).to include(rubygems_version("activesupport", "~> 3.0")) + end + + it "is able to update a top-level dependency when there is a conflict on a shared transitive child", :ruby => "2.1" do + # from https://github.com/bundler/bundler/issues/5031 + + gemfile <<-G + source "https://rubygems.org" + gem 'rails', '~> 4.2.7.1' + gem 'paperclip', '~> 5.1.0' + G + + lockfile <<-L + GEM + remote: https://rubygems.org/ + specs: + actionmailer (4.2.7.1) + actionpack (= 4.2.7.1) + actionview (= 4.2.7.1) + activejob (= 4.2.7.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.7.1) + actionview (= 4.2.7.1) + activesupport (= 4.2.7.1) + rack (~> 1.6) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.7.1) + activesupport (= 4.2.7.1) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.7.1) + activesupport (= 4.2.7.1) + globalid (>= 0.3.0) + activemodel (4.2.7.1) + activesupport (= 4.2.7.1) + builder (~> 3.1) + activerecord (4.2.7.1) + activemodel (= 4.2.7.1) + activesupport (= 4.2.7.1) + arel (~> 6.0) + activesupport (4.2.7.1) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + arel (6.0.3) + builder (3.2.2) + climate_control (0.0.3) + activesupport (>= 3.0) + cocaine (0.5.8) + climate_control (>= 0.0.3, < 1.0) + concurrent-ruby (1.0.2) + erubis (2.7.0) + globalid (0.3.7) + activesupport (>= 4.1.0) + i18n (0.7.0) + json (1.8.3) + loofah (2.0.3) + nokogiri (>= 1.5.9) + mail (2.6.4) + mime-types (>= 1.16, < 4) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mimemagic (0.3.2) + mini_portile2 (2.1.0) + minitest (5.9.1) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) + paperclip (5.1.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + cocaine (~> 0.5.5) + mime-types + mimemagic (~> 0.3.0) + pkg-config (1.1.7) + rack (1.6.4) + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.7.1) + actionmailer (= 4.2.7.1) + actionpack (= 4.2.7.1) + actionview (= 4.2.7.1) + activejob (= 4.2.7.1) + activemodel (= 4.2.7.1) + activerecord (= 4.2.7.1) + activesupport (= 4.2.7.1) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.7.1) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (4.2.7.1) + actionpack (= 4.2.7.1) + activesupport (= 4.2.7.1) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (11.3.0) + sprockets (3.7.0) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.19.1) + thread_safe (0.3.5) + tzinfo (1.2.2) + thread_safe (~> 0.1) + + PLATFORMS + ruby + + DEPENDENCIES + paperclip (~> 5.1.0) + rails (~> 4.2.7.1) + + BUNDLED WITH + 1.13.1 + L + + bundle! "lock --update paperclip" + + expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0")) end # https://github.com/bundler/bundler/issues/1500 diff --git a/spec/realworld/gemfile_source_header_spec.rb b/spec/realworld/gemfile_source_header_spec.rb index 1c39fe97bb..ba888d43bd 100644 --- a/spec/realworld/gemfile_source_header_spec.rb +++ b/spec/realworld/gemfile_source_header_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "thread" -describe "fetching dependencies with a mirrored source", :realworld => true, :rubygems => ">= 2.0" do +RSpec.describe "fetching dependencies with a mirrored source", :realworld => true, :rubygems => ">= 2.0" do let(:mirror) { "https://server.example.org" } let(:original) { "http://127.0.0.1:#{@port}" } @@ -12,6 +12,7 @@ describe "fetching dependencies with a mirrored source", :realworld => true, :ru end after do + Artifice.deactivate @t.kill @t.join end diff --git a/spec/realworld/mirror_probe_spec.rb b/spec/realworld/mirror_probe_spec.rb index bb2be7f232..93dca0c173 100644 --- a/spec/realworld/mirror_probe_spec.rb +++ b/spec/realworld/mirror_probe_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" require "thread" -describe "fetching dependencies with a not available mirror", :realworld => true do +RSpec.describe "fetching dependencies with a not available mirror", :realworld => true do let(:mirror) { @mirror_uri } let(:original) { @server_uri } let(:server_port) { @server_port } @@ -15,6 +15,7 @@ describe "fetching dependencies with a not available mirror", :realworld => true end after do + Artifice.deactivate @server_thread.kill @server_thread.join end @@ -78,6 +79,22 @@ describe "fetching dependencies with a not available mirror", :realworld => true expect(out).to include("Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}") expect(out).to include("Could not fetch specs from #{mirror}") end + + it "prints each error and warning on a new line" do + gemfile <<-G + source "#{original}" + gem 'weakling' + G + + bundle :install + + expect(out).to eq "Fetching source index from #{mirror}/ + +Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ +Retrying fetcher due to error (3/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ +Retrying fetcher due to error (4/4): Bundler::HTTPError Could not fetch specs from #{mirror}/ +Could not fetch specs from #{mirror}/" + end end context "with a global mirror without a fallback timeout" do diff --git a/spec/realworld/parallel_spec.rb b/spec/realworld/parallel_spec.rb index 539b8ab287..6950bead19 100644 --- a/spec/realworld/parallel_spec.rb +++ b/spec/realworld/parallel_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "parallel", :realworld => true, :sometimes => true do +RSpec.describe "parallel", :realworld => true, :sometimes => true do it "installs" do gemfile <<-G source "https://rubygems.org" diff --git a/spec/resolver/basic_spec.rb b/spec/resolver/basic_spec.rb index 3e8883d1d4..69881d1019 100644 --- a/spec/resolver/basic_spec.rb +++ b/spec/resolver/basic_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Resolving" do +RSpec.describe "Resolving" do before :each do @index = an_awesome_index end @@ -214,18 +214,22 @@ describe "Resolving" do should_conservative_resolve_and_include :patch, [], %w(foo-1.4.4 bar-2.1.1) end - it "will not revert to a previous version in strict mode level patch" do - pending "possible issue with molinillo - needs further research" - should_conservative_resolve_and_include [:patch, :strict], [], %w(foo-1.4.3 bar-2.1.1) + it "cannot revert to a previous version in strict mode level patch" do + # the strict option removes the version required to match, so a version conflict results + expect do + should_conservative_resolve_and_include [:patch, :strict], [], %w(foo-1.4.3 bar-2.1.1) + end.to raise_error Bundler::VersionConflict, /#{Regexp.escape("Could not find gem 'bar (~> 2.1.0)'")}/ end it "could revert to a previous version level minor" do should_conservative_resolve_and_include :minor, [], %w(foo-1.5.0 bar-2.0.5) end - it "will not revert to a previous version in strict mode level minor" do - pending "possible issue with molinillo - needs further research" - should_conservative_resolve_and_include [:minor, :strict], [], %w(foo-1.4.3 bar-2.1.1) + it "cannot revert to a previous version in strict mode level minor" do + # the strict option removes the version required to match, so a version conflict results + expect do + should_conservative_resolve_and_include [:minor, :strict], [], %w(foo-1.4.3 bar-2.1.1) + end.to raise_error Bundler::VersionConflict, /#{Regexp.escape("Could not find gem 'bar (~> 2.0.0)'")}/ end end end diff --git a/spec/resolver/platform_spec.rb b/spec/resolver/platform_spec.rb index fa91eab9c2..90d6f637ce 100644 --- a/spec/resolver/platform_spec.rb +++ b/spec/resolver/platform_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Resolving platform craziness" do +RSpec.describe "Resolving platform craziness" do describe "with cross-platform gems" do before :each do @index = an_awesome_index diff --git a/spec/runtime/executable_spec.rb b/spec/runtime/executable_spec.rb index 1ce65bd598..ff27d0b415 100644 --- a/spec/runtime/executable_spec.rb +++ b/spec/runtime/executable_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Running bin/* commands" do +RSpec.describe "Running bin/* commands" do before :each do gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/runtime/gem_tasks_spec.rb b/spec/runtime/gem_tasks_spec.rb index c187efd681..422ec45470 100644 --- a/spec/runtime/gem_tasks_spec.rb +++ b/spec/runtime/gem_tasks_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "require 'bundler/gem_tasks'" do +RSpec.describe "require 'bundler/gem_tasks'" do before :each do bundled_app("foo.gemspec").open("w") do |f| f.write <<-GEMSPEC diff --git a/spec/runtime/inline_spec.rb b/spec/runtime/inline_spec.rb index 3119045be4..022b123dc3 100644 --- a/spec/runtime/inline_spec.rb +++ b/spec/runtime/inline_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundler/inline#gemfile" do +RSpec.describe "bundler/inline#gemfile" do def script(code, options = {}) requires = ["bundler/inline"] requires.unshift File.expand_path("../../support/artifice/" + options.delete(:artifice) + ".rb", __FILE__) if options.key?(:artifice) @@ -85,12 +85,14 @@ describe "bundler/inline#gemfile" do script <<-RUBY, :artifice => "endpoint" gemfile(true) do - source "https://rubygems.org" + source "https://notaserver.com" gem "activesupport", :require => true end RUBY expect(out).to include("Installing activesupport") + err.gsub! %r{.*lib/sinatra/base\.rb:\d+: warning: constant ::Fixnum is deprecated$}, "" + err.strip! expect(err).to lack_errors expect(exitstatus).to be_zero if exitstatus end @@ -104,12 +106,12 @@ describe "bundler/inline#gemfile" do end end gemfile(true, :ui => MyBundlerUI.new) do - source "https://rubygems.org" + source "https://notaserver.com" gem "activesupport", :require => true end RUBY - expect(out).to eq("CONFIRMED!") + expect(out).to eq("CONFIRMED!\nCONFIRMED!") expect(exitstatus).to be_zero if exitstatus end @@ -173,4 +175,79 @@ describe "bundler/inline#gemfile" do expect(err).to be_empty expect(exitstatus).to be_zero if exitstatus end + + it "allows calling gemfile twice" do + script <<-RUBY + gemfile do + path "#{lib_path}" do + gem "two" + end + end + + gemfile do + path "#{lib_path}" do + gem "four" + end + end + RUBY + + expect(out).to eq("two\nfour") + expect(err).to be_empty + expect(exitstatus).to be_zero if exitstatus + end + + it "installs inline gems when a Gemfile.lock is present" do + gemfile <<-G + source "https://notaserver.com" + gem "rake" + G + + lockfile <<-G + GEM + remote: https://rubygems.org/ + specs: + rake (11.3.0) + + PLATFORMS + ruby + + DEPENDENCIES + rake + + BUNDLED WITH + 1.13.6 + G + + in_app_root do + script <<-RUBY + gemfile do + source "file://#{gem_repo1}" + gem "rack" + end + + puts RACK + RUBY + end + + expect(err).to be_empty + expect(exitstatus).to be_zero if exitstatus + end + + it "installs inline gems when BUNDLE_GEMFILE is set to an empty string" do + ENV["BUNDLE_GEMFILE"] = "" + + in_app_root do + script <<-RUBY + gemfile do + source "file://#{gem_repo1}" + gem "rack" + end + + puts RACK + RUBY + end + + expect(err).to be_empty + expect(exitstatus).to be_zero if exitstatus + end end diff --git a/spec/runtime/load_spec.rb b/spec/runtime/load_spec.rb index c010f8f8e9..d0e308ed3e 100644 --- a/spec/runtime/load_spec.rb +++ b/spec/runtime/load_spec.rb @@ -1,16 +1,9 @@ # frozen_string_literal: true require "spec_helper" -describe "Bundler.load" do +RSpec.describe "Bundler.load" do before :each do system_gems "rack-1.0.0" - # clear memoized method results - # TODO: Don't reset internal ivars - Bundler.instance_eval do - @load = nil - @runtime = nil - @definition = nil - end end describe "with a gemfile" do diff --git a/spec/runtime/platform_spec.rb b/spec/runtime/platform_spec.rb index 666864a88c..4df934e71f 100644 --- a/spec/runtime/platform_spec.rb +++ b/spec/runtime/platform_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Bundler.setup with multi platform stuff" do +RSpec.describe "Bundler.setup with multi platform stuff" do it "raises a friendly error when gems are missing locally" do gemfile <<-G source "file://#{gem_repo1}" @@ -88,4 +88,36 @@ describe "Bundler.setup with multi platform stuff" do expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 x86-darwin-100" end + + it "allows specifying only-ruby-platform" do + simulate_platform "java" + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "nokogiri" + gem "platform_specific" + G + + bundle! "config force_ruby_platform true" + + bundle! "install" + + expect(the_bundle).to include_gems "nokogiri 1.4.2", "platform_specific 1.0 RUBY" + end + + it "allows specifying only-ruby-platform on windows with dependency platforms" do + simulate_windows do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "nokogiri", :platforms => [:mingw, :mswin, :x64_mingw, :jruby] + gem "platform_specific" + G + + bundle! "config force_ruby_platform true" + + bundle! "install" + + expect(the_bundle).to include_gems "platform_specific 1.0 RUBY" + end + end end diff --git a/spec/runtime/require_spec.rb b/spec/runtime/require_spec.rb index ef88f91282..2d8935d2af 100644 --- a/spec/runtime/require_spec.rb +++ b/spec/runtime/require_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Bundler.require" do +RSpec.describe "Bundler.require" do before :each do build_lib "one", "1.0.0" do |s| s.write "lib/baz.rb", "puts 'baz'" @@ -196,7 +196,7 @@ describe "Bundler.require" do expect(err).to lack_errors end - it "does not mangle explictly given requires" do + it "does not mangle explicitly given requires" do gemfile <<-G path "#{lib_path}" gem 'jquery-rails', :require => 'jquery-rails' @@ -362,7 +362,7 @@ describe "Bundler.require" do end end -describe "Bundler.require with platform specific dependencies" do +RSpec.describe "Bundler.require with platform specific dependencies" do it "does not require the gems that are pinned to other platforms" do install_gemfile <<-G source "file://#{gem_repo1}" diff --git a/spec/runtime/setup_spec.rb b/spec/runtime/setup_spec.rb index eab66e2ee2..be0c7a1593 100644 --- a/spec/runtime/setup_spec.rb +++ b/spec/runtime/setup_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Bundler.setup" do +RSpec.describe "Bundler.setup" do describe "with no arguments" do it "makes all groups available" do install_gemfile <<-G @@ -673,6 +673,34 @@ describe "Bundler.setup" do expect(out).to be_empty end + it "does not load all gemspecs", :rubygems => ">= 2.3" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + run! <<-R + File.open(File.join(Gem.dir, "specifications", "broken.gemspec"), "w") do |f| + f.write <<-RUBY +# -*- encoding: utf-8 -*- +# stub: broken 1.0.0 ruby lib + +Gem::Specification.new do |s| + s.name = "broken" + s.version = "1.0.0" + raise "BROKEN GEMSPEC" +end + RUBY + end + R + + run! <<-R + puts "WIN" + R + + expect(out).to eq("WIN") + end + it "ignores empty gem paths" do install_gemfile <<-G source "file://#{gem_repo1}" @@ -1080,8 +1108,8 @@ describe "Bundler.setup" do end end - describe "when Psych is not in the Gemfile", :ruby => "~> 2.2" do - it "does not load Psych" do + describe "with gemified standard libraries" do + it "does not load Psych", :ruby => "~> 2.2" do gemfile "" ruby <<-RUBY require 'bundler/setup' @@ -1093,5 +1121,31 @@ describe "Bundler.setup" do expect(pre_bundler).to eq("undefined") expect(post_bundler).to match(/\d+\.\d+\.\d+/) end + + it "does not load openssl" do + install_gemfile! "" + ruby! <<-RUBY + require "bundler/setup" + puts defined?(OpenSSL) || "undefined" + require "openssl" + puts defined?(OpenSSL) || "undefined" + RUBY + expect(out).to eq("undefined\nconstant") + end + end + + describe "after setup" do + it "allows calling #gem on random objects" do + install_gemfile <<-G + source "file:#{gem_repo1}" + gem "rack" + G + ruby! <<-RUBY + require "bundler/setup" + Object.new.gem "rack" + puts Gem.loaded_specs["rack"].full_name + RUBY + expect(out).to eq("rack-1.0.0") + end end end diff --git a/spec/runtime/with_clean_env_spec.rb b/spec/runtime/with_clean_env_spec.rb index 752754be39..b351e86c8b 100644 --- a/spec/runtime/with_clean_env_spec.rb +++ b/spec/runtime/with_clean_env_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "Bundler.with_env helpers" do +RSpec.describe "Bundler.with_env helpers" do describe "Bundler.original_env" do before do gemfile "" @@ -116,14 +116,14 @@ describe "Bundler.with_env helpers" do end end - describe "Bundler.clean_system" do + describe "Bundler.clean_system", :ruby => ">= 1.9" do it "runs system inside with_clean_env" do Bundler.clean_system(%(echo 'if [ "$BUNDLE_PATH" = "" ]; then exit 42; else exit 1; fi' | /bin/sh)) expect($?.exitstatus).to eq(42) end end - describe "Bundler.clean_exec" do + describe "Bundler.clean_exec", :ruby => ">= 1.9" do it "runs exec inside with_clean_env" do pid = Kernel.fork do Bundler.clean_exec(%(echo 'if [ "$BUNDLE_PATH" = "" ]; then exit 42; else exit 1; fi' | /bin/sh)) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a3251ea640..297d81f531 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,6 +17,10 @@ rescue LoadError abort "Run rake spec:deps to install development dependencies" end +if File.expand_path(__FILE__) =~ %r{([^\w/\.])} + abort "The bundler specs cannot be run from a path that contains special characters (particularly #{$1.inspect})" +end + require "bundler" # Require the correct version of popen for the current platform @@ -31,7 +35,7 @@ else end Dir["#{File.expand_path("../support", __FILE__)}/*.rb"].each do |file| - require file unless file =~ %r{fakeweb/.*\.rb} + require file unless file.end_with?("hax.rb") end $debug = false @@ -61,6 +65,14 @@ RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" + config.disable_monkey_patching! + + # Since failures cause us to keep a bunch of long strings in memory, stop + # once we have a large number of failures (indicative of core pieces of + # bundler being broken) so that running the full test suite doesn't take + # forever due to memory constraints + config.fail_fast ||= 25 + if ENV["BUNDLER_SUDO_TESTS"] && Spec::Sudo.present? config.filter_run :sudo => true else @@ -91,6 +103,14 @@ RSpec.configure do |config| config.before :all do build_repo1 + # HACK: necessary until rspec-mocks > 3.5.0 is used + # see https://github.com/bundler/bundler/pull/5363#issuecomment-278089256 + if RUBY_VERSION < "1.9" + FileUtils.module_eval do + alias_method :mkpath, :mkdir_p + module_function :mkpath + end + end end config.before :each do diff --git a/spec/support/artifice/compact_index.rb b/spec/support/artifice/compact_index.rb index f3ff2db4fa..9111ed8211 100644 --- a/spec/support/artifice/compact_index.rb +++ b/spec/support/artifice/compact_index.rb @@ -67,9 +67,11 @@ class CompactIndexAPI < Endpoint @gems ||= {} @gems[gem_repo] ||= begin specs = Bundler::Deprecate.skip_during do - Marshal.load(File.open(gem_repo.join("specs.4.8")).read).map do |name, version, platform| - load_spec(name, version, platform, gem_repo) - end + %w(specs.4.8 prerelease_specs.4.8).map do |filename| + Marshal.load(File.open(gem_repo.join(filename)).read).map do |name, version, platform| + load_spec(name, version, platform, gem_repo) + end + end.flatten end specs.group_by(&:name).map do |name, versions| @@ -103,8 +105,8 @@ class CompactIndexAPI < Endpoint file = tmp("versions.list") file.delete if file.file? file = CompactIndex::VersionsFile.new(file.to_s) - file.update_with(gems) - CompactIndex.versions(file, nil, {}) + file.create(gems) + file.contents end end diff --git a/spec/support/artifice/compact_index_concurrent_download.rb b/spec/support/artifice/compact_index_concurrent_download.rb index 30a2171a30..b788a852cf 100644 --- a/spec/support/artifice/compact_index_concurrent_download.rb +++ b/spec/support/artifice/compact_index_concurrent_download.rb @@ -22,8 +22,8 @@ class CompactIndexConcurrentDownload < CompactIndexAPI file = tmp("versions.list") file.delete if file.file? file = CompactIndex::VersionsFile.new(file.to_s) - file.update_with(gems) - CompactIndex.versions(file, nil, {}) + file.create(gems) + file.contents end end end diff --git a/spec/support/artifice/compact_index_extra_api.rb b/spec/support/artifice/compact_index_extra_api.rb index 063e5589d4..844a9ca9f2 100644 --- a/spec/support/artifice/compact_index_extra_api.rb +++ b/spec/support/artifice/compact_index_extra_api.rb @@ -15,8 +15,8 @@ class CompactIndexExtraApi < CompactIndexAPI file = tmp("versions.list") file.delete if file.file? file = CompactIndex::VersionsFile.new(file.to_s) - file.update_with(gems(gem_repo4)) - CompactIndex.versions(file, nil, {}) + file.create(gems(gem_repo4)) + file.contents end end diff --git a/spec/support/artifice/compact_index_wrong_dependencies.rb b/spec/support/artifice/compact_index_wrong_dependencies.rb new file mode 100644 index 0000000000..25935f5e5d --- /dev/null +++ b/spec/support/artifice/compact_index_wrong_dependencies.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require File.expand_path("../compact_index", __FILE__) + +Artifice.deactivate + +class CompactIndexWrongDependencies < CompactIndexAPI + get "/info/:name" do + etag_response do + gem = gems.find {|g| g.name == params[:name] } + gem.versions.each {|gv| gv.dependencies.clear } if gem + CompactIndex.info(gem ? gem.versions : []) + end + end +end + +Artifice.activate_with(CompactIndexWrongDependencies) diff --git a/spec/support/artifice/endpoint.rb b/spec/support/artifice/endpoint.rb index 2955889a86..861ac49e93 100644 --- a/spec/support/artifice/endpoint.rb +++ b/spec/support/artifice/endpoint.rb @@ -3,12 +3,7 @@ require File.expand_path("../../path.rb", __FILE__) require File.expand_path("../../../../lib/bundler/deprecate", __FILE__) include Spec::Path -# Set up pretend http gem server with FakeWeb -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/artifice*/lib")].first}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/rack-*/lib")].first}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/rack-*/lib")].last}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/tilt*/lib")].first}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/sinatra*/lib")].first}" +$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,rack,tilt,sinatra}-*/lib")].map(&:to_s)) require "artifice" require "sinatra/base" @@ -24,7 +19,11 @@ class Endpoint < Sinatra::Base require "rubygems" require "bundler" Bundler::Deprecate.skip_during do - Marshal.load(File.open(gem_repo.join("specs.4.8")).read).map do |name, version, platform| + all_specs = %w(specs.4.8 prerelease_specs.4.8).map do |filename| + Marshal.load(File.open(gem_repo.join(filename)).read) + end.inject(:+) + + all_specs.map do |name, version, platform| spec = load_spec(name, version, platform, gem_repo) next unless gem_names.include?(spec.name) { diff --git a/spec/support/artifice/endpoint_500.rb b/spec/support/artifice/endpoint_500.rb index 9bbbd1e2f2..993630b79e 100644 --- a/spec/support/artifice/endpoint_500.rb +++ b/spec/support/artifice/endpoint_500.rb @@ -2,11 +2,7 @@ require File.expand_path("../../path.rb", __FILE__) include Spec::Path -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/artifice*/lib")].first}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/rack-*/lib")].first}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/rack-*/lib")].last}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/tilt*/lib")].first}" -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/sinatra*/lib")].first}" +$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{artifice,rack,tilt,sinatra}-*/lib")].map(&:to_s)) require "artifice" require "sinatra/base" diff --git a/spec/support/artifice/fail.rb b/spec/support/artifice/fail.rb index e15768a328..1059c6df4e 100644 --- a/spec/support/artifice/fail.rb +++ b/spec/support/artifice/fail.rb @@ -1,23 +1,39 @@ # frozen_string_literal: true -require File.expand_path("../../path.rb", __FILE__) - -# Set up pretend http gem server with FakeWeb -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gems.join("gems/artifice*/lib")].first.to_s -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gems.join("gems/rack-*/lib")].first.to_s -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gems.join("gems/rack-*/lib")].last.to_s -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gems.join("gems/tilt*/lib")].first.to_s -require "artifice" - -class Fail - def call(env) - raise(exception(env)) +require "net/http" +begin + require "net/https" +rescue LoadError + nil # net/https or openssl +end + +# We can't use artifice here because it uses rack + +module Artifice; end # for < 2.0, Net::HTTP::Persistent::SSLReuse + +class Fail < Net::HTTP + # Net::HTTP uses a @newimpl instance variable to decide whether + # to use a legacy implementation. Since we are subclassing + # Net::HTTP, we must set it + @newimpl = true + + def request(req, body = nil, &block) + raise(exception(req)) end - def exception(env) + # Ensure we don't start a connect here + def connect + end + + def exception(req) name = ENV.fetch("BUNDLER_SPEC_EXCEPTION") { "Errno::ENETUNREACH" } const = name.split("::").reduce(Object) {|mod, sym| mod.const_get(sym) } - const.new("host down: Bundler spec artifice fail! #{env["PATH_INFO"]}") + const.new("host down: Bundler spec artifice fail! #{req["PATH_INFO"]}") end end -Artifice.activate_with(Fail.new) + +# Replace Net::HTTP with our failing subclass +::Net.class_eval do + remove_const(:HTTP) + const_set(:HTTP, ::Fail) +end diff --git a/spec/support/artifice/windows.rb b/spec/support/artifice/windows.rb new file mode 100644 index 0000000000..c18ca454ec --- /dev/null +++ b/spec/support/artifice/windows.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true +require File.expand_path("../../path.rb", __FILE__) +include Spec::Path + +$LOAD_PATH.unshift Dir[base_system_gems.join("gems/artifice*/lib")].first.to_s +$LOAD_PATH.unshift(*Dir[base_system_gems.join("gems/rack-*/lib")]) +$LOAD_PATH.unshift Dir[base_system_gems.join("gems/tilt*/lib")].first.to_s +$LOAD_PATH.unshift Dir[base_system_gems.join("gems/sinatra*/lib")].first.to_s +require "artifice" +require "sinatra/base" + +Artifice.deactivate + +class Windows < Sinatra::Base + set :raise_errors, true + set :show_exceptions, false + + helpers do + def gem_repo + Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"] || Spec::Path.gem_repo1) + end + end + + files = ["specs.4.8.gz", + "prerelease_specs.4.8.gz", + "quick/Marshal.4.8/rcov-1.0-mswin32.gemspec.rz", + "gems/rcov-1.0-mswin32.gem"] + + files.each do |file| + get "/#{file}" do + File.read gem_repo.join(file) + end + end + + get "/gems/rcov-1.0-x86-mswin32.gem" do + halt 404 + end + + get "/api/v1/dependencies" do + halt 404 + end + + get "/versions" do + halt 500 + end +end + +Artifice.activate_with(Windows) diff --git a/spec/support/builders.rb b/spec/support/builders.rb index 7436779d15..bda808c0b2 100644 --- a/spec/support/builders.rb +++ b/spec/support/builders.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require "bundler/shared_helpers" +require "shellwords" module Spec module Builders @@ -79,8 +80,8 @@ module Spec end build_gem "platform_specific" do |s| - s.platform = Gem::Platform.local - s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Gem::Platform.local}'" + s.platform = Bundler.local_platform + s.write "lib/platform_specific.rb", "PLATFORM_SPECIFIC = '1.0.0 #{Bundler.local_platform}'" end build_gem "platform_specific" do |s| @@ -505,6 +506,10 @@ module Spec @spec.add_runtime_dependency(name, requirements) end + def development(name, requirements) + @spec.add_development_dependency(name, requirements) + end + def required_ruby_version=(*reqs) @spec.required_ruby_version = *reqs end @@ -660,14 +665,15 @@ module Spec if branch = options[:branch] raise "You can't specify `master` as the branch" if branch == "master" + escaped_branch = Shellwords.shellescape(branch) - if `git branch | grep #{branch}`.empty? - silently("git branch #{branch}") + if `git branch | grep #{escaped_branch}`.empty? + silently("git branch #{escaped_branch}") end - silently("git checkout #{branch}") + silently("git checkout #{escaped_branch}") elsif tag = options[:tag] - `git tag #{tag}` + `git tag #{Shellwords.shellescape(tag)}` elsif options[:remote] silently("git remote add origin file://#{options[:remote]}") elsif options[:push] diff --git a/spec/support/fakeweb/rack-1.0.0.marshal b/spec/support/fakeweb/rack-1.0.0.marshal deleted file mode 100644 index 383ce407f9..0000000000 --- a/spec/support/fakeweb/rack-1.0.0.marshal +++ /dev/null @@ -1,2 +0,0 @@ -[{ :dependencies[[" thin" >= 0["ruby-openid"
~> 2.0.0["mongrel" >= 0["memcache-client" >= 0[" fcgi" >= 0["camping" >= 0["test-spec" >= 0:
platform" ruby: name" rack:number" -1.0.0
\ No newline at end of file diff --git a/spec/support/fakeweb/windows.rb b/spec/support/fakeweb/windows.rb deleted file mode 100644 index f1f21eed3f..0000000000 --- a/spec/support/fakeweb/windows.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true -require File.expand_path("../../path.rb", __FILE__) -include Spec::Path - -files = ["specs.4.8.gz", - "prerelease_specs.4.8.gz", - "quick/Marshal.4.8/rcov-1.0-mswin32.gemspec.rz", - "gems/rcov-1.0-mswin32.gem"] - -# Set up pretend http gem server with FakeWeb -$LOAD_PATH.unshift "#{Dir[base_system_gems.join("gems/fakeweb*/lib")].first}" -require "fakeweb" - -FakeWeb.allow_net_connect = false - -files.each do |file| - FakeWeb.register_uri(:get, "http://localgemserver.test/#{file}", - :body => File.read("#{gem_repo1}/#{file}")) -end -FakeWeb.register_uri(:get, "http://localgemserver.test/gems/rcov-1.0-x86-mswin32.gem", - :status => ["404", "Not Found"]) - -FakeWeb.register_uri(:get, "http://localgemserver.test/api/v1/dependencies", - :status => ["404", "Not Found"]) - -FakeWeb.register_uri(:get, "http://localgemserver.test/versions", - :status => ["500", "Internal Server Error"]) diff --git a/spec/support/hax.rb b/spec/support/hax.rb index 2cca22c6ab..663d3527c5 100644 --- a/spec/support/hax.rb +++ b/spec/support/hax.rb @@ -1,12 +1,16 @@ # frozen_string_literal: true require "rubygems" -class Gem::Platform - @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) if ENV["BUNDLER_SPEC_PLATFORM"] +module Gem + class Platform + @local = new(ENV["BUNDLER_SPEC_PLATFORM"]) if ENV["BUNDLER_SPEC_PLATFORM"] + end + @platforms = [Gem::Platform::RUBY, Gem::Platform.local] end if ENV["BUNDLER_SPEC_VERSION"] module Bundler + remove_const(:VERSION) if const_defined?(:VERSION) VERSION = ENV["BUNDLER_SPEC_VERSION"].dup end end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index fe79604f30..1b2769ebf8 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -2,17 +2,17 @@ module Spec module Helpers def reset! - Dir["#{tmp}/{gems/*,*}"].each do |dir| - next if %(base remote1 gems rubygems).include?(File.basename(dir)) + Dir.glob("#{tmp}/{gems/*,*}", File::FNM_DOTMATCH).each do |dir| + next if %w(base remote1 gems rubygems . ..).include?(File.basename(dir)) if ENV["BUNDLER_SUDO_TESTS"] - `sudo rm -rf #{dir}` + `sudo rm -rf "#{dir}"` else FileUtils.rm_rf(dir) end end - FileUtils.mkdir_p(tmp) FileUtils.mkdir_p(home) - ENV["BUNDLE_DISABLE_POSTIT"] = "1" + FileUtils.mkdir_p(tmpdir) + ENV["BUNDLE_TRAMPOLINE_DISABLE"] = "1" Bundler.reset! Bundler.ui = nil Bundler.ui # force it to initialize @@ -84,22 +84,33 @@ module Spec with_sudo = options.delete(:sudo) sudo = with_sudo == :preserve_env ? "sudo -E" : "sudo" if with_sudo - options["no-color"] = true unless options.key?("no-color") || cmd.to_s.start_with?("exec", "exe", "ex", "e", "conf") + options["no-color"] = true unless options.key?("no-color") || cmd.to_s =~ /\A(e|ex|exe|exec|conf|confi|config)(\s|\z)/ bundle_bin = options.delete("bundle_bin") || File.expand_path("../../../exe/bundle", __FILE__) + if system_bundler = options.delete(:system_bundler) + bundle_bin = "-S bundle" + end + requires = options.delete(:requires) || [] - requires << File.expand_path("../fakeweb/" + options.delete(:fakeweb) + ".rb", __FILE__) if options.key?(:fakeweb) - requires << File.expand_path("../artifice/" + options.delete(:artifice) + ".rb", __FILE__) if options.key?(:artifice) + if artifice = options.delete(:artifice) { "fail" unless RSpec.current_example.metadata[:realworld] } + requires << File.expand_path("../artifice/#{artifice}.rb", __FILE__) + end requires << "support/hax" requires_str = requires.map {|r| "-r#{r}" }.join(" ") + load_path = [] + load_path << lib unless system_bundler + load_path << spec + load_path_str = "-I#{load_path.join(File::PATH_SEPARATOR)}" + env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}'" }.join(" ") + env["PATH"].gsub!("#{Path.root}/exe", "") if env["PATH"] && system_bundler args = options.map do |k, v| v == true ? " --#{k}" : " --#{k} #{v}" if v end.join - cmd = "#{env} #{sudo} #{Gem.ruby} -I#{lib}:#{spec} #{requires_str} #{bundle_bin} #{cmd}#{args}" + cmd = "#{env} #{sudo} #{Gem.ruby} #{load_path_str} #{requires_str} #{bundle_bin} #{cmd}#{args}" sys_exec(cmd) {|i, o, thr| yield i, o, thr if block_given? } end bang :bundle @@ -110,19 +121,8 @@ module Spec end def bundle_ruby(options = {}) - options["no-color"] = true unless options.key?("no-color") - - bundle_bin = File.expand_path("../../../exe/bundle_ruby", __FILE__) - - requires = options.delete(:requires) || [] - requires << File.expand_path("../fakeweb/" + options.delete(:fakeweb) + ".rb", __FILE__) if options.key?(:fakeweb) - 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 - cmd = "#{env}#{Gem.ruby} -I#{lib} #{requires_str} #{bundle_bin}" - - sys_exec(cmd) {|i, o, thr| yield i, o, thr if block_given? } + options["bundle_bin"] = File.expand_path("../../../exe/bundle_ruby", __FILE__) + bundle("", options) end def ruby(ruby, options = {}) @@ -208,7 +208,11 @@ module Spec end def gemfile(*args) - create_file("Gemfile", *args) + if args.empty? + File.open("Gemfile", "r", &:read) + else + create_file("Gemfile", *args) + end end def lockfile(*args) @@ -241,12 +245,20 @@ module Spec end def install_gems(*gems) + options = gems.last.is_a?(Hash) ? gems.pop : {} + gem_repo = options.fetch(:gem_repo) { gem_repo1 } gems.each do |g| - path = "#{gem_repo1}/gems/#{g}.gem" + path = if g == :bundler + Dir.chdir(root) { gem_command! :build, "#{root}/bundler.gemspec" } + bundler_path = root + "bundler-#{Bundler::VERSION}.gem" + else + "#{gem_repo}/gems/#{g}.gem" + end raise "OMG `#{path}` does not exist!" unless File.exist?(path) - gem_command! :install, "--no-rdoc --no-ri --ignore-dependencies #{path}" + gem_command! :install, "--no-rdoc --no-ri --ignore-dependencies '#{path}'" + bundler_path && bundler_path.rmtree end end diff --git a/spec/support/path.rb b/spec/support/path.rb index cf77adb509..2b929003fb 100644 --- a/spec/support/path.rb +++ b/spec/support/path.rb @@ -89,6 +89,10 @@ module Spec bundled_app ".bundle", "plugin", "gems", *args end + def tmpdir(*args) + tmp "tmpdir", *args + end + extend self end end diff --git a/spec/support/rubygems_ext.rb b/spec/support/rubygems_ext.rb index 4ddbc3312a..88886c3f1f 100644 --- a/spec/support/rubygems_ext.rb +++ b/spec/support/rubygems_ext.rb @@ -9,8 +9,9 @@ module Spec # rack 2.x requires Ruby version >= 2.2.2. # artifice doesn't support rack 2.x now. "rack" => "< 2", - "fakeweb artifice compact_index" => nil, - "sinatra" => "1.2.7", + "artifice" => "~> 0.6.0", + "compact_index" => "~> 0.11.0", + "sinatra" => "~> 1.4.7", # Rake version has to be consistent for tests to pass "rake" => "10.0.2", # 3.0.0 breaks 1.9.2 specs @@ -36,19 +37,25 @@ module Spec FileUtils.rm_rf(Path.base_system_gems) FileUtils.mkdir_p(Path.base_system_gems) puts "installing gems for the tests to use..." - DEPS.sort {|a, _| a[1].nil? ? 1 : -1 }.each {|n, v| install_gem(n, v) } + install_gems(DEPS) File.open(manifest_path, "w") {|f| f << manifest.join } end ENV["HOME"] = Path.home.to_s + ENV["TMPDIR"] = Path.tmpdir.to_s Gem::DefaultUserInteraction.ui = Gem::SilentUI.new end - def self.install_gem(name, version = nil) - cmd = "gem install #{name} --no-rdoc --no-ri" - cmd += " --version '#{version}'" if version - system(cmd) || raise("Installing gem #{name} for the tests to use failed!") + def self.install_gems(gems) + reqs, no_reqs = gems.partition {|_, req| !req.nil? && !req.split(" ").empty? } + reqs = reqs.sort_by {|name, _| name == "rack" ? 0 : 1 } # TODO: remove when we drop ruby 1.8.7 support + no_reqs.map!(&:first) + reqs.map! {|name, req| "'#{name}:#{req}'" } + deps = reqs.concat(no_reqs).join(" ") + cmd = "gem install #{deps} --no-rdoc --no-ri --conservative" + puts cmd + system(cmd) || raise("Installing gems #{deps} for the tests to use failed!") end end end diff --git a/spec/update/gems/post_install_spec.rb b/spec/update/gems/post_install_spec.rb new file mode 100644 index 0000000000..5a4fe7f321 --- /dev/null +++ b/spec/update/gems/post_install_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe "bundle update" do + let(:config) {} + + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack', "< 1.0" + gem 'thin' + G + + bundle! "config #{config}" if config + + bundle! :install + end + + shared_examples "a config observer" do + context "when ignore post-install messages for gem is set" do + let(:config) { "ignore_messages.rack true" } + + it "doesn't display gem's post-install message" do + expect(out).not_to include("Rack's post install message") + end + end + + context "when ignore post-install messages for all gems" do + let(:config) { "ignore_messages true" } + + it "doesn't display any post-install messages" do + expect(out).not_to include("Post-install message") + end + end + end + + shared_examples "a post-install message outputter" do + it "should display post-install messages for updated gems" do + expect(out).to include("Post-install message from rack:") + expect(out).to include("Rack's post install message") + end + + it "should not display the post-install message for non-updated gems" do + expect(out).not_to include("Thin's post install message") + end + end + + context "when listed gem is updated" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + gem 'thin' + G + + bundle! :update + end + + it_behaves_like "a post-install message outputter" + it_behaves_like "a config observer" + end + + context "when dependency triggers update" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack-obama' + gem 'thin' + G + + bundle! :update + end + + it_behaves_like "a post-install message outputter" + it_behaves_like "a config observer" + end +end diff --git a/spec/update/git_spec.rb b/spec/update/git_spec.rb index b67ddda202..021c8c942b 100644 --- a/spec/update/git_spec.rb +++ b/spec/update/git_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "bundle update" do +RSpec.describe "bundle update" do describe "git sources" do it "floats on a branch when :branch is used" do build_git "foo", "1.0" diff --git a/spec/update/path_spec.rb b/spec/update/path_spec.rb index aa220ad60e..5ac4f7b1fe 100644 --- a/spec/update/path_spec.rb +++ b/spec/update/path_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -describe "path sources" do +RSpec.describe "path sources" do describe "bundle update --source" do it "shows the previous version of the gem when updated from path source" do build_lib "activesupport", "2.3.5", :path => lib_path("rails/activesupport") diff --git a/task/release.rake b/task/release.rake new file mode 100644 index 0000000000..5653969084 --- /dev/null +++ b/task/release.rake @@ -0,0 +1,115 @@ +# frozen_string_literal: true +namespace :release do + def confirm(prompt = "") + loop do + print(prompt) + print(": ") unless prompt.empty? + break if $stdin.gets.strip == "y" + end + rescue Interrupt + abort + end + + def gh_api_request(opts) + require "net/http" + require "json" + host = opts.fetch(:host) { "https://api.github.com/" } + path = opts.fetch(:path) + response = Net::HTTP.get_response(URI.join(host, path)) + + links = Hash[*(response["Link"] || "").split(", ").map do |link| + href, name = link.match(/<(.*?)>; rel="(\w+)"/).captures + + [name.to_sym, href] + end.flatten] + + parsed_response = JSON.parse(response.body) + + if n = links[:next] + parsed_response.concat gh_api_request(:host => host, :path => n) + end + + parsed_response + end + + desc "Make a patch release with the PRs from master in the patch milestone" + task :patch, :version do |_t, args| + version = args.version + + version ||= begin + version = BUNDLER_SPEC.version + segments = version.segments + if segments.last.is_a?(String) + segments << "1" + else + segments[-1] += 1 + end + segments.join(".") + end + + confirm "You are about to release #{version}, currently #{BUNDLER_SPEC.version}" + + milestones = gh_api_request(:path => "repos/bundler/bundler/milestones?state=open") + unless patch_milestone = milestones.find {|m| m["title"] == version } + abort "failed to find #{version} milestone on GitHub" + end + prs = gh_api_request(:path => "repos/bundler/bundler/issues?milestone=#{patch_milestone["number"]}&state=all") + prs.map! do |pr| + abort "#{pr["html_url"]} hasn't been closed yet!" unless pr["state"] == "closed" + next unless pr["pull_request"] + pr["number"].to_s + end.compact + + version_file = "lib/bundler/version.rb" + version_contents = File.read(version_file) + unless version_contents.sub!(/^(\s*VERSION = )"#{Gem::Version::VERSION_PATTERN}"/, "\\1#{version.to_s.dump}") + abort "failed to update #{version_file}, is it in the expected format?" + end + File.open(version_file, "w") {|f| f.write(version_contents) } + + BUNDLER_SPEC.version = version + + branch = version.split(".", 3)[0, 2].push("stable").join("-") + sh("git", "checkout", branch) + + commits = `git log --oneline origin/master --`.split("\n").map {|l| l.split(/\s/, 2) }.reverse + commits.select! {|_sha, message| message =~ /(Auto merge of|Merge pull request) ##{Regexp.union(*prs)}/ } + + abort "Could not find commits for all PRs" unless commits.size == prs.size + + unless system("git", "cherry-pick", "-x", "-m", "1", *commits.map(&:first)) + abort unless system("zsh") + end + + prs.each do |pr| + system("open", "https://github.com/bundler/bundler/pull/#{pr}") + confirm "Add to the changelog" + end + + confirm "Update changelog" + sh("git", "commit", "-am", "Version #{version} with changelog") + sh("rake", "release") + sh("git", "checkout", "master") + sh("git", "pull") + sh("git", "merge", "v#{version}", "--no-edit") + sh("git", "push") + end + + desc "Open all PRs that have not been included in a stable release" + task :open_unreleased_prs do + def prs(on = "master") + commits = `git log --oneline origin/#{on} --`.split("\n") + commits.reverse_each.map {|c| c =~ /(Auto merge of|Merge pull request) #(\d+)/ && $2 }.compact + end + + last_stable = `git ls-remote origin`.split("\n").map {|r| r =~ %r{refs/tags/v([\d.]+)$} && $1 }.compact.map {|v| Gem::Version.create(v) }.max + last_stable = last_stable.segments[0, 2].<<("stable").join("-") + + in_release = prs("HEAD") - prs(last_stable) + + in_release.each do |pr| + system("open", "https://github.com/bundler/bundler/pull/#{pr}") + confirm + end + end +end |