diff --git a/ChangeLog b/ChangeLog index d662a80dd9..3053e6d251 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +Fri Nov 22 08:27:13 2013 Eric Hodel + + * lib/rubygems: Update to RubyGems master 50a8210. Important changes + in this commit: + + RubyGems now automatically checks for gem.deps.rb or Gemfile when + running ruby executables. This behavior is similar to `bundle exec + rake`. This change may be reverted before Ruby 2.1.0 if too many bugs + are found. + + * test/rubygems: ditto. + Thu Nov 21 22:33:59 2013 Koichi Sasada * gc.c: RGENGC_CHECK_MODE should be 0. diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 2e8ad7bba1..a6c97ed16d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -215,50 +215,6 @@ module Gem end end - def self.detect_gemdeps - if path = ENV['RUBYGEMS_GEMDEPS'] - path = path.dup.untaint - - if path == "-" - here = Dir.pwd.untaint - start = here - - begin - while true - path = GEM_DEP_FILES.find { |f| File.file?(f) } - - if path - path = File.join here, path - break - end - - Dir.chdir ".." - - # If we're at a toplevel, stop. - return if Dir.pwd == here - - here = Dir.pwd - end - ensure - Dir.chdir start - end - end - - path.untaint - - return unless File.file? path - - rs = Gem::RequestSet.new - rs.load_gemdeps path - - rs.resolve_current.map do |s| - sp = s.full_spec - sp.activate - sp - end - end - end - ## # Find the full path to the executable for gem +name+. If the +exec_name+ # is not given, the gem's default_executable is chosen, otherwise the @@ -1035,6 +991,61 @@ module Gem load_plugin_files files end + ## + # Looks for gem dependency files (gem.deps.rb, Gemfile, Isolate) from the + # current directory up and activates the gems in the first file found. + # + # This is run automatically when rubygems starts. To disable, set + # the RUBYGEMS_GEMDEPS= environment variable to an empty + # string. + + def self.use_gemdeps + return unless path = ENV['RUBYGEMS_GEMDEPS'] || '-' + path = path.dup.untaint + + if path == "-" + here = Dir.pwd.untaint + start = here + + begin + while true + path = GEM_DEP_FILES.find { |f| File.file?(f) } + + if path + path = File.join here, path + break + end + + Dir.chdir ".." + + # If we're at a toplevel, stop. + return if Dir.pwd == here + + here = Dir.pwd + end + ensure + Dir.chdir start + end + end + + path.untaint + + return unless File.file? path + + rs = Gem::RequestSet.new + rs.load_gemdeps path + + rs.resolve_current.map do |s| + sp = s.full_spec + sp.activate + sp + end + end + + class << self + alias detect_gemdeps use_gemdeps # :nodoc: + end + # FIX: Almost everywhere else we use the `def self.` way of defining class # methods, and then we switch over to `class << self` here. Pick one or the # other. diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb index 2cb8901b4d..0aa0f7b79f 100644 --- a/lib/rubygems/doctor.rb +++ b/lib/rubygems/doctor.rb @@ -27,10 +27,13 @@ class Gem::Doctor ['gems', ''], ] - raise 'Update REPOSITORY_EXTENSION_MAP' unless - Gem::REPOSITORY_SUBDIRECTORIES.sort == + missing = + Gem::REPOSITORY_SUBDIRECTORIES.sort - REPOSITORY_EXTENSION_MAP.map { |(k,_)| k }.sort + raise "Update REPOSITORY_EXTENSION_MAP, missing: #{missing.join ', '}" unless + missing.empty? + ## # Creates a new Gem::Doctor that will clean up +gem_repository+. Only one # gem repository may be cleaned at a time. diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index ee3d8fecdf..6bd50eca2c 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -223,7 +223,7 @@ class Gem::UnsatisfiableDependencyError < Gem::Exception attr_reader :dependency ## - # Creates a new UnsatisfiableDepedencyError for the unsatisfiable + # Creates a new UnsatisfiableDependencyError for the unsatisfiable # Gem::Resolver::DependencyRequest +dep+ def initialize dep, platform_mismatch=nil diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 42d457063f..d91a39cb22 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -2,6 +2,7 @@ require 'rubygems' require 'rubygems/dependency' require 'rubygems/dependency_list' require 'rubygems/installer' +require 'rubygems/resolver' require 'tsort' ## @@ -146,7 +147,15 @@ class Gem::RequestSet resolve - install options, &block + if options[:explain] + puts "Gems to install:" + + specs.map { |s| s.full_name }.sort.each do |s| + puts " #{s}" + end + else + install options, &block + end end def install_into dir, force = true, options = {} @@ -201,7 +210,7 @@ class Gem::RequestSet # Resolve the requested dependencies and return an Array of Specification # objects to be activated. - def resolve set = Gem::Resolver::IndexSet.new + def resolve set = Gem::Resolver::BestSet.new @sets << set @sets << @git_set @sets << @vendor_set @@ -253,7 +262,7 @@ class Gem::RequestSet end else unless @soft_missing - raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}" + raise Gem::DependencyError, "Unresolved dependency found during sorting - #{dep} (requested by #{node.spec.full_name})" end end end diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index 8e29eb87e5..0c27b1a61a 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -115,7 +115,7 @@ class Gem::RequestSet::GemDependencyAPI ## # A Hash containing gem names and files to require from those gems. - attr_reader :requires + attr_reader :requires # :nodoc: ## # A set of gems that are loaded via the +:path+ option to #gem @@ -125,7 +125,7 @@ class Gem::RequestSet::GemDependencyAPI ## # The groups of gems to exclude from installation - attr_accessor :without_groups + attr_accessor :without_groups # :nodoc: ## # Creates a new GemDependencyAPI that will add dependencies to the @@ -282,6 +282,8 @@ class Gem::RequestSet::GemDependencyAPI true end + private :gem_github + ## # Handles the :group and :groups +options+ for the gem with the given # +name+. @@ -361,7 +363,7 @@ class Gem::RequestSet::GemDependencyAPI def gem_requires name, options # :nodoc: if options.include? :require then if requires = options.delete(:require) then - @requires[name].concat requires + @requires[name].concat Array requires end else @requires[name] << name @@ -370,6 +372,11 @@ class Gem::RequestSet::GemDependencyAPI private :gem_requires + ## + # :category: Gem Dependencies DSL + # + # Block form for specifying gems from a git +repository+. + def git repository @current_repository = repository @@ -424,6 +431,8 @@ class Gem::RequestSet::GemDependencyAPI ## # :category: Gem Dependencies DSL + # + # Block form for restricting gems to a particular platform. def platform what @current_platform = what @@ -436,6 +445,8 @@ class Gem::RequestSet::GemDependencyAPI ## # :category: Gem Dependencies DSL + # + # Block form for restricting gems to a particular platform. alias :platforms :platform diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index 2669cc4f24..828c61de01 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -29,9 +29,23 @@ class Gem::Resolver attr_accessor :soft_missing + ## + # Combines +sets+ into a ComposedSet that allows specification lookup in a + # uniform manner. If one of the +sets+ is itself a ComposedSet its sets are + # flattened into the result ComposedSet. + def self.compose_sets *sets sets.compact! + sets = sets.map do |set| + case set + when Gem::Resolver::ComposedSet then + set.sets + else + set + end + end.flatten + case sets.length when 0 then raise ArgumentError, 'one set in the composition must be non-nil' @@ -77,6 +91,15 @@ class Gem::Resolver end end + def explain_list(stage, data) + if DEBUG_RESOLVER + STDOUT.printf "%20s (%d entries)\n", stage.to_s.upcase, data.size + data.each do |d| + STDOUT.printf "%20s %s\n", "", d + end + end + end + ## # Creates an ActivationRequest for the given +dep+ and the last +possible+ # specification. @@ -134,8 +157,6 @@ class Gem::Resolver # If no good candidate is found, the first state is tried. def find_conflict_state conflict, states # :nodoc: - rejected = [] - until states.empty? do state = states.pop @@ -145,14 +166,9 @@ class Gem::Resolver state.conflicts << [state.spec, conflict] return state end - - rejected << state end - return rejected.shift - ensure - rejected = rejected.concat states - states.replace rejected + nil end ## @@ -172,14 +188,23 @@ class Gem::Resolver # If the existing activation indicates that there are other possibles for # it, then issue the conflict on the dependency for the activation itself. - # Otherwise, issue it on the requester's request itself. - if existing.others_possible? or existing.request.requester.nil? then + # Otherwise, if there was a requester, issue it on the requester's + # request itself. + # Finally, if the existing request has no requester (toplevel) unwind to + # it anyway. + + if existing.others_possible? conflict = Gem::Resolver::Conflict.new dep, existing - else + elsif dep.requester depreq = dep.requester.request conflict = Gem::Resolver::Conflict.new depreq, existing, dep + elsif existing.request.requester.nil? + conflict = + Gem::Resolver::Conflict.new dep, existing + else + raise Gem::DependencyError, "Unable to figure out how to unwind conflict" end @conflicts << conflict unless @conflicts.include? conflict @@ -234,6 +259,8 @@ class Gem::Resolver while !needed.empty? dep = needed.remove explain :try, [dep, dep.requester ? dep.requester.request : :toplevel] + explain_list :next5, needed.next5 + explain_list :specs, Array(specs).map { |x| x.full_name }.sort # If there is already a spec activated for the requested name... if specs && existing = specs.find { |s| dep.name == s.name } @@ -284,7 +311,7 @@ class Gem::Resolver # Retry resolution with this spec and add it's dependencies spec, act = activation_request state.dep, state.possibles - needed = requests spec, act, state.needed + needed = requests spec, act, state.needed.dup specs = Gem::List.prepend state.specs, act return needed, specs diff --git a/lib/rubygems/resolver/api_set.rb b/lib/rubygems/resolver/api_set.rb index 60bf911063..89ee3c9b15 100644 --- a/lib/rubygems/resolver/api_set.rb +++ b/lib/rubygems/resolver/api_set.rb @@ -10,13 +10,28 @@ class Gem::Resolver::APISet < Gem::Resolver::Set attr_reader :dep_uri # :nodoc: ## - # Creates a new APISet that will retrieve gems from +uri+ using the RubyGems - # API described at http://guides.rubygems.org/rubygems-org-api + # The Gem::Source that gems are fetched from - def initialize uri = 'https://rubygems.org/api/v1/dependencies' - uri = URI uri unless URI === uri # for ruby 1.8 - @data = Hash.new { |h,k| h[k] = [] } - @dep_uri = uri + attr_reader :source + + ## + # The corresponding place to fetch gems. + + attr_reader :uri + + ## + # Creates a new APISet that will retrieve gems from +uri+ using the RubyGems + # API URL +dep_uri+ which is described at + # http://guides.rubygems.org/rubygems-org-api + + def initialize dep_uri = 'https://rubygems.org/api/v1/dependencies' + dep_uri = URI dep_uri unless URI === dep_uri # for ruby 1.8 + + @dep_uri = dep_uri + @uri = dep_uri + '../../..' + + @data = Hash.new { |h,k| h[k] = [] } + @source = Gem::Source.new @uri end ## @@ -41,15 +56,35 @@ class Gem::Resolver::APISet < Gem::Resolver::Set def prefetch reqs names = reqs.map { |r| r.dependency.name } - needed = names.find_all { |d| !@data.key?(d) } + needed = names - @data.keys return if needed.empty? uri = @dep_uri + "?gems=#{needed.sort.join ','}" str = Gem::RemoteFetcher.fetcher.fetch_path uri + loaded = [] + Marshal.load(str).each do |ver| - @data[ver[:name]] << ver + name = ver[:name] + + @data[name] << ver + loaded << name + end + + (needed - loaded).each do |missing| + @data[missing] = [] + end + end + + def pretty_print q # :nodoc: + q.group 2, '[APISet', ']' do + q.breakable + q.text "URI: #{@dep_uri}" + + q.breakable + q.text 'gem names:' + q.pp @data.keys end end diff --git a/lib/rubygems/resolver/api_specification.rb b/lib/rubygems/resolver/api_specification.rb index 19611e17d8..0ab8e830c6 100644 --- a/lib/rubygems/resolver/api_specification.rb +++ b/lib/rubygems/resolver/api_specification.rb @@ -34,5 +34,42 @@ class Gem::Resolver::APISpecification < Gem::Resolver::Specification @dependencies == other.dependencies end + def pretty_print q # :nodoc: + q.group 2, '[APISpecification', ']' do + q.breakable + q.text "name: #{name}" + + q.breakable + q.text "version: #{version}" + + q.breakable + q.text "platform: #{platform}" + + q.breakable + q.text 'dependencies:' + q.breakable + q.pp @dependencies + + q.breakable + q.text "set uri: #{@set.dep_uri}" + end + end + + ## + # Fetches a Gem::Specification for this APISpecification. + + def spec # :nodoc: + @spec ||= + begin + tuple = Gem::NameTuple.new @name, @version, @platform + + source.fetch_spec tuple + end + end + + def source # :nodoc: + @set.source + end + end diff --git a/lib/rubygems/resolver/composed_set.rb b/lib/rubygems/resolver/composed_set.rb index e4aa15e4d0..702bd9ccfc 100644 --- a/lib/rubygems/resolver/composed_set.rb +++ b/lib/rubygems/resolver/composed_set.rb @@ -1,17 +1,36 @@ +## +# A ComposedSet allows multiple sets to be queried like a single set. +# +# To create a composed set with any number of sets use: +# +# Gem::Resolver.compose_sets set1, set2 +# +# This method will eliminate nesting of composed sets. + class Gem::Resolver::ComposedSet < Gem::Resolver::Set attr_reader :sets # :nodoc: + ## + # Creates a new ComposedSet containing +sets+. Use + # Gem::Resolver::compose_sets instead. + def initialize *sets @sets = sets end + ## + # Finds all specs matching +req+ in all sets. + def find_all req res = [] @sets.each { |s| res += s.find_all(req) } res end + ## + # Prefetches +reqs+ in all sets. + def prefetch reqs @sets.each { |s| s.prefetch(reqs) } end diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb index b081972658..20c6eced31 100644 --- a/lib/rubygems/resolver/conflict.rb +++ b/lib/rubygems/resolver/conflict.rb @@ -4,25 +4,38 @@ class Gem::Resolver::Conflict + ## + # The specification that was activated prior to the conflict + attr_reader :activated + ## + # The dependency that is in conflict with the activated gem. + attr_reader :dependency attr_reader :failed_dep # :nodoc: + ## + # Creates a new resolver conflict when +dependency+ is in conflict with an + # already +activated+ specification. + def initialize(dependency, activated, failed_dep=dependency) @dependency = dependency @activated = activated @failed_dep = failed_dep end - def == other + def == other # :nodoc: self.class === other and @dependency == other.dependency and @activated == other.activated and @failed_dep == other.failed_dep end + ## + # A string explanation of the conflict. + def explain "" end @@ -41,11 +54,15 @@ class Gem::Resolver::Conflict activated = @activated.spec.full_name requirement = @failed_dep.dependency.requirement - " Activated %s instead of (%s) via:\n %s\n" % [ - activated, requirement, request_path.join(', ') + " Activated %s via:\n %s\n instead of (%s) via:\n %s\n" % [ + activated, request_path(@activated).join(', '), + requirement, request_path(requester).join(', '), ] end + ## + # Returns true if the conflicting dependency's name matches +spec+. + def for_spec?(spec) @dependency.name == spec.name end @@ -72,16 +89,17 @@ class Gem::Resolver::Conflict end ## - # Path of specifications that requested this dependency + # Path of activations from the +current+ list. - def request_path - current = requester - path = [] + def request_path current + path = [] while current do - path << current.spec.full_name + spec_name = current.spec.full_name + requirement = current.request.dependency.requirement + path << "#{current.spec.full_name} (#{requirement})" - current = current.request.requester + current = current.parent end path = ['user request (gem command or Gemfile)'] if path.empty? @@ -98,5 +116,8 @@ class Gem::Resolver::Conflict end -Gem::Resolver::DependencyConflict = Gem::Resolver::Conflict +## +# TODO: Remove in RubyGems 3 + +Gem::Resolver::DependencyConflict = Gem::Resolver::Conflict # :nodoc: diff --git a/lib/rubygems/resolver/git_set.rb b/lib/rubygems/resolver/git_set.rb index 3c38d3dca0..c912e367d9 100644 --- a/lib/rubygems/resolver/git_set.rb +++ b/lib/rubygems/resolver/git_set.rb @@ -42,38 +42,27 @@ class Gem::Resolver::GitSet < Gem::Resolver::Set # Finds all git gems matching +req+ def find_all req - @repositories.keys.select do |name| - name == req.name - end.map do |name| - @specs[name] || load_spec(name) - end.select do |spec| + prefetch nil + + specs.values.select do |spec| req.matches_spec? spec end end - def load_spec name - repository, reference = @repositories[name] - - source = Gem::Source::Git.new name, repository, reference - - spec = source.load_spec name - - git_spec = - Gem::Resolver::GitSpecification.new self, spec, source - - @specs[name] = git_spec - end - ## # Prefetches specifications from the git repositories in this set. def prefetch reqs - names = reqs.map { |req| req.name } + return unless @specs.empty? - @repositories.each_key do |name| - next unless names.include? name + @repositories.each do |name, (repository, reference)| + source = Gem::Source::Git.new name, repository, reference - load_spec name + source.specs.each do |spec| + git_spec = Gem::Resolver::GitSpecification.new self, spec, source + + @specs[spec.name] = git_spec + end end end diff --git a/lib/rubygems/resolver/requirement_list.rb b/lib/rubygems/resolver/requirement_list.rb index 8123e84fc7..04e437b2a9 100644 --- a/lib/rubygems/resolver/requirement_list.rb +++ b/lib/rubygems/resolver/requirement_list.rb @@ -37,4 +37,8 @@ class Gem::Resolver::RequirementList def remove @list.shift end + + def next5 + @list[0,5] + end end diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index a40a27594b..b6e2d97523 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -1,13 +1,30 @@ require 'uri' require 'fileutils' +## +# A Source knows how to list and fetch gems from a RubyGems marshal index. +# +# There are other Source subclasses for installed gems, local gems, the +# bundler dependency API and so-forth. + class Gem::Source - FILES = { + + include Comparable + + FILES = { # :nodoc: :released => 'specs', :latest => 'latest_specs', :prerelease => 'prerelease_specs', } + ## + # The URI this source will fetch gems from. + + attr_reader :uri + + ## + # Creates a new Source which will use the index located at +uri+. + def initialize(uri) unless uri.kind_of? URI uri = URI.parse(uri.to_s) @@ -17,13 +34,17 @@ class Gem::Source @api_uri = nil end - attr_reader :uri + ## + # Use an SRV record on the host to look up the true endpoint for the index. - def api_uri + def api_uri # :nodoc: require 'rubygems/remote_fetcher' @api_uri ||= Gem::RemoteFetcher.fetcher.api_endpoint uri end + ## + # Sources are ordered by installation preference. + def <=>(other) case other when Gem::Source::Installed, @@ -46,13 +67,11 @@ class Gem::Source end end - include Comparable - - def ==(other) + def == other # :nodoc: self.class === other and @uri == other.uri end - alias_method :eql?, :== + alias_method :eql?, :== # :nodoc: ## # Returns a Set that can fetch specifications from this source. @@ -70,7 +89,7 @@ class Gem::Source end end - def hash + def hash # :nodoc: @uri.hash end @@ -83,6 +102,9 @@ class Gem::Source File.join Gem.spec_cache_dir, "#{uri.host}%#{uri.port}", File.dirname(escaped_path) end + ## + # Returns true when it is possible and safe to update the cache directory. + def update_cache? @update_cache ||= begin @@ -166,6 +188,10 @@ class Gem::Source end end + ## + # Downloads +spec+ and writes it to +dir+. See also + # Gem::RemoteFetcher#download. + def download(spec, dir=Dir.pwd) fetcher = Gem::RemoteFetcher.fetcher fetcher.download spec, api_uri.to_s, dir @@ -176,7 +202,7 @@ class Gem::Source q.breakable q.text @uri.to_s if api = api_uri - g.text api + q.text api.to_s end end end diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index fdce03c59a..82690923ff 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -36,9 +36,11 @@ class Gem::Source::Git < Gem::Source attr_reader :need_submodules ## - # Creates a new git gem source for a gem with the given +name+ that will be - # loaded from +reference+ in +repository+. If +submodules+ is true, - # submodules will be checked out when the gem is installed. + # Creates a new git gem source for a gems from loaded from +repository+ at + # the given +reference+. The +name+ is only used to track the repository + # back to a gem dependencies file, it has no real significance as a git + # repository may contain multiple gems. If +submodules+ is true, submodules + # will be checked out when the gem is installed. def initialize name, repository, reference, submodules = false super(nil) @@ -125,34 +127,6 @@ class Gem::Source::Git < Gem::Source File.join Gem.dir, 'bundler', 'gems', "#{@name}-#{dir_shortref}" end - ## - # Loads a Gem::Specification for +name+ from this git repository. - - def load_spec name - cache - - gemspec_reference = "#{@reference}:#{name}.gemspec" - - Dir.chdir repo_cache_dir do - source = Gem::Util.popen @git, 'show', gemspec_reference - - source.force_encoding Encoding::UTF_8 if Object.const_defined? :Encoding - source.untaint - - begin - spec = eval source, binding, gemspec_reference - - return spec if Gem::Specification === spec - - warn "git gem specification for #{@repository} #{gemspec_reference} is not a Gem::Specification (#{spec.class} instead)." - rescue SignalException, SystemExit - raise - rescue SyntaxError, Exception - warn "invalid git gem specification for #{@repository} #{gemspec_reference}" - end - end - end - ## # The directory where the git gem's repository will be cached. @@ -164,12 +138,29 @@ class Gem::Source::Git < Gem::Source # Converts the git reference for the repository into a commit hash. def rev_parse # :nodoc: - # HACK no safe equivalent of ` exists on 1.8.7 Dir.chdir repo_cache_dir do Gem::Util.popen(@git, 'rev-parse', @reference).strip end end + ## + # Loads all gemspecs in the repository + + def specs + checkout + + Dir.chdir install_dir do + Dir['{,*,*/*}.gemspec'].map do |spec_file| + directory = File.dirname spec_file + file = File.basename spec_file + + Dir.chdir directory do + Gem::Specification.load file + end + end.compact + end + end + ## # A hash for the git gem based on the git repository URI. diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index b95e2c0699..22e2981198 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -27,6 +27,7 @@ class Date; end # Gem::Specification.new do |s| # s.name = 'example' # s.version = '0.1.0' +# s.licenses = ['MIT'] # s.summary = "This is an example!" # s.description = "Much longer explanation of the example!" # s.authors = ["Ruby Coder"] @@ -530,6 +531,7 @@ class Gem::Specification < Gem::BasicSpecification end ## + # :category: Recommended gemspec attributes # The license for this gem. # # The license must be a short name, no more than 64 characters. @@ -538,7 +540,12 @@ class Gem::Specification < Gem::BasicSpecification # text of the license should be inside of the gem when you build it. # # See http://opensource.org/licenses/alphabetical for a list of licenses and - # their abbreviations (or short names). + # their abbreviations (or short names). GitHub also provides a + # license picker at http://choosealicense.com/ + # + # According to copyright law, not having an OSI-approved open source license + # means you have no rights to use the code for any purpose-- in other words, + # "all rights reserved". # # You can set multiple licenses with #licenses= # @@ -550,6 +557,7 @@ class Gem::Specification < Gem::BasicSpecification end ## + # :category: Recommended gemspec attributes # The license(s) for the library. # # Each license must be a short name, no more than 64 characters. @@ -2526,8 +2534,8 @@ class Gem::Specification < Gem::BasicSpecification } warning <<-warning if licenses.empty? -licenses is empty. Use a license abbreviation from: - http://opensource.org/licenses/alphabetical +licenses is empty, but is recommended. Use a license abbreviation from: +http://opensource.org/licenses/alphabetical warning validate_permissions diff --git a/lib/rubygems/test_case.rb b/lib/rubygems/test_case.rb index d1b471f619..f3967aba8b 100644 --- a/lib/rubygems/test_case.rb +++ b/lib/rubygems/test_case.rb @@ -1149,8 +1149,10 @@ Also, a list: def dependency_request dep, from_name, from_version, parent = nil remote = Gem::Source.new @uri - parent ||= Gem::Resolver::DependencyRequest.new \ - dep, nil + unless parent then + parent_dep = dep from_name, from_version + parent = Gem::Resolver::DependencyRequest.new parent_dep, nil + end spec = Gem::Resolver::IndexSpecification.new \ nil, from_name, from_version, remote, Gem::Platform::RUBY diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index af53e599b5..42663974a5 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -40,27 +40,24 @@ module Gem::Util # for a command. def self.popen *command - begin - r, = IO.popen command, &:read - rescue TypeError # ruby 1.8 only supports string command - r, w = IO.pipe + IO.popen command, &:read + rescue TypeError # ruby 1.8 only supports string command + r, w = IO.pipe - pid = fork do - STDIN.close - STDOUT.reopen w + pid = fork do + STDIN.close + STDOUT.reopen w - exec(*command) - end - - w.close - - begin - return r.read - ensure - Process.wait pid - end + exec(*command) end + w.close + + begin + return r.read + ensure + Process.wait pid + end end end diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index ec7f7299ec..33d4b25e0d 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -1245,6 +1245,60 @@ class TestGem < Gem::TestCase end end + def test_use_gemdeps + rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil + + spec = util_spec 'a', 1 + + refute spec.activated? + + open 'Gemfile', 'w' do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + assert spec.activated? + ensure + ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + end + + def test_use_gemdeps_disabled + rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], '' + + spec = util_spec 'a', 1 + + refute spec.activated? + + open 'Gemfile', 'w' do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + refute spec.activated? + ensure + ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + end + + def test_use_gemdeps_specific + rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], 'x' + + spec = util_spec 'a', 1 + + refute spec.activated? + + open 'x', 'w' do |io| + io.write 'gem "a"' + end + + Gem.use_gemdeps + + assert spec.activated? + ensure + ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps + end + def with_plugin(path) test_plugin_path = File.expand_path("test/rubygems/plugin/#{path}", @@project_dir) diff --git a/test/rubygems/test_gem_impossible_dependencies_error.rb b/test/rubygems/test_gem_impossible_dependencies_error.rb index f61b86e490..577ee580ec 100644 --- a/test/rubygems/test_gem_impossible_dependencies_error.rb +++ b/test/rubygems/test_gem_impossible_dependencies_error.rb @@ -28,10 +28,14 @@ class TestGemImpossibleDependenciesError < Gem::TestCase expected = <<-EXPECTED rye-0.9.8 requires net-ssh (>= 2.0.13) but it conflicted: - Activated net-ssh-2.6.5 instead of (~> 2.2.2) via: - net-ssh-2.6.5, rye-0.9.8 - Activated net-ssh-2.2.2 instead of (>= 2.6.5) via: - net-ssh-2.2.2, rye-0.9.8 + Activated net-ssh-2.6.5 via: + net-ssh-2.6.5 (>= 2.0.13), rye-0.9.8 (= 0.9.8) + instead of (~> 2.2.2) via: + net-ssh-2.6.5 (>= 2.0.13), rye-0.9.8 (= 0.9.8) + Activated net-ssh-2.2.2 via: + net-ssh-2.2.2 (>= 2.0.13), rye-0.9.8 (= 0.9.8) + instead of (>= 2.6.5) via: + net-ssh-2.2.2 (>= 2.0.13), rye-0.9.8 (= 0.9.8) EXPECTED assert_equal expected, error.message diff --git a/test/rubygems/test_gem_request_set.rb b/test/rubygems/test_gem_request_set.rb index 531b6c09d1..6719be372c 100644 --- a/test/rubygems/test_gem_request_set.rb +++ b/test/rubygems/test_gem_request_set.rb @@ -123,7 +123,7 @@ class TestGemRequestSet < Gem::TestCase assert_equal %w[a-1], names - assert_equal [@DR::IndexSet, @DR::GitSet, @DR::VendorSet], + assert_equal [@DR::BestSet, @DR::GitSet, @DR::VendorSet], rs.sets.map { |set| set.class } end @@ -169,7 +169,7 @@ class TestGemRequestSet < Gem::TestCase assert_equal ["a-1", "b-2"], names - assert_equal [@DR::IndexSet, @DR::GitSet, @DR::VendorSet], + assert_equal [@DR::BestSet, @DR::GitSet, @DR::VendorSet], rs.sets.map { |set| set.class } end diff --git a/test/rubygems/test_gem_request_set_gem_dependency_api.rb b/test/rubygems/test_gem_request_set_gem_dependency_api.rb index 4286a761cb..c41d983744 100644 --- a/test/rubygems/test_gem_request_set_gem_dependency_api.rb +++ b/test/rubygems/test_gem_request_set_gem_dependency_api.rb @@ -272,10 +272,12 @@ class TestGemRequestSetGemDependencyAPI < Gem::TestCase def test_gem_require @gda.gem 'a', :require => %w[b c] + @gda.gem 'd', :require => 'e' - assert_equal [dep('a')], @set.dependencies + assert_equal [dep('a'), dep('d')], @set.dependencies assert_equal %w[b c], @gda.requires['a'] + assert_equal %w[e], @gda.requires['d'] end def test_gem_require_false diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb index 80769de8cf..91cf84c5fc 100644 --- a/test/rubygems/test_gem_resolver.rb +++ b/test/rubygems/test_gem_resolver.rb @@ -44,6 +44,21 @@ class TestGemResolver < Gem::TestCase assert_equal [index_set, vendor_set], composed.sets end + def test_self_compose_sets_nest + index_set = @DR::IndexSet.new + vendor_set = @DR::VendorSet.new + + inner = @DR.compose_sets index_set, vendor_set + + current_set = @DR::CurrentSet.new + + composed = @DR.compose_sets inner, current_set + + assert_kind_of Gem::Resolver::ComposedSet, composed + + assert_equal [index_set, vendor_set, current_set], composed.sets + end + def test_self_compose_sets_nil index_set = @DR::IndexSet.new diff --git a/test/rubygems/test_gem_resolver_api_set.rb b/test/rubygems/test_gem_resolver_api_set.rb index 976d861cdf..288f496c93 100644 --- a/test/rubygems/test_gem_resolver_api_set.rb +++ b/test/rubygems/test_gem_resolver_api_set.rb @@ -6,20 +6,161 @@ class TestGemResolverAPISet < Gem::TestCase super @DR = Gem::Resolver + @dep_uri = URI "#{@gem_repo}api/v1/dependencies" end def test_initialize set = @DR::APISet.new - assert_equal URI('https://rubygems.org/api/v1/dependencies'), - set.dep_uri + assert_equal URI('https://rubygems.org/api/v1/dependencies'), set.dep_uri + assert_equal URI('https://rubygems.org'), set.uri + assert_equal Gem::Source.new(URI('https://rubygems.org')), set.source end def test_initialize_uri - set = @DR::APISet.new @gem_repo + set = @DR::APISet.new @dep_uri - assert_equal URI('http://gems.example.com/'), - set.dep_uri + assert_equal URI("#{@gem_repo}api/v1/dependencies"), set.dep_uri + assert_equal URI("#{@gem_repo}"), set.uri + end + + def test_find_all + spec_fetcher + + data = [ + { :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], }, + ] + + @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump data + + set = @DR::APISet.new @dep_uri + + a_dep = @DR::DependencyRequest.new dep('a'), nil + + expected = [ + @DR::APISpecification.new(set, data.first) + ] + + assert_equal expected, set.find_all(a_dep) + end + + def test_find_all_cache + spec_fetcher + + data = [ + { :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], }, + ] + + @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump data + + set = @DR::APISet.new @dep_uri + + a_dep = @DR::DependencyRequest.new dep('a'), nil + + set.prefetch [a_dep] + + @fetcher.data.delete "#{@dep_uri}?gems=a" + + expected = [ + @DR::APISpecification.new(set, data.first) + ] + + assert_equal expected, set.find_all(a_dep) + end + + def test_find_all_missing + spec_fetcher + + @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump [] + + set = @DR::APISet.new @dep_uri + + a_dep = @DR::DependencyRequest.new dep('a'), nil + + assert_empty set.find_all(a_dep) + + @fetcher.data.delete "#{@dep_uri}?gems=a" + + assert_empty set.find_all(a_dep) + end + + def test_prefetch + spec_fetcher + + data = [ + { :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], }, + ] + + @fetcher.data["#{@dep_uri}?gems=a,b"] = Marshal.dump data + @fetcher.data["#{@dep_uri}?gems=b"] = Marshal.dump [] + + set = @DR::APISet.new @dep_uri + + a_dep = @DR::DependencyRequest.new dep('a'), nil + b_dep = @DR::DependencyRequest.new dep('b'), nil + + set.prefetch [a_dep, b_dep] + + assert_equal %w[a-1], set.find_all(a_dep).map { |s| s.full_name } + assert_empty set.find_all(b_dep) + end + + def test_prefetch_cache + spec_fetcher + + data = [ + { :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], }, + ] + + @fetcher.data["#{@dep_uri}?gems=a"] = Marshal.dump data + + set = @DR::APISet.new @dep_uri + + a_dep = @DR::DependencyRequest.new dep('a'), nil + b_dep = @DR::DependencyRequest.new dep('b'), nil + + set.prefetch [a_dep] + + @fetcher.data.delete "#{@dep_uri}?gems=a" + @fetcher.data["#{@dep_uri}?gems=b"] = Marshal.dump [] + + set.prefetch [a_dep, b_dep] + end + + def test_prefetch_cache_missing + spec_fetcher + + data = [ + { :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], }, + ] + + @fetcher.data["#{@dep_uri}?gems=a,b"] = Marshal.dump data + + set = @DR::APISet.new @dep_uri + + a_dep = @DR::DependencyRequest.new dep('a'), nil + b_dep = @DR::DependencyRequest.new dep('b'), nil + + set.prefetch [a_dep, b_dep] + + @fetcher.data.delete "#{@dep_uri}?gems=a,b" + + set.prefetch [a_dep, b_dep] end end diff --git a/test/rubygems/test_gem_resolver_api_specification.rb b/test/rubygems/test_gem_resolver_api_specification.rb index e61d30c7c6..4a94135f06 100644 --- a/test/rubygems/test_gem_resolver_api_specification.rb +++ b/test/rubygems/test_gem_resolver_api_specification.rb @@ -28,5 +28,41 @@ class TestGemResolverAPISpecification < Gem::TestCase assert_equal expected, spec.dependencies end + def test_source + set = Gem::Resolver::APISet.new + data = { + :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], + } + + api_spec = Gem::Resolver::APISpecification.new set, data + + assert_equal set.source, api_spec.source + end + + def test_spec + spec_fetcher do |fetcher| + fetcher.spec 'a', 1 + end + + dep_uri = URI(@gem_repo) + 'api/v1/dependencies' + set = Gem::Resolver::APISet.new dep_uri + data = { + :name => 'a', + :number => '1', + :platform => 'ruby', + :dependencies => [], + } + + api_spec = Gem::Resolver::APISpecification.new set, data + + spec = api_spec.spec + + assert_kind_of Gem::Specification, spec + assert_equal 'a-1', spec.full_name + end + end diff --git a/test/rubygems/test_gem_resolver_conflict.rb b/test/rubygems/test_gem_resolver_conflict.rb index 3ae2a7cf5f..f8bba3f16f 100644 --- a/test/rubygems/test_gem_resolver_conflict.rb +++ b/test/rubygems/test_gem_resolver_conflict.rb @@ -12,12 +12,20 @@ class TestGemResolverConflict < Gem::TestCase child = dependency_request dep('net-ssh', '>= 2.6.5'), 'net-ssh', '2.2.2', root + dep = Gem::Resolver::DependencyRequest.new dep('net-ssh', '>= 2.0.13'), nil + + spec = quick_spec 'net-ssh', '2.2.2' + active = + Gem::Resolver::ActivationRequest.new spec, dep + conflict = - Gem::Resolver::Conflict.new child, child.requester + Gem::Resolver::Conflict.new child, active expected = <<-EXPECTED - Activated net-ssh-2.2.2 instead of (>= 2.6.5) via: - net-ssh-2.2.2, rye-0.9.8 + Activated net-ssh-2.2.2 via: + net-ssh-2.2.2 (>= 2.0.13) + instead of (>= 2.6.5) via: + net-ssh-2.2.2 (>= 2.0.13), rye-0.9.8 (= 0.9.8) EXPECTED assert_equal expected, conflict.explanation @@ -36,7 +44,9 @@ class TestGemResolverConflict < Gem::TestCase conflict = @DR::Conflict.new a1_req, activated expected = <<-EXPECTED - Activated a-2 instead of (= 1) via: + Activated a-2 via: + a-2 (= 2) + instead of (= 1) via: user request (gem command or Gemfile) EXPECTED @@ -46,13 +56,19 @@ class TestGemResolverConflict < Gem::TestCase def test_request_path root = dependency_request dep('net-ssh', '>= 2.0.13'), 'rye', '0.9.8' + child = - dependency_request dep('net-ssh', '>= 2.6.5'), 'net-ssh', '2.2.2', root + dependency_request dep('other', '>= 1.0'), 'net-ssh', '2.2.2', root conflict = - Gem::Resolver::Conflict.new child, nil + Gem::Resolver::Conflict.new nil, nil - assert_equal %w[net-ssh-2.2.2 rye-0.9.8], conflict.request_path + expected = [ + 'net-ssh-2.2.2 (>= 2.0.13)', + 'rye-0.9.8 (= 0.9.8)' + ] + + assert_equal expected, conflict.request_path(child.requester) end end diff --git a/test/rubygems/test_gem_resolver_git_set.rb b/test/rubygems/test_gem_resolver_git_set.rb index 6943df9ec8..1ca12a7251 100644 --- a/test/rubygems/test_gem_resolver_git_set.rb +++ b/test/rubygems/test_gem_resolver_git_set.rb @@ -66,6 +66,24 @@ class TestGemResolverGitSet < Gem::TestCase refute_empty @set.specs end + def test_prefetch_cache + name, _, repository, = git_gem + + @set.add_git_gem name, repository, 'master', false + + dependency = dep name + req = Gem::Resolver::ActivationRequest.new dependency, nil + @reqs.add req + + @set.prefetch @reqs + + spec = @set.specs[name] + + @set.prefetch @reqs + + assert_same spec, @set.specs[name] + end + def test_prefetch_filter name, _, repository, = git_gem @@ -77,7 +95,7 @@ class TestGemResolverGitSet < Gem::TestCase @set.prefetch @reqs - assert_empty @set.specs + refute_empty @set.specs, 'the git source does not filter' end end diff --git a/test/rubygems/test_gem_source_git.rb b/test/rubygems/test_gem_source_git.rb index f1c5eb9b71..9cdcbc0ed5 100644 --- a/test/rubygems/test_gem_source_git.rb +++ b/test/rubygems/test_gem_source_git.rb @@ -78,12 +78,6 @@ class TestGemSourceGit < Gem::TestCase refute_equal @source, source end - def test_load_spec - spec = @source.load_spec @name - - assert_equal "#{@name}-#{@version}", spec.full_name - end - def test_install_dir @source.cache @@ -133,6 +127,38 @@ class TestGemSourceGit < Gem::TestCase assert_equal(-1, git. <=>(installed), 'git <=> installed') end + def test_specs + source = Gem::Source::Git.new @name, @repository, 'master', true + + Dir.chdir 'git/a' do + FileUtils.mkdir 'b' + + Dir.chdir 'b' do + b = Gem::Specification.new 'b', 1 + + open 'b.gemspec', 'w' do |io| + io.write b.to_ruby + end + + system @git, 'add', 'b.gemspec' + system @git, 'commit', '--quiet', '-m', 'add b/b.gemspec' + end + + FileUtils.touch 'c.gemspec' + + system @git, 'add', 'c.gemspec' + system @git, 'commit', '--quiet', '-m', 'add c.gemspec' + end + + specs = nil + + capture_io do + specs = source.specs + end + + assert_equal %w[a-1 b-1], specs.map { |spec| spec.full_name } + end + def test_uri_hash assert_equal @hash, @source.uri_hash diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb index 3924191db9..d08e77faff 100644 --- a/test/rubygems/test_gem_specification.rb +++ b/test/rubygems/test_gem_specification.rb @@ -2447,8 +2447,8 @@ duplicate dependency on b (>= 1.2.3), (~> 1.2) use: end assert_match <<-warning, @ui.error -WARNING: licenses is empty. Use a license abbreviation from: - http://opensource.org/licenses/alphabetical +WARNING: licenses is empty, but is recommended. Use a license abbreviation from: +http://opensource.org/licenses/alphabetical warning end diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb index bf67b14d38..17a19dd302 100644 --- a/test/rubygems/test_gem_util.rb +++ b/test/rubygems/test_gem_util.rb @@ -5,6 +5,10 @@ class TestGemUtil < Gem::TestCase def test_class_popen assert_equal "0\n", Gem::Util.popen(Gem.ruby, '-e', 'p 0') + + assert_raises Errno::ECHILD do + Process.wait -1 + end end end