diff options
author | Asutosh Palai <asupalai@gmail.com> | 2016-06-07 21:37:44 +0530 |
---|---|---|
committer | Asutosh Palai <asupalai@gmail.com> | 2016-06-07 21:37:44 +0530 |
commit | f1d2abfb3cefa963df0089e0b858265a817b652c (patch) | |
tree | 3ce92da25b4698a41564b42e7fc4744a8df53678 | |
parent | 81ea9335591768560dcc917a1d2e848ff938f11e (diff) | |
parent | 8d7b671909d9b75a9e1e64889f6ba45b302e3940 (diff) | |
download | bundler-f1d2abfb3cefa963df0089e0b858265a817b652c.tar.gz |
Merge branch 'master' into plugin
38 files changed, 410 insertions, 141 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 853706f643..5c5b50c0bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.12.5 (2016-05-25) + +Bugfixes: + - only take over `--help` on `bundle exec` when the first two arguments are `exec` and `--help` (#4596, @segiddins) + - don't require `require: true` dependencies that are excluded via `env` or `install_if` (@BrianHawley) + - reduce the number of threads used simultaneously by bundler (#4367, @will-in-wi) + ## 1.12.4 (2016-05-16) Bugfixes: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49586ea8ce..8349280ddd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ If you're having a problem, please see [ISSUES](https://github.com/bundler/bundl # Requesting Features -Head on over to the [Bundler features](https://github.com/bundler/bundler-features) project, or any of the lists or channels listed below. Feature-wise, we consider Bundler stable, so the core team does not tend to work on feature suggestions. Pull requests are welcome though! +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! # Discussing Bundler diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 994ed95f21..8676904244 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -8,12 +8,11 @@ You can start learning about Bundler by reading [the documentation](http://bundl 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](http://github.com/indirect) or [@hone](http://github.com/hone). They are both happy to provide help working through your first bugfix or thinking through the problem you’re trying to resolve. +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. +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 @@ -99,15 +98,14 @@ If you can reproduce an issue, you're well on your way to fixing it. :) Fixing i 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 +# 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-features/issues/new) in the bundler-features repo to discuss your feature. + 1. [Create an issue](https://github.com/bundler/bundler/issues/new) with the [`feature-request` label](https://github.com/bundler/bundler/labels/feature-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. Put a line in the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md) summarizing your changes under the next release under the "Features" heading. - 5. Send us a [pull request](https://help.github.com/articles/using-pull-requests) from your feature branch. + 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. diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 3e5b7e0009..757643d235 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -233,6 +233,8 @@ module Bundler "Overwrite existing binstubs if they exist" method_option "path", :type => :string, :lazy_default => "bin", :banner => "Binstub destination directory (default bin)" + method_option "standalone", :type => :array, :lazy_default => [], :banner => + "Make binstubs that can work without the Bundler runtime" def binstubs(*gems) require "bundler/cli/binstubs" Binstubs.new(options, gems).run @@ -427,6 +429,8 @@ module Bundler "the path the lockfile should be written to" 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" def lock require "bundler/cli/lock" Lock.new(options).run diff --git a/lib/bundler/cli/binstubs.rb b/lib/bundler/cli/binstubs.rb index 8452ca3bfe..e8982e90b4 100644 --- a/lib/bundler/cli/binstubs.rb +++ b/lib/bundler/cli/binstubs.rb @@ -29,6 +29,8 @@ module Bundler if spec.name == "bundler" Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems." + elsif options[:standalone] + installer.generate_standalone_bundler_executable_stubs(spec) else installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true) end diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index e95e9ff636..ba9a32655d 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -26,6 +26,11 @@ module Bundler definition = Bundler.definition(true) end + options["add-platform"].each do |platform| + platform = Gem::Platform.new(platform) + definition.add_platform(platform) + end + definition.resolve_remotely! unless options[:local] if print diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 294d6567c6..02f8ee0957 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -87,8 +87,7 @@ module Bundler @unlock[:sources] ||= [] current_platform = Bundler.rubygems.platforms.map {|p| generic(p) }.compact.last - @new_platform = !@platforms.include?(current_platform) - @platforms |= [current_platform] + add_platform(current_platform) @path_changes = converge_paths eager_unlock = expand_dependencies(@unlock[:gems]) @@ -137,8 +136,17 @@ module Bundler # @return [Bundler::SpecSet] def specs @specs ||= begin - specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies) - + begin + specs = resolve.materialize(Bundler.settings[:cache_all_platforms] ? dependencies : requested_dependencies) + 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 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 " \ + "to a different version of #{locked_gem} that hasn't been removed in order to install." + end unless specs["bundler"].any? local = Bundler.settings[:frozen] ? rubygems_index : index bundler = local.search(Gem::Dependency.new("bundler", VERSION)).last @@ -216,7 +224,7 @@ module Bundler source.dependency_names = dependency_names.dup idx.add_source source.specs dependency_names -= pinned_spec_names(source.specs) - dependency_names.push(*source.unmet_deps).uniq! + dependency_names.concat(source.unmet_deps).uniq! end end end @@ -358,10 +366,20 @@ module Bundler changed = [] gemfile_sources = sources.lock_sources - if @locked_sources != gemfile_sources - new_sources = gemfile_sources - @locked_sources - deleted_sources = @locked_sources - gemfile_sources + new_sources = gemfile_sources - @locked_sources + deleted_sources = @locked_sources - gemfile_sources + + new_deps = @dependencies - @locked_deps + deleted_deps = @locked_deps - @dependencies + + # Check if it is possible that the source is only changed thing + if (new_deps.empty? && deleted_deps.empty?) && (!new_sources.empty? && !deleted_sources.empty?) + new_sources.reject! {|source| source.is_a_path? && source.path.exist? } + deleted_sources.reject! {|source| source.is_a_path? && source.path.exist? } + end + + if @locked_sources != gemfile_sources if new_sources.any? added.concat new_sources.map {|source| "* source: #{source}" } end @@ -371,11 +389,7 @@ module Bundler end end - new_deps = @dependencies - @locked_deps - deleted_deps = @locked_deps - @dependencies - added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? - if deleted_deps.any? deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } end @@ -424,6 +438,11 @@ module Bundler end end + def add_platform(platform) + @new_platform ||= !@platforms.include?(platform) + @platforms |= [platform] + end + attr_reader :sources private :sources @@ -529,7 +548,15 @@ module Bundler def converge_dependencies (@dependencies + @locked_deps).each do |dep| - dep.source = sources.get(dep.source) if dep.source + locked_source = @locked_deps.select {|d| d.name == dep.name }.last + # This is to make sure that if bundler is installing in deployment mode and + # after locked_source and sources don't match, we still use locked_source. + if Bundler.settings[:frozen] && !locked_source.nil? && + locked_source.respond_to?(:source) && locked_source.source.instance_of?(Source::Path) && locked_source.source.path.exist? + dep.source = locked_source.source + elsif dep.source + dep.source = sources.get(dep.source) + end end Set.new(@dependencies) != Set.new(@locked_deps) end @@ -690,5 +717,11 @@ module Bundler end current == proposed end + + def extract_gem_info(error) + # This method will extract the error message like "Could not find foo-1.2.3 in any of the sources" + # to an array. The first element will be the gem name (e.g. foo), the second will be the version number. + error.message.scan(/Could not find (\w+)-(\d+(?:\.\d+)+)/).flatten + end end end diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index dacfeaa24f..4208f9b575 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -67,16 +67,16 @@ module Bundler "#{file}. Make sure you can build the gem, then try again" end + @gemspecs << spec + gem_platforms = Bundler::Dependency::REVERSE_PLATFORM_MAP[Bundler::GemHelpers.generic_local_platform] - gem spec.name, :path => path, :glob => glob, :platforms => gem_platforms + gem spec.name, :name => spec.name, :path => path, :glob => glob, :platforms => gem_platforms group(development_group) do spec.development_dependencies.each do |dep| - gem dep.name, *(dep.requirement.as_list + [:type => :development]) + gem dep.name, *(dep.requirement.as_list + [:type => :development, :platforms => gem_platforms]) end end - - @gemspecs << gemspecs.first when 0 raise InvalidOption, "There are no gemspecs at #{expanded_path}" else @@ -149,7 +149,11 @@ module Bundler end def path(path, options = {}, &blk) - source_options = normalize_hash(options).merge("path" => Pathname.new(path), "root_path" => gemfile_root) + source_options = normalize_hash(options).merge( + "path" => Pathname.new(path), + "root_path" => gemfile_root, + "gemspec" => gemspecs.find {|g| g.name == options["name"] } + ) source = @sources.add_path_source(source_options) with_source(source, &blk) end diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index b26fdaf9cc..69d05167e8 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -115,8 +115,8 @@ module Bundler end end - def build_dependency(name, *requirements) - Gem::Dependency.new(name, *requirements) + def build_dependency(name, requirements) + Gem::Dependency.new(name, requirements) rescue ArgumentError => e raise unless e.message.include?(ILLFORMED_MESSAGE) puts # we shouldn't print the error message on the "fetching info" status line diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb index baf6a10f31..47e720a201 100644 --- a/lib/bundler/env.rb +++ b/lib/bundler/env.rb @@ -44,8 +44,8 @@ module Bundler if print_gemspecs dsl = Dsl.new.tap {|d| d.eval_gemfile(Bundler.default_gemfile) } dsl.gemspecs.each do |gs| - out << "\n#{Pathname.new(gs).basename}" - out << "\n\n " << read_file(gs).gsub(/\n/, "\n ") << "\n" + out << "\n#{File.basename(gs.loaded_from)}" + out << "\n\n " << read_file(gs.loaded_from).gsub(/\n/, "\n ") << "\n" end end diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 7da22f8ed1..fb9a407791 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -63,7 +63,7 @@ module Bundler FAIL_ERRORS = begin fail_errors = [AuthenticationRequiredError, BadAuthenticationError, FallbackError] fail_errors << Gem::Requirement::BadRequirementError if defined?(Gem::Requirement::BadRequirementError) - fail_errors.push(*NET_ERRORS.map {|e| SharedHelpers.const_get_safely(e, Net) }.compact) + fail_errors.concat(NET_ERRORS.map {|e| SharedHelpers.const_get_safely(e, Net) }.compact) end.freeze class << self diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index 31557be053..c0e1b3b4cf 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -41,7 +41,7 @@ module Bundler deps = compact_index_client.dependencies(remaining_gems) next_gems = deps.map {|d| d[3].map(&:first).flatten(1) }.flatten(1).uniq deps.each {|dep| gem_info << dep } - complete_gems.push(*deps.map(&:first)).uniq! + complete_gems.concat(deps.map(&:first)).uniq! remaining_gems = next_gems - complete_gems end @@ -89,7 +89,7 @@ module Bundler worker_name = "Compact Index (#{display_uri.host})" worker = Bundler::Worker.new(25, worker_name, func) inputs.each {|input| worker.enq(input) } - inputs.map { worker.deq } + inputs.map { worker.deq }.tap { worker.stop } end end end diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index 0e67375de8..a145837a88 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -54,7 +54,7 @@ module Bundler gem_list = [] gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| marshalled_deps = downloader.fetch(dependency_api_uri(names)).body - gem_list.push(*Bundler.load_marshal(marshalled_deps)) + gem_list.concat(Bundler.load_marshal(marshalled_deps)) end gem_list end @@ -64,7 +64,7 @@ module Bundler spec_list = [] gem_list.each do |s| - deps_list.push(*s[:dependencies].map(&:first)) + deps_list.concat(s[:dependencies].map(&:first)) deps = s[:dependencies].map {|n, d| [n, d.split(", ")] } spec_list.push([s[:name], s[:number], s[:platform], deps]) end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index 5c49034280..c28a120990 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -21,15 +21,14 @@ module Bundler @sources = [] @cache = {} @specs = Hash.new {|h, k| h[k] = {} } - @all_specs = Hash.new {|h, k| h[k] = [] } + @all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH } end def initialize_copy(o) - super - @sources = @sources.dup + @sources = o.sources.dup @cache = {} @specs = Hash.new {|h, k| h[k] = {} } - @all_specs = Hash.new {|h, k| h[k] = [] } + @all_specs = Hash.new {|h, k| h[k] = EMPTY_SEARCH } o.specs.each do |name, hash| @specs[name] = hash.dup @@ -49,7 +48,7 @@ module Bundler end def search_all(name) - all_matches = @all_specs[name] + local_search(name) + all_matches = local_search(name) + @all_specs[name] @sources.each do |source| all_matches.concat(source.search_all(name)) end @@ -60,19 +59,18 @@ module Bundler # about, returning all of the results. def search(query, base = nil) results = local_search(query, base) - seen = Set.new(results.map {|spec| [spec.name, spec.version, spec.platform] }) + seen = results.map(&:full_name).to_set @sources.each do |source| source.search(query, base).each do |spec| - lookup = [spec.name, spec.version, spec.platform] - unless seen.include?(lookup) - results << spec - seen << lookup - end + results << spec if seen.add?(spec.full_name) end end - results.sort_by {|s| [s.version, s.platform.to_s == RUBY ? NULL : s.platform.to_s] } + results.sort_by do |s| + platform_string = s.platform.to_s + [s.version, platform_string == RUBY ? NULL : platform_string] + end end def local_search(query, base = nil) @@ -90,14 +88,15 @@ module Bundler def <<(spec) @specs[spec.name][spec.full_name] = spec - spec end def each(&blk) + return enum_for(:each) unless blk specs.values.each do |spec_sets| spec_sets.values.each(&blk) end + sources.each {|s| s.each(&blk) } end # returns a list of the dependencies @@ -109,17 +108,17 @@ module Bundler def dependency_names names = [] - each {|s| names.push(*s.dependencies.map(&:name)) } + each {|s| names.concat(s.dependencies.map(&:name)) } names.uniq end def use(other, override_dupes = false) return unless other other.each do |s| - if (dupes = search_by_spec(s)) && dupes.any? - @all_specs[s.name] = [s] + dupes + if (dupes = search_by_spec(s)) && !dupes.empty? + # safe to << since it's a new array when it has contents + @all_specs[s.name] = dupes << s next unless override_dupes - self << s end self << s end @@ -154,8 +153,10 @@ module Bundler def search_by_dependency(dependency, base = nil) @cache[base || false] ||= {} @cache[base || false][dependency] ||= begin - specs = specs_by_name(dependency.name) + (base || []) + specs = specs_by_name(dependency.name) + specs += base if base found = specs.select do |spec| + next true if spec.source.is_a?(Source::Gemspec) if base # allow all platforms when searching from a lockfile dependency.matches_spec?(spec) else @@ -174,25 +175,11 @@ module Bundler end end + EMPTY_SEARCH = [].freeze + def search_by_spec(spec) spec = @specs[spec.name][spec.full_name] - spec ? [spec] : [] - end - - if RUBY_VERSION < "1.9" - def same_version?(a, b) - regex = /^(.*?)(?:\.0)*$/ - a.to_s[regex, 1] == b.to_s[regex, 1] - end - else - def same_version?(a, b) - a == b - end - end - - def spec_satisfies_dependency?(spec, dep) - return false unless dep.name == spec.name - dep.requirement.satisfied_by?(spec.version) + spec ? [spec] : EMPTY_SEARCH end end end diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index 1ad78c61d1..b6bddda5dd 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -48,8 +48,17 @@ def gemfile(install = false, options = {}, &gemfile) def definition.lock(*); end definition.validate_ruby! - if install - Bundler.ui = ui + missing_specs = proc do + begin + !definition.missing_specs.empty? + rescue Bundler::GemNotFound + definition.instance_variable_set(:@index, nil) + true + end + end + + Bundler.ui = ui if install + if install || missing_specs.call Bundler::Installer.install(Bundler.root, definition, :system => true) Bundler::Installer.post_install_messages.each do |name, message| Bundler.ui.info "Post-install message from #{name}:\n#{message}" @@ -58,7 +67,7 @@ def gemfile(install = false, options = {}, &gemfile) runtime = Bundler::Runtime.new(nil, definition) runtime.setup.require - +ensure bundler_module = class << Bundler; self; end - bundler_module.send(:define_method, :root, old_root) + bundler_module.send(:define_method, :root, old_root) if old_root end diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index 29735fc6ab..6a02897c63 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -66,6 +66,10 @@ module Bundler [@name, @version, @platform == Gem::Platform::RUBY ? -1 : 1] end + def to_s + "#<#{self.class} name=#{name} version=#{version} platform=#{platform}>" + end + private def _remote_specification @@ -75,11 +79,7 @@ module Bundler end def method_missing(method, *args, &blk) - if Gem::Specification.new.respond_to?(method) - _remote_specification.send(method, *args, &blk) - else - super - end + _remote_specification.send(method, *args, &blk) end end end diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index 48f0d468b6..918b3c977b 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -106,14 +106,8 @@ module Bundler specs.values end - def activate_platform(platform) - unless @activated.include?(platform) - if for?(platform, nil) - @activated << platform - return __dependencies[platform] || [] - end - end - [] + def activate_platform!(platform) + @activated << platform if !@activated.include?(platform) && for?(platform, nil) end def name @@ -252,28 +246,34 @@ module Bundler platform = dependency.__platform dependency = dependency.dep unless dependency.is_a? Gem::Dependency search = @search_for[dependency] ||= begin - index = @source_requirements[dependency.name] || @index + index = index_for(dependency) results = index.search(dependency, @base[dependency.name]) if vertex = @base_dg.vertex_named(dependency.name) locked_requirement = vertex.payload.requirement end if results.any? - version = results.first.version - nested = [[]] + nested = [] results.each do |spec| - if spec.version != version - nested << [] - version = spec.version + version, specs = nested.last + if version == spec.version + specs << spec + else + nested << [spec.version, [spec]] end - nested.last << spec end - groups = nested.map {|a| SpecGroup.new(a) } - !locked_requirement ? groups : groups.select {|sg| locked_requirement.satisfied_by? sg.version } + nested.reduce([]) do |groups, (version, specs)| + next groups if locked_requirement && !locked_requirement.satisfied_by?(version) + groups << SpecGroup.new(specs) + end else [] end end - search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform(platform) } + search.select {|sg| sg.for?(platform, @ruby_version) }.each {|sg| sg.activate_platform!(platform) } + end + + def index_for(dependency) + @source_requirements[dependency.name] || @index end def name_for(dependency) @@ -293,7 +293,7 @@ module Bundler end def requirement_satisfied_by?(requirement, activated, spec) - requirement.matches_spec?(spec) + requirement.matches_spec?(spec) || spec.source.is_a?(Source::Gemspec) end def sort_dependencies(dependencies, activated, conflicts) @@ -314,14 +314,12 @@ module Bundler if (base = @base[dependency.name]) && !base.empty? dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1 else - base_dep = Dependency.new dependency.name, ">= 0.a" - all = search_for(DepProxy.new base_dep, dependency.__platform).size.to_f - if all.zero? - 0 - elsif (search = search_for(dependency).size.to_f) == all && all == 1 - 0 + all = index_for(dependency).search(dependency.name).size + if all <= 1 + all else - search / all + search = search_for(dependency).size + search - all end end end diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index 8a0fdbaeeb..41ebc87c77 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -212,7 +212,7 @@ module Bundler # Fetch all specs, minus prerelease specs spec_list = fetch_specs(true, false) # Then fetch the prerelease specs - fetch_prerelease_specs.each {|k, v| spec_list[k].push(*v) } + fetch_prerelease_specs.each {|k, v| spec_list[k].concat(v) } spec_list.values.first ensure @@ -605,7 +605,7 @@ module Bundler specs = fetch_specs(source, remote, "specs") pres = fetch_specs(source, remote, "prerelease_specs") || [] - specs.push(*pres) + specs.concat(pres) end def download_gem(spec, uri, path) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 6f02ad8512..3a86fe9226 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -68,9 +68,9 @@ module Bundler groups = [:default] if groups.empty? @definition.dependencies.each do |dep| - # Skip the dependency if it is not in any of the requested - # groups - next unless (dep.groups & groups).any? && dep.current_platform? + # Skip the dependency if it is not in any of the requested groups, or + # not for the current platform, or doesn't match the gem constraints. + next unless (dep.groups & groups).any? && dep.should_include? required_file = nil @@ -117,16 +117,9 @@ module Bundler Bundler.ui.info "Updating files in #{Bundler.settings.app_cache_path}" - # Do not try to cache specification for the gem described any of the gemspecs - root_gem_names = nil - if gemspec_cache_hash = Bundler.instance_variable_get(:@gemspec_cache) - gemspecs = gemspec_cache_hash.values - root_gem_names = gemspecs.map(&:name) - end - specs.each do |spec| next if spec.name == "bundler" - next if !Dir.glob("*.gemspec").empty? && spec.source.class == Bundler::Source::Path && root_gem_names.include?(spec.name) + next if spec.source.is_a?(Source::Gemspec) spec.source.send(:fetch_gem, spec) if Bundler.settings[:cache_all_platforms] && spec.source.respond_to?(:fetch_gem, true) spec.source.cache(spec, custom_path) if spec.source.respond_to?(:cache) end diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index eee4ade45b..afa7d91838 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true module Bundler class Source - autoload :Rubygems, "bundler/source/rubygems" - autoload :Path, "bundler/source/path" + autoload :Gemspec, "bundler/source/gemspec" autoload :Git, "bundler/source/git" + autoload :Path, "bundler/source/path" + autoload :Rubygems, "bundler/source/rubygems" attr_accessor :dependency_names diff --git a/lib/bundler/source/gemspec.rb b/lib/bundler/source/gemspec.rb new file mode 100644 index 0000000000..37e9a43945 --- /dev/null +++ b/lib/bundler/source/gemspec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +module Bundler + class Source + class Gemspec < Path + attr_reader :gemspec + + def initialize(options) + super + @gemspec = options["gemspec"] + end + end + end +end diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index bfead5c357..bbdd30b1e6 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -60,8 +60,8 @@ module Bundler end def eql?(other) - other.instance_of?(Path) && - expanded_path == expand(other.path) && + return unless other.class == Path || other.class == Gemspec + expanded_path == expand(other.path) && version == other.version end @@ -111,6 +111,10 @@ module Bundler Bundler.root end + def is_a_path? + instance_of?(Path) + end + private def expanded_path diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 369de4fcc0..a7721de870 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -27,6 +27,7 @@ module Bundler end def remote! + @specs = nil @allow_remote = true end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index dc62c8926a..0595cbdcf4 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -12,7 +12,11 @@ module Bundler end def add_path_source(options = {}) - add_source_to_list Source::Path.new(options), path_sources + if options["gemspec"] + add_source_to_list Source::Gemspec.new(options), path_sources + else + add_source_to_list Source::Path.new(options), path_sources + end end def add_git_source(options = {}) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index db93547b5f..b1e8a49e25 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -7,5 +7,5 @@ module Bundler # We're doing this because we might write tests that deal # with other versions of bundler and we are unsure how to # handle this better. - VERSION = "1.12.4" unless defined?(::Bundler::VERSION) + VERSION = "1.12.5" unless defined?(::Bundler::VERSION) end diff --git a/man/gemfile.5.ronn b/man/gemfile.5.ronn index b707e2a5c5..5b39268657 100644 --- a/man/gemfile.5.ronn +++ b/man/gemfile.5.ronn @@ -480,6 +480,10 @@ options, which control where bundler looks for the `.gemspec`, the glob it uses for the gemspec (defaults to: "{,*,*/*}.gemspec"), what named `.gemspec` it uses (if more than one is present), and which group development dependencies are included in. +When a `gemspec` dependency encounters version conflicts during resolution, the +local version under development will always be selected -- even if there are +remote versions that better match other requirements for the `gemspec` gem. + ## SOURCE PRIORITY When attempting to locate a gem to satisfy a gem requirement, diff --git a/spec/bundler/endpoint_specification_spec.rb b/spec/bundler/endpoint_specification_spec.rb index fccf1842fc..cdf81cd0f7 100644 --- a/spec/bundler/endpoint_specification_spec.rb +++ b/spec/bundler/endpoint_specification_spec.rb @@ -16,25 +16,26 @@ describe Bundler::EndpointSpecification do let(:requirement2) { ">= 1.1.7" } it "should return a Gem::Dependency" do - expect(subject.send(:build_dependency, name, requirement1, requirement2)).to be_instance_of(Gem::Dependency) + expect(subject.send(:build_dependency, name, [requirement1, requirement2])). + to eq(Gem::Dependency.new(name, requirement1, requirement2)) end context "when an ArgumentError occurs" do before do - allow(Gem::Dependency).to receive(:new).with(name, requirement1, requirement2) { + allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) { raise ArgumentError.new("Some error occurred") } end it "should raise the original error" do - expect { subject.send(:build_dependency, name, requirement1, requirement2) }.to raise_error( + expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error( ArgumentError, "Some error occurred") end end context "when there is an ill formed requirement" do before do - allow(Gem::Dependency).to receive(:new).with(name, requirement1, requirement2) { + allow(Gem::Dependency).to receive(:new).with(name, [requirement1, requirement2]) { raise ArgumentError.new("Ill-formed requirement [\"#<YAML::Syck::DefaultKey") } # Eliminate extra line break in rspec output due to `puts` in `#build_dependency` @@ -42,7 +43,7 @@ describe Bundler::EndpointSpecification do end it "should raise a Bundler::GemspecError with invalid gemspec message" do - expect { subject.send(:build_dependency, name, requirement1, requirement2) }.to raise_error( + expect { subject.send(:build_dependency, name, [requirement1, requirement2]) }.to raise_error( Bundler::GemspecError, /Unfortunately, the gem foo \(1\.0\.0\) has an invalid gemspec/) end end diff --git a/spec/bundler/fetcher/compact_index_spec.rb b/spec/bundler/fetcher/compact_index_spec.rb new file mode 100644 index 0000000000..e111d8a3b6 --- /dev/null +++ b/spec/bundler/fetcher/compact_index_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +require "spec_helper" + +describe Bundler::Fetcher::CompactIndex do + let(:downloader) { double(:downloader) } + let(:remote) { double(:remote, :cache_slug => "lsjdf") } + let(:display_uri) { URI("http://sampleuri.com") } + let(:compact_index) { described_class.new(downloader, remote, display_uri) } + + describe "#specs_for_names" 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 + expect(thread_count).to eq 1 + end + + it "calls worker#stop during the run" do + expect_any_instance_of(Bundler::Worker).to receive(:stop).at_least(:once) + + compact_index.specs_for_names(["lskdjf"]) + end + end +end diff --git a/spec/bundler/remote_specification_spec.rb b/spec/bundler/remote_specification_spec.rb index 527f48facc..6a8e9a6434 100644 --- a/spec/bundler/remote_specification_spec.rb +++ b/spec/bundler/remote_specification_spec.rb @@ -170,13 +170,5 @@ describe Bundler::RemoteSpecification do subject.missing_method_call end end - - context "and is not present in Gem::Specification" do - before { allow_any_instance_of(Gem::Specification).to receive(:respond_to?).and_return(false) } - - it "should throw method missing error" do - expect { subject.missing_method_call }.to raise_error(NoMethodError) - end - end end end diff --git a/spec/commands/binstubs_spec.rb b/spec/commands/binstubs_spec.rb index ee319e016c..338f07100a 100644 --- a/spec/commands/binstubs_spec.rb +++ b/spec/commands/binstubs_spec.rb @@ -145,6 +145,22 @@ describe "bundle binstubs <gem>" do end end + context "after installing with --standalone" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle "install --standalone" + end + + 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', __FILE__" + end + end + context "when the bin already exists" do it "doesn't overwrite and warns" do FileUtils.mkdir_p(bundled_app("bin")) diff --git a/spec/commands/lock_spec.rb b/spec/commands/lock_spec.rb index 7f32409ea6..b204c6aad5 100644 --- a/spec/commands/lock_spec.rb +++ b/spec/commands/lock_spec.rb @@ -42,7 +42,7 @@ describe "bundle lock" do with_license (1.0) PLATFORMS - ruby + #{local} DEPENDENCIES foo @@ -103,4 +103,11 @@ describe "bundle lock" do expect(read_lockfile).to eq(@lockfile) end + + it "supports adding new platforms" do + bundle! "lock --add-platform java x86-mingw32" + + lockfile = Bundler::LockfileParser.new(read_lockfile) + expect(lockfile.platforms).to eq([java, local, mingw]) + end end diff --git a/spec/install/bundler_spec.rb b/spec/install/bundler_spec.rb index 560272e5cf..835afa0e96 100644 --- a/spec/install/bundler_spec.rb +++ b/spec/install/bundler_spec.rb @@ -44,8 +44,15 @@ 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. E expect(out).to include(nice_error) end diff --git a/spec/install/deploy_spec.rb b/spec/install/deploy_spec.rb index d8e7ff7c08..2156ad8786 100644 --- a/spec/install/deploy_spec.rb +++ b/spec/install/deploy_spec.rb @@ -263,4 +263,32 @@ describe "install with --deployment or --frozen" do should_be_installed "rack 1.0.0" end end + + 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")}" + G + + bundle :install + should_be_installed "foo 1.0" + bundle "package --all" + expect(bundled_app("vendor/cache/foo")).to be_directory + + 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" + 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") + expect(out).to include("Using foo 1.0 from source at") + expect(out).to include("vendor/cache/foo") + should_be_installed "foo 1.0" + end + end end diff --git a/spec/install/gemfile/gemspec_spec.rb b/spec/install/gemfile/gemspec_spec.rb index 36c7353045..f860f55f13 100644 --- a/spec/install/gemfile/gemspec_spec.rb +++ b/spec/install/gemfile/gemspec_spec.rb @@ -143,6 +143,25 @@ describe "bundle install from an existing gemspec" do expect(@err).not_to match(/ahh/) end + it "allows conflicts" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.version = "1.0.0" + s.add_dependency "bar", "= 1.0.0" + end + build_gem "deps", :to_system => true do |s| + s.add_dependency "foo", "= 0.0.1" + end + build_gem "foo", "0.0.1", :to_system => true + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "deps" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + should_be_installed "foo 1.0.0" + 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 diff --git a/spec/install/yanked_spec.rb b/spec/install/yanked_spec.rb new file mode 100644 index 0000000000..ab96d4fcee --- /dev/null +++ b/spec/install/yanked_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +require "spec_helper" + +context "when installing a bundle that includes yanked gems" do + before(:each) do + build_repo4 do + build_gem "foo", "9.0.0" + end + end + + it "throws an error when the original gem version is yanked" do + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + foo (10.0.0) + + PLATFORMS + ruby + + DEPENDENCIES + foo (= 10.0.0) + + L + + install_gemfile <<-G + source "file://#{gem_repo4}" + gem "foo", "10.0.0" + G + + expect(out).to include("Your bundle is locked to foo (10.0.0)") + end + + it "throws the original error when only the Gemfile specifies a gem version that doesn't exist" do + install_gemfile <<-G + source "file://#{gem_repo4}" + gem "foo", "10.0.0" + G + + expect(out).not_to include("Your bundle is locked to foo (10.0.0)") + expect(out).to include("Could not find gem 'foo (= 10.0.0)' in any of the gem sources") + end +end diff --git a/spec/quality_spec.rb b/spec/quality_spec.rb index 043958a74d..d5178d73be 100644 --- a/spec/quality_spec.rb +++ b/spec/quality_spec.rb @@ -35,6 +35,22 @@ describe "The library itself" do "#{filename} has debugging mechanisms (like binding.pry, sleep, debugger, rspec focusing, etc.) on lines #{failing_lines.join(", ")}" end + def check_for_git_merge_conflicts(filename) + merge_conflicts_regex = / + <<<<<<<| + =======| + >>>>>>> + /x + + failing_lines = [] + File.readlines(filename).each_with_index do |line, number| + failing_lines << number + 1 if line =~ merge_conflicts_regex + end + + return if failing_lines.empty? + "#{filename} has unresolved git merge conflicts on lines #{failing_lines.join(", ")}" + end + def check_for_tab_characters(filename) failing_lines = [] File.readlines(filename).each_with_index do |line, number| @@ -115,12 +131,10 @@ describe "The library itself" do end it "does not include any leftover debugging or development mechanisms" do - included = /spec/ - exempt = /quality\_spec\.rb/ + exempt = %r{quality_spec.rb|support/helpers} error_messages = [] Dir.chdir(File.expand_path("../", __FILE__)) do `git ls-files -z`.split("\x0").each do |filename| - next unless filename =~ included next if filename =~ exempt error_messages << check_for_debugging_mechanisms(filename) end @@ -128,6 +142,18 @@ describe "The library itself" do expect(error_messages.compact).to be_well_formed end + it "does not include any unresolved merge conflicts" do + error_messages = [] + exempt = %r{lock/lockfile_spec|quality_spec} + Dir.chdir(File.expand_path("../", __FILE__)) do + `git ls-files -z`.split("\x0").each do |filename| + next if filename =~ exempt + error_messages << check_for_git_merge_conflicts(filename) + end + end + expect(error_messages.compact).to be_well_formed + end + it "maintains language quality of the documentation" do included = /ronn/ error_messages = [] diff --git a/spec/runtime/inline_spec.rb b/spec/runtime/inline_spec.rb index 47851430e5..870a3ffd57 100644 --- a/spec/runtime/inline_spec.rb +++ b/spec/runtime/inline_spec.rb @@ -140,4 +140,19 @@ describe "bundler/inline#gemfile" do expect(out).to match("OKAY") expect(exitstatus).to be_zero if exitstatus end + + it "installs quietly if necessary when the install option is not set" do + script <<-RUBY + gemfile do + source "file://#{gem_repo1}" + gem "rack" + end + + puts RACK + RUBY + + expect(out).to eq("1.0.0") + expect(err).to be_empty + expect(exitstatus).to be_zero if exitstatus + end end diff --git a/spec/runtime/require_spec.rb b/spec/runtime/require_spec.rb index fbfa398239..5ddf1ef013 100644 --- a/spec/runtime/require_spec.rb +++ b/spec/runtime/require_spec.rb @@ -38,6 +38,14 @@ describe "Bundler.require" do s.write "lib/eight.rb", "puts 'eight'" end + build_lib "nine", "1.0.0" do |s| + s.write "lib/nine.rb", "puts 'nine'" + end + + build_lib "ten", "1.0.0" do |s| + s.write "lib/ten.rb", "puts 'ten'" + end + gemfile <<-G path "#{lib_path}" gem "one", :group => :bar, :require => %w[baz qux] @@ -48,6 +56,10 @@ describe "Bundler.require" do gem "six", :group => "string" gem "seven", :group => :not gem "eight", :require => true, :group => :require_true + env "BUNDLER_TEST" => "nine" do + gem "nine", :require => true + end + gem "ten", :install_if => lambda { ENV["BUNDLER_TEST"] == "ten" } G end @@ -86,6 +98,18 @@ describe "Bundler.require" do expect(out).to eq("two\nfive") end + it "allows requiring gems which are scoped by env" do + ENV["BUNDLER_TEST"] = "nine" + run "Bundler.require" + expect(out).to eq("two\nnine") + end + + it "allows requiring gems which are scoped by install_if" do + ENV["BUNDLER_TEST"] = "ten" + run "Bundler.require" + expect(out).to eq("two\nten") + end + it "raises an exception if a require is specified but the file does not exist" do gemfile <<-G path "#{lib_path}" |