diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2013-07-09 23:21:36 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2013-07-09 23:21:36 +0000 |
commit | 47f0248b0858898dd24d1e654cedf174059ca677 () | |
tree | 493e84160f8609db408d88349f0624a3ff92c3c2 /lib | |
parent | cd9f9e471977447a991ced4ea38efb2309459ef5 (diff) |
* lib/rubygems: Import RubyGems 2.1
* test/rubygems: Ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@41873 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
64 files changed, 2958 insertions, 1568 deletions
@@ -8,7 +8,7 @@ require 'rbconfig' module Gem - VERSION = '2.0.4' end # Must be first since it unloads the prelude from 1.9.2 @@ -143,6 +143,14 @@ module Gem specifications ] @@win_platform = nil @configuration = nil @@ -379,6 +387,10 @@ module Gem paths.path end ## # Quietly ensure the Gem directory +dir+ contains all the proper # subdirectories. If we can't create a directory due to a permission @@ -389,6 +401,23 @@ module Gem # World-writable directories will never be created. def self.ensure_gem_subdirectories dir = Gem.dir, mode = nil old_umask = File.umask File.umask old_umask | 002 @@ -398,7 +427,7 @@ module Gem options[:mode] = mode if mode - REPOSITORY_SUBDIRECTORIES.each do |name| subdir = File.join dir, name next if File.exist? subdir FileUtils.mkdir_p subdir, options rescue nil @@ -971,10 +1000,33 @@ module Gem attr_reader :loaded_specs ## - # Register a Gem::Specification for default gem def register_default_spec(spec) spec.files.each do |file| @path_to_default_spec_map[file] = spec end end @@ -1,4 +1,7 @@ class Gem::AvailableSet Tuple = Struct.new(:spec, :source) def initialize @@ -36,6 +39,28 @@ class Gem::AvailableSet self end def empty? @set.empty? end @@ -66,6 +91,49 @@ class Gem::AvailableSet f.source end def pick_best! return self if empty? @@ -0,0 +1,139 @@ @@ -1,6 +1,11 @@ require 'rubygems/command' require 'rubygems/security' -require 'openssl' class Gem::Commands::CertCommand < Gem::Command @@ -21,7 +26,8 @@ class Gem::Commands::CertCommand < Gem::Command OptionParser.accept OpenSSL::PKey::RSA do |key_file| begin - key = OpenSSL::PKey::RSA.new File.read key_file rescue Errno::ENOENT raise OptionParser::InvalidArgument, "#{key_file}: does not exist" rescue OpenSSL::PKey::RSAError @@ -115,16 +121,31 @@ class Gem::Commands::CertCommand < Gem::Command end def build name - key = options[:key] || Gem::Security.create_key - cert = Gem::Security.create_cert_email name, key - key_path = Gem::Security.write key, "gem-private_key.pem" cert_path = Gem::Security.write cert, "gem-public_cert.pem" say "Certificate: #{cert_path}" - say "Private Key: #{key_path}" - say "Don't forget to move the key file to somewhere private!" end def certificates_matching filter @@ -198,7 +219,8 @@ For further reading on signing gems see `ri Gem::Security`. def load_default_key key_file = File.join Gem.default_key_path key = File.read key_file - options[:key] = OpenSSL::PKey::RSA.new key rescue Errno::ENOENT alert_error \ "--private-key not specified and ~/.gem/gem-private_key.pem does not exist" @@ -225,5 +247,5 @@ For further reading on signing gems see `ri Gem::Security`. Gem::Security.write cert, cert_file, permissions end -end @@ -9,7 +9,8 @@ class Gem::Commands::CleanupCommand < Gem::Command 'Clean up old versions of installed gems in the local repository', :force => false, :install_dir => Gem.dir - add_option('-d', '--dryrun', "") do |value, options| options[:dryrun] = true end @@ -162,4 +163,3 @@ are not removed. end end - @@ -99,6 +99,8 @@ lib/rubygems/defaults/operating_system.rb out << " - EXECUTABLE DIRECTORY: #{Gem.bindir}\n" out << " - RUBYGEMS PLATFORMS:\n" Gem.platforms.each do |platform| out << " - #{platform}\n" @@ -107,11 +109,9 @@ lib/rubygems/defaults/operating_system.rb out << " - GEM PATHS:\n" out << " - #{Gem.dir}\n" - path = Gem.path.dup - path.delete Gem.dir - path.each do |p| - out << " - #{p}\n" - end out << " - GEM CONFIGURATION:\n" Gem.configuration.each do |name, value| @@ -124,6 +124,11 @@ lib/rubygems/defaults/operating_system.rb out << " - #{s}\n" end else raise Gem::CommandLineError, "Unknown environment option [#{arg}]" end @@ -131,5 +136,11 @@ lib/rubygems/defaults/operating_system.rb true end end @@ -46,6 +46,10 @@ Some examples of 'gem' usage. * Update all gems on your system: gem update EOF PLATFORMS = <<-'EOF' @@ -55,8 +59,9 @@ your current platform by running `gem environment`. RubyGems matches platforms as follows: - * The CPU must match exactly, unless one of the platforms has - "universal" as the CPU. * The OS must match exactly. * The versions must match exactly unless one of the versions is nil. @@ -66,11 +71,20 @@ you pass must match "#{cpu}-#{os}" or "#{cpu}-#{os}-#{version}". On mswin platforms, the version is the compiler version, not the OS version. (Ruby compiled with VC6 uses "60" as the compiler version, VC8 uses "80".) Example platforms: x86-freebsd # Any FreeBSD version on an x86 CPU universal-darwin-8 # Darwin 8 only gems that run on any CPU x86-mswin32-80 # Windows gems compiled with VC8 When building platform gems, set the platform in the gem specification to Gem::Platform::CURRENT. This will correctly mark the gem with your ruby's @@ -119,7 +133,7 @@ platform. if command then command.summary else - "[No command found for #{cmd_name}, bug?]" end summary = wrap(summary, summary_width).split "\n" @@ -4,8 +4,6 @@ require 'rubygems/dependency_installer' require 'rubygems/local_remote_options' require 'rubygems/validator' require 'rubygems/version_option' -require 'rubygems/install_message' # must come before rdoc for messaging -require 'rubygems/rdoc' ## # Gem installer command line tool @@ -39,6 +37,12 @@ class Gem::Commands::InstallCommand < Gem::Command 'install the listed gems') do |v,o| o[:gemdeps] = v end @installed_specs = nil end @@ -153,7 +157,14 @@ to write the specification by hand. For example: alert_error "Can't use --version w/ multiple gems. Use name:ver instead." terminate_interaction 1 end - get_all_gem_names_and_versions.each do |gem_name, gem_version| gem_version ||= options[:version] @@ -31,9 +31,15 @@ class Gem::Commands::OwnerCommand < Gem::Command add_option '-r', '--remove EMAIL', 'Remove an owner' do |value, options| options[:remove] << value end end def execute sign_in name = get_one_gem_name @@ -30,6 +30,12 @@ class Gem::Commands::PristineCommand < Gem::Command options[:only_executables] = value end add_version_option('restore to', 'pristine condition') end @@ -104,16 +110,21 @@ with extensions. Gem::RemoteFetcher.fetcher.download_to_cache dep end - # TODO use installer options - install_defaults = Gem::ConfigFile::PLATFORM_DEFAULTS['install'] - installer_env_shebang = install_defaults.to_s['--env-shebang'] installer = Gem::Installer.new(gem, :wrappers => true, :force => true, :install_dir => spec.base_dir, - :env_shebang => installer_env_shebang, :build_args => spec.build_args) if options[:only_executables] then installer.generate_bin else @@ -48,7 +48,7 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:update]) if options[:clear_all] then - path = File.join Gem.user_home, '.gem', 'specs' FileUtils.rm_rf path unless File.exist? path then @@ -67,6 +67,12 @@ class Gem::Commands::UninstallCommand < Gem::Command options[:force] = value end add_version_option add_platform_option end @@ -141,6 +141,11 @@ class Gem::ConfigFile attr_reader :ssl_ca_cert ## # Create the config file object. +args+ is the list of arguments # from the command line. # @@ -210,6 +215,7 @@ class Gem::ConfigFile @ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode @ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert @api_keys = nil @rubygems_api_key = nil @@ -246,6 +252,10 @@ Your gem push credentials file located at: has file permissions of 0#{existing_permissions.to_s 8} but 0600 is required. You should reset your credentials at: \thttps://rubygems.org/profile/edit @@ -309,6 +319,9 @@ if you believe they were disclosed to a third party. @rubygems_api_key = api_key end def load_file(filename) Gem.load_yaml @@ -321,8 +334,8 @@ if you believe they were disclosed to a third party. return {} end return content - rescue ArgumentError - warn "Failed to load #{filename}" rescue Errno::EACCES warn "Failed to load #{filename} due to permissions problem." end @@ -57,7 +57,7 @@ module Kernel #-- # TODO request access to the C implementation of this to speed up RubyGems - spec = Gem::Specification.find { |s| s.activated? and s.contains_requirable_file? path } @@ -15,6 +15,14 @@ module Gem end ## # Default home directory path to be used if an alternate value is not # specified in the environment @@ -203,6 +203,8 @@ class Gem::Dependency requirement.satisfied_by? version end # DOC: this method needs either documented or :nodoc'd def match? obj, version=nil @@ -250,10 +252,10 @@ class Gem::Dependency # DOC: this method needs either documented or :nodoc'd def matching_specs platform_only = false - matches = Gem::Specification.find_all { |spec| self.name === spec.name and # TODO: == instead of === requirement.satisfied_by? spec.version - } if platform_only matches.reject! { |spec| @@ -1,11 +1,12 @@ require 'rubygems' require 'rubygems/dependency_list' require 'rubygems/package' require 'rubygems/installer' require 'rubygems/spec_fetcher' require 'rubygems/user_interaction' -require 'rubygems/source_local' -require 'rubygems/source_specific_file' require 'rubygems/available_set' ## @@ -15,15 +16,7 @@ class Gem::DependencyInstaller include Gem::UserInteraction - attr_reader :gems_to_install - attr_reader :installed_gems - - ## - # Documentation types. For use by the Gem.done_installing hook - - attr_reader :document - - DEFAULT_OPTIONS = { :env_shebang => false, :document => %w[ri], :domain => :both, # HACK dup @@ -35,9 +28,31 @@ class Gem::DependencyInstaller :wrappers => true, :build_args => nil, :build_docs_in_background => false, }.freeze ## # Creates a new installer instance. # # Options are: @@ -56,7 +71,8 @@ class Gem::DependencyInstaller # :wrappers:: See Gem::Installer::new # :build_args:: See Gem::Installer::new - def initialize(options = {}) @install_dir = options[:install_dir] || Gem.dir if options[:install_dir] then @@ -82,6 +98,7 @@ class Gem::DependencyInstaller @wrappers = options[:wrappers] @build_args = options[:build_args] @build_docs_in_background = options[:build_docs_in_background] # Indicates that we should not try to update any deps unless # we absolutely must. @@ -93,13 +110,61 @@ class Gem::DependencyInstaller @cache_dir = options[:cache_dir] || @install_dir - # Set with any errors that SpecFetcher finds while search through - # gemspecs for a dep @errors = nil end - attr_reader :errors ## # Creates an AvailableSet to install from based on +dep_or_name+ and # +version+ @@ -138,7 +203,7 @@ class Gem::DependencyInstaller # sources. Gems are sorted with newer gems preferred over older gems, and # local gems preferred over remote gems. - def find_gems_with_sources(dep) set = Gem::AvailableSet.new if consider_local? @@ -179,10 +244,52 @@ class Gem::DependencyInstaller end ## # Gathers all dependencies necessary for the installation from local and # remote sources unless the ignore_dependencies was given. - def gather_dependencies specs = @available.all_specs # these gems were listed by the user, always install them @@ -214,93 +321,19 @@ class Gem::DependencyInstaller @gems_to_install = dependency_list.dependency_order.reverse end - def add_found_dependencies to_do, dependency_list - seen = {} - dependencies = Hash.new { |h, name| h[name] = Gem::Dependency.new name } - - until to_do.empty? do - spec = to_do.shift - - # HACK why is spec nil? - next if spec.nil? or seen[spec.name] - seen[spec.name] = true - - deps = spec.runtime_dependencies - - if @development - if @dev_shallow - if @toplevel_specs.include? spec.full_name - deps |= spec.development_dependencies - end - else - deps |= spec.development_dependencies - end - end - - deps.each do |dep| - dependencies[dep.name] = dependencies[dep.name].merge dep - - if @minimal_deps - next if Gem::Specification.any? do |installed_spec| - dep.name == installed_spec.name and - dep.requirement.satisfied_by? installed_spec.version - end - end - - results = find_gems_with_sources(dep) - - results.sorted.each do |t| - to_do.push t.spec - end - - results.remove_installed! dep - - @available << results - results.inject_into_list dependency_list - end - end - - dependency_list.remove_specs_unsatisfied_by dependencies - end - - ## - # Finds a spec and the source_uri it came from for gem +gem_name+ and - # +version+. Returns an Array of specs and sources required for - # installation of the gem. - - def find_spec_by_name_and_version(gem_name, - version = Gem::Requirement.default, - prerelease = false) - - set = Gem::AvailableSet.new - - if consider_local? - if gem_name =~ /\.gem$/ and File.file? gem_name then - src = Gem::Source::SpecificFile.new(gem_name) - set.add src.spec, src - else - local = Gem::Source::Local.new - - if s = local.find_gem(gem_name, version) - set.add s, local end end end - - if set.empty? - dep = Gem::Dependency.new gem_name, version - # HACK Dependency objects should be immutable - dep.prerelease = true if prerelease - - set = find_gems_with_sources(dep) - set.match_platform! - end - - if set.empty? - raise Gem::SpecificGemNotFoundException.new(gem_name, version, @errors) - end - - @available = set end ## @@ -318,61 +351,30 @@ class Gem::DependencyInstaller # separately. def install dep_or_name, version = Gem::Requirement.default - available_set_for dep_or_name, version @installed_gems = [] - gather_dependencies - - # REFACTOR is the last gem always the one that the user requested? - # This code assumes that but is that actually validated by the code? - - last = @gems_to_install.size - 1 - @gems_to_install.each_with_index do |spec, index| - # REFACTOR more current spec set hardcoding, should be abstracted? - next if Gem::Specification.include?(spec) and index != last - - # TODO: make this sorta_verbose so other users can benefit from it - say "Installing gem #{spec.full_name}" if Gem.configuration.really_verbose - - source = @available.source_for spec - - begin - # REFACTOR make the fetcher to use configurable - local_gem_path = source.download spec, @cache_dir - rescue Gem::RemoteFetcher::FetchError - # TODO I doubt all fetch errors are recoverable, we should at least - # report the errors probably. - next if @force - raise - end - - if @development - if @dev_shallow - is_dev = @toplevel_specs.include? spec.full_name - else - is_dev = true - end - end - inst = Gem::Installer.new local_gem_path, - :bin_dir => @bin_dir, - :development => is_dev, - :env_shebang => @env_shebang, - :force => @force, - :format_executable => @format_executable, - :ignore_dependencies => @ignore_dependencies, - :install_dir => @install_dir, - :security_policy => @security_policy, - :user_install => @user_install, - :wrappers => @wrappers, - :build_args => @build_args - - spec = inst.install - - @installed_gems << spec end # Since this is currently only called for docs, we can be lazy and just say # it's documentation. Ideally the hook adder could decide whether to be in # the background or not, and what to call it. @@ -385,18 +387,34 @@ class Gem::DependencyInstaller @installed_gems end - def in_background what - fork_happened = false - if @build_docs_in_background and Process.respond_to?(:fork) - begin - Process.fork do - yield - end - fork_happened = true - say "#{what} in a background process." - rescue NotImplementedError - end end - yield unless fork_happened end end @@ -1,575 +1,240 @@ require 'rubygems' require 'rubygems/dependency' require 'rubygems/exceptions' require 'uri' require 'net/http' -module Gem - # Raised when a DependencyConflict reaches the toplevel. - # Indicates which dependencies were incompatible. - # - class DependencyResolutionError < Gem::Exception - def initialize(conflict) - @conflict = conflict - a, b = conflicting_dependencies - super "unable to resolve conflicting dependencies '#{a}' and '#{b}'" - end - attr_reader :conflict - def conflicting_dependencies - @conflict.conflicting_dependencies - end - end - # Raised when a dependency requests a gem for which there is - # no spec. - # - class UnsatisfiableDepedencyError < Gem::Exception - def initialize(dep) - super "unable to find any gem matching dependency '#{dep}'" - @dependency = dep - end - attr_reader :dependency - end - # Raised when dependencies conflict and create the inability to - # find a valid possible spec for a request. - # - class ImpossibleDependenciesError < Gem::Exception - def initialize(request, conflicts) - s = conflicts.size == 1 ? "" : "s" - super "detected #{conflicts.size} conflict#{s} with dependency '#{request.dependency}'" - @request = request - @conflicts = conflicts - end - def dependency - @request.dependency - end - attr_reader :conflicts end - # Given a set of Gem::Dependency objects as +needed+ and a way - # to query the set of available specs via +set+, calculates - # a set of ActivationRequest objects which indicate all the specs - # that should be activated to meet the all the requirements. # - class DependencyResolver - - # Represents a specification retrieved via the rubygems.org - # API. This is used to avoid having to load the full - # Specification object when all we need is the name, version, - # and dependencies. - # - class APISpecification - attr_reader :set # :nodoc: - - def initialize(set, api_data) - @set = set - @name = api_data[:name] - @version = Gem::Version.new api_data[:number] - @dependencies = api_data[:dependencies].map do |name, ver| - Gem::Dependency.new name, ver.split(/\s*,\s*/) - end - end - - attr_reader :name, :version, :dependencies - - def == other # :nodoc: - self.class === other and - @set == other.set and - @name == other.name and - @version == other.version and - @dependencies == other.dependencies - end - - def full_name - "#{@name}-#{@version}" - end - end - - # The global rubygems pool, available via the rubygems.org API. - # Returns instances of APISpecification. - # - class APISet - def initialize - @data = Hash.new { |h,k| h[k] = [] } - @dep_uri = URI 'https://rubygems.org/api/v1/dependencies' - end - - # Return data for all versions of the gem +name+. - # - def versions(name) - if @data.key?(name) - return @data[name] - end - - uri = @dep_uri + "?gems=#{name}" - str = Gem::RemoteFetcher.fetcher.fetch_path uri - - Marshal.load(str).each do |ver| - @data[ver[:name]] << ver - end - - @data[name] - end - - # Return an array of APISpecification objects matching - # DependencyRequest +req+. - # - def find_all(req) - res = [] - - versions(req.name).each do |ver| - if req.dependency.match? req.name, ver[:number] - res << APISpecification.new(self, ver) - end - end - - res - end - - # A hint run by the resolver to allow the Set to fetch - # data for DependencyRequests +reqs+. - # - def prefetch(reqs) - names = reqs.map { |r| r.dependency.name } - needed = names.find_all { |d| [email protected]?(d) } - - return if needed.empty? - - uri = @dep_uri + "?gems=#{needed.sort.join ','}" - str = Gem::RemoteFetcher.fetcher.fetch_path uri - - Marshal.load(str).each do |ver| - @data[ver[:name]] << ver - end - end - end - - # Represents a possible Specification object returned - # from IndexSet. Used to delay needed to download full - # Specification objects when only the +name+ and +version+ - # are needed. - # - class IndexSpecification - def initialize(set, name, version, source, plat) - @set = set - @name = name - @version = version - @source = source - @platform = plat - - @spec = nil - end - - attr_reader :name, :version, :source - - def full_name - "#{@name}-#{@version}" - end - - def spec - @spec ||= @set.load_spec(@name, @version, @source) - end - - def dependencies - spec.dependencies - end - end - - # The global rubygems pool represented via the traditional - # source index. - # - class IndexSet - def initialize - @f = Gem::SpecFetcher.fetcher - - @all = Hash.new { |h,k| h[k] = [] } - - list, _ = @f.available_specs(:released) - list.each do |uri, specs| - specs.each do |n| - @all[n.name] << [uri, n] - end - end - - @specs = {} - end - - # Return an array of IndexSpecification objects matching - # DependencyRequest +req+. - # - def find_all(req) - res = [] - - name = req.dependency.name - - @all[name].each do |uri, n| - if req.dependency.match? n - res << IndexSpecification.new(self, n.name, n.version, - uri, n.platform) - end - end - - res - end - - # No prefetching needed since we load the whole index in - # initially. - # - def prefetch(gems) - end - - # Called from IndexSpecification to get a true Specification - # object. - # - def load_spec(name, ver, source) - key = "#{name}-#{ver}" - @specs[key] ||= source.fetch_spec(Gem::NameTuple.new(name, ver)) - end - end - - # A set which represents the installed gems. Respects - # all the normal settings that control where to look - # for installed gems. - # - class CurrentSet - def find_all(req) - req.dependency.matching_specs - end - - def prefetch(gems) - end - end - - # Create DependencyResolver object which will resolve - # the tree starting with +needed+ Depedency objects. - # - # +set+ is an object that provides where to look for - # specifications to satisify the Dependencies. This - # defaults to IndexSet, which will query rubygems.org. - # - def initialize(needed, set=IndexSet.new) - @set = set || IndexSet.new # Allow nil to mean IndexSet - @needed = needed - - @conflicts = nil - end - - # Provide a DependencyResolver that queries only against - # the already installed gems. - # - def self.for_current_gems(needed) - new needed, CurrentSet.new - end - - # Contains all the conflicts encountered while doing resolution - # - attr_reader :conflicts - - # Proceed with resolution! Returns an array of ActivationRequest - # objects. - # - def resolve - @conflicts = [] - - needed = @needed.map { |n| DependencyRequest.new(n, nil) } - - res = resolve_for needed, [] - - if res.kind_of? DependencyConflict - raise DependencyResolutionError.new(res) - end - - res - end - - # Used internally to indicate that a dependency conflicted - # with a spec that would be activated. - # - class DependencyConflict - def initialize(dependency, activated, failed_dep=dependency) - @dependency = dependency - @activated = activated - @failed_dep = failed_dep - end - - attr_reader :dependency, :activated - - # Return the Specification that listed the dependency - # - def requester - @failed_dep.requester - end - - def for_spec?(spec) - @dependency.name == spec.name - end - # Return the 2 dependency objects that conflicted - # - def conflicting_dependencies - [@failed_dep.dependency, @activated.request.dependency] - end end - # Used Internally. Wraps a Depedency object to also track - # which spec contained the Dependency. - # - class DependencyRequest - def initialize(dep, act) - @dependency = dep - @requester = act - end - attr_reader :dependency, :requester - def name - @dependency.name - end - def matches_spec?(spec) - @dependency.matches_spec? spec - end - def to_s - @dependency.to_s - end - def ==(other) - case other - when Dependency - @dependency == other - when DependencyRequest - @dependency == other.dependency && @requester == other.requester - else - false - end - end end - # Specifies a Specification object that should be activated. - # Also contains a dependency that was used to introduce this - # activation. - # - class ActivationRequest - def initialize(spec, req, others_possible=true) - @spec = spec - @request = req - @others_possible = others_possible - end - - attr_reader :spec, :request - # Indicate if this activation is one of a set of possible - # requests for the same Dependency request. - # - def others_possible? - @others_possible - end - # Return the ActivationRequest that contained the dependency - # that we were activated for. - # - def parent - @request.requester - end - - def name - @spec.name - end - - def full_name - @spec.full_name - end - - def version - @spec.version - end - - def full_spec - Gem::Specification === @spec ? @spec : @spec.spec - end - def download(path) - if @spec.respond_to? :source - source = @spec.source else - source = Gem.sources.first end - Gem.ensure_gem_subdirectories path - - source.download full_spec, path - end - - def ==(other) - case other - when Gem::Specification - @spec == other - when ActivationRequest - @spec == other.spec && @request == other.request - else - false - end end - ## - # Indicates if the requested gem has already been installed. - def installed? - this_spec = full_spec - Gem::Specification.any? do |s| - s == this_spec end - end - end - - def requests(s, act) - reqs = [] - s.dependencies.each do |d| - next unless d.type == :runtime - reqs << DependencyRequest.new(d, act) - end - - @set.prefetch(reqs) - - reqs - end - - # The meat of the algorithm. Given +needed+ DependencyRequest objects - # and +specs+ being a list to ActivationRequest, calculate a new list - # of ActivationRequest objects. - # - def resolve_for(needed, specs) - until needed.empty? - dep = needed.shift - - # If there is already a spec activated for the requested name... - if existing = specs.find { |s| dep.name == s.name } - - # then we're done since this new dep matches the - # existing spec. - next if dep.matches_spec? existing - - # There is a conflict! We return the conflict - # object which will be seen by the caller and be - # handled at the right level. - - # If the existing activation indicates that there - # are other possibles for it, then issue the conflict - # on the dep for the activation itself. Otherwise, issue - # it on the requester's request itself. - # - if existing.others_possible? - conflict = DependencyConflict.new(dep, existing) else - depreq = existing.request.requester.request - conflict = DependencyConflict.new(depreq, existing, dep) end - @conflicts << conflict - - return conflict end - # Get a list of all specs that satisfy dep - possible = @set.find_all(dep) - - case possible.size - when 0 - # If there are none, then our work here is done. - raise UnsatisfiableDepedencyError.new(dep) - when 1 - # If there is one, then we just add it to specs - # and process the specs dependencies by adding - # them to needed. - - spec = possible.first - act = ActivationRequest.new(spec, dep, false) - - specs << act - - # Put the deps for at the beginning of needed - # rather than the end to match the depth first - # searching done by the multiple case code below. - # - # This keeps the error messages consistent. - needed = requests(spec, act) + needed - else - # There are multiple specs for this dep. This is - # the case that this class is built to handle. - - # Sort them so that we try the highest versions - # first. - possible = possible.sort_by { |s| s.version } - - # We track the conflicts seen so that we can report them - # to help the user figure out how to fix the situation. - conflicts = [] - - # To figure out which to pick, we keep resolving - # given each one being activated and if there isn't - # a conflict, we know we've found a full set. - # - # We use an until loop rather than #reverse_each - # to keep the stack short since we're using a recursive - # algorithm. - # - until possible.empty? - s = possible.pop - - # Recursively call #resolve_for with this spec - # and add it's dependencies into the picture... - - act = ActivationRequest.new(s, dep) - - try = requests(s, act) + needed - - res = resolve_for(try, specs + [act]) - - # While trying to resolve these dependencies, there may - # be a conflict! - - if res.kind_of? DependencyConflict - # The conflict might be created not by this invocation - # but rather one up the stack, so if we can't attempt - # to resolve this conflict (conflict isn't with the spec +s+) - # then just return it so the caller can try to sort it out. - return res unless res.for_spec? s - - # Otherwise, this is a conflict that we can attempt to fix - conflicts << [s, res] - - # Optimization: - # - # Because the conflict indicates the dependency that trigger - # it, we can prune possible based on this new information. - # - # This cuts down on the number of iterations needed. - possible.delete_if { |x| !res.dependency.matches_spec? x } - else - # No conflict, return the specs - return res - end - end - - # We tried all possibles and nothing worked, so we let the user - # know and include as much information about the problem since - # the user is going to have to take action to fix this. - raise ImpossibleDependenciesError.new(dep, conflicts) - end end - - specs end end end @@ -0,0 +1,109 @@ @@ -0,0 +1,65 @@ @@ -0,0 +1,36 @@ @@ -0,0 +1,18 @@ @@ -0,0 +1,16 @@ @@ -0,0 +1,85 @@ @@ -0,0 +1,51 @@ @@ -0,0 +1,59 @@ @@ -0,0 +1,53 @@ @@ -0,0 +1,38 @@ @@ -0,0 +1,130 @@ @@ -17,6 +17,28 @@ class Gem::DependencyError < Gem::Exception; end class Gem::DependencyRemovalException < Gem::Exception; end ## # Raised when attempting to uninstall a gem that isn't in GEM_HOME. class Gem::GemNotInHomeException < Gem::Exception @@ -65,6 +87,42 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException attr_reader :name, :version, :errors end class Gem::InstallError < Gem::Exception; end ## @@ -107,3 +165,26 @@ class Gem::SystemExitException < SystemExit end @@ -18,7 +18,7 @@ class Gem::Ext::Builder # try to find make program from Ruby configure arguments first RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/ - make_program = $1 || ENV['MAKE'] || ENV['make'] unless make_program then make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make' end @@ -33,17 +33,11 @@ class Gem::GemRunner ## # Run the gem command with the following arguments. - def run(args) - if args.include?('--') - # We need to preserve the original ARGV to use for passing gem options - # to source gems. If there is a -- in the line, strip all options after - # it...its for the source building process. - # TODO use slice! - build_args = args[args.index("--") + 1...args.length] - args = args[0...args.index("--")] - end do_configuration args cmd = @command_manager_class.instance cmd.command_names.each do |command_name| @@ -60,6 +54,20 @@ class Gem::GemRunner cmd.run Gem.configuration.args, build_args end private def do_configuration(args) @@ -1,11 +1,17 @@ require 'rubygems/remote_fetcher' module Gem::GemcutterUtilities # TODO: move to Gem::Command OptionParser.accept Symbol do |value| value.to_sym end ## # Add the --key option @@ -17,6 +23,9 @@ module Gem::GemcutterUtilities end end def api_key if options[:key] then verify_api_key options[:key] @@ -27,6 +36,47 @@ module Gem::GemcutterUtilities end end def sign_in sign_in_host = self.host return if Gem.configuration.rubygems_api_key @@ -55,47 +105,36 @@ module Gem::GemcutterUtilities end end - attr_writer :host - def host - configured_host = Gem.host unless - Gem.configuration.disable_default_gem_server - - @host ||= - begin - env_rubygems_host = ENV['RUBYGEMS_HOST'] - env_rubygems_host = nil if - env_rubygems_host and env_rubygems_host.empty? - - env_rubygems_host|| configured_host - end - end - - def rubygems_api_request(method, path, host = nil, &block) - require 'net/http' - self.host = host if host - unless self.host - alert_error "You must specify a gem server" terminate_interaction 1 # TODO: question this end - - uri = URI.parse "#{self.host}/#{path}" - - request_method = Net::HTTP.const_get method.to_s.capitalize - - Gem::RemoteFetcher.fetcher.request(uri, request_method, &block) end - def with_response resp, error_prefix = nil - case resp when Net::HTTPSuccess then if block_given? then - yield resp else - say resp.body end else - message = resp.body message = "#{error_prefix}: #{message}" if error_prefix say message @@ -103,13 +142,5 @@ module Gem::GemcutterUtilities end end - def verify_api_key(key) - if Gem.configuration.api_keys.key? key then - Gem.configuration.api_keys[key] - else - alert_error "No such API key. Please add it to your configuration (done automatically on initial `gem push`)." - terminate_interaction 1 # TODO: question this - end - end - end @@ -0,0 +1,12 @@ @@ -26,6 +26,9 @@ module Gem::InstallUpdateOptions OptionParser.accept Gem::Security::Policy do |value| require 'rubygems/security' value = Gem::Security::Policies[value] valid = Gem::Security::Policies.keys.sort message = "#{value} (#{valid.join ', '} are valid)" @@ -212,16 +212,21 @@ class Gem::Installer FileUtils.rm_rf gem_dir FileUtils.mkdir_p gem_dir - - extract_files - - build_extensions - write_build_info_file - run_post_build_hooks - - generate_bin - write_spec - write_cache_file say spec.post_install_message unless spec.post_install_message.nil? @@ -327,6 +332,14 @@ class Gem::Installer end ## # Writes the .gemspec specification (in Ruby) to the gem home's # specifications directory. @@ -336,6 +349,16 @@ class Gem::Installer file.fsync rescue nil # for filesystems without fsync(2) end end ## # Creates windows .bat files for easy running of commands @@ -538,13 +561,13 @@ class Gem::Installer :bin_dir => nil, :env_shebang => false, :force => false, - :install_dir => Gem.dir, :only_install_dir => false }.merge options @env_shebang = options[:env_shebang] @force = options[:force] - @gem_home = options[:install_dir] @ignore_dependencies = options[:ignore_dependencies] @format_executable = options[:format_executable] @security_policy = options[:security_policy] @@ -715,6 +738,15 @@ EOF def extract_files @package.extract_files gem_dir end ## # Prefix and suffix the program filename the same as ruby. @@ -756,7 +788,11 @@ EOF ensure_loadable_spec - Gem.ensure_gem_subdirectories gem_home return true if @force @@ -43,6 +43,20 @@ class Gem::NameTuple end ## # Indicate if this NameTuple matches the current platform. def match_platform? @@ -59,12 +73,7 @@ class Gem::NameTuple # Return the name that the gemspec file would be def spec_name - case @platform - when nil, 'ruby', '' - "#{@name}-#{@version}.gemspec" - else - "#{@name}-#{@version}-#{@platform}.gemspec" - end end ## @@ -74,10 +83,12 @@ class Gem::NameTuple [@name, @version, @platform] end - def to_s "#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>" end def <=> other to_a <=> other.to_a end @@ -280,11 +280,16 @@ EOM algorithms = if @checksums then @checksums.keys else - [Gem::Security::DIGEST_NAME] end algorithms.each do |algorithm| - digester = OpenSSL::Digest.new algorithm digester << entry.read(16384) until entry.eof? @@ -298,8 +303,11 @@ EOM ## # Extracts the files in this package into +destination_dir+ - def extract_files destination_dir verify unless @spec FileUtils.mkdir_p destination_dir @@ -310,7 +318,7 @@ EOM reader.each do |entry| next unless entry.full_name == 'data.tar.gz' - extract_tar_gz entry, destination_dir return # ignore further entries end @@ -324,10 +332,15 @@ EOM # If an entry in the archive contains a relative path above # +destination_dir+ or an absolute path is encountered an exception is # raised. - def extract_tar_gz io, destination_dir # :nodoc: open_tar_gz io do |tar| tar.each do |entry| destination = install_location entry.full_name, destination_dir FileUtils.rm_rf destination @@ -428,12 +441,13 @@ EOM # certificate and key are not present only checksum generation is set up. def setup_signer if @spec.signing_key then - @signer = Gem::Security::Signer.new @spec.signing_key, @spec.cert_chain @spec.signing_key = nil @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_s } else - @signer = Gem::Security::Signer.new nil, nil @spec.cert_chain = @signer.cert_chain.map { |cert| cert.to_pem } if @signer.cert_chain end @@ -510,27 +524,38 @@ EOM end ## # Verifies the files of the +gem+ def verify_files gem gem.each do |entry| - file_name = entry.full_name - @files << file_name - - case file_name - when /\.sig$/ then - @signatures[$`] = entry.read if @security_policy - next - else - digest entry - end - - case file_name - when /^metadata(.gz)?$/ then - load_spec entry - when 'data.tar.gz' then - verify_gz entry - end end unless @spec then @@ -71,7 +71,7 @@ class Gem::Package::TarTestCase < Gem::TestCase SP(Z(to_oct(sum, 6))) end - def header(type, fname, dname, length, mode, checksum = nil) checksum ||= " " * 8 arr = [ # struct tarfile_entry_posix @@ -80,7 +80,7 @@ class Gem::Package::TarTestCase < Gem::TestCase Z(to_oct(0, 7)), # char uid[8]; ditto Z(to_oct(0, 7)), # char gid[8]; ditto Z(to_oct(length, 11)), # char size[12]; 0 padded, octal, null - Z(to_oct(0, 11)), # char mtime[12]; 0 padded, octal, null checksum, # char checksum[8]; 0 padded, octal, null, space type, # char typeflag[1]; file: "0" dir: "5" "\0" * 100, # char linkname[100]; ASCII + (Z unless filled) @@ -105,16 +105,16 @@ class Gem::Package::TarTestCase < Gem::TestCase ret end - def tar_dir_header(name, prefix, mode) - h = header("5", name, prefix, 0, mode) checksum = calc_checksum(h) - header("5", name, prefix, 0, mode, checksum) end - def tar_file_header(fname, dname, mode, length) - h = header("0", fname, dname, length, mode) checksum = calc_checksum(h) - header("0", fname, dname, length, mode, checksum) end def to_oct(n, pad_size) @@ -130,7 +130,7 @@ class Gem::Package::TarTestCase < Gem::TestCase end def util_dir_entry - util_entry tar_dir_header("foo", "bar", 0) end end @@ -4,6 +4,8 @@ # See LICENSE.txt for additional licensing information. #++ ## # Allows writing of tar files @@ -121,7 +123,8 @@ class Gem::Package::TarWriter @io.pos = init_pos header = Gem::Package::TarHeader.new :name => name, :mode => mode, - :size => size, :prefix => prefix @io.write header @io.pos = final_pos @@ -140,7 +143,15 @@ class Gem::Package::TarWriter def add_file_digest name, mode, digest_algorithms # :yields: io digests = digest_algorithms.map do |digest_algorithm| digest = digest_algorithm.new - [digest.name, digest] end digests = Hash[*digests.flatten] @@ -165,22 +176,32 @@ class Gem::Package::TarWriter def add_file_signed name, mode, signer digest_algorithms = [ signer.digest_algorithm, - OpenSSL::Digest::SHA512, - ].uniq digests = add_file_digest name, mode, digest_algorithms do |io| yield io end - signature_digest = digests.values.find do |digest| - digest.name == signer.digest_name end - signature = signer.sign signature_digest.digest - add_file_simple "#{name}.sig", 0444, signature.length do |io| - io.write signature - end if signature digests end @@ -195,7 +216,8 @@ class Gem::Package::TarWriter name, prefix = split_name name header = Gem::Package::TarHeader.new(:name => name, :mode => mode, - :size => size, :prefix => prefix).to_s @io.write header os = BoundedStream.new @io, size @@ -256,7 +278,8 @@ class Gem::Package::TarWriter header = Gem::Package::TarHeader.new :name => name, :mode => mode, :typeflag => "5", :size => 0, - :prefix => prefix @io.write header @@ -13,6 +13,10 @@ class Gem::PathSupport attr_reader :path ## # # Constructor. Takes a single argument which is to be treated like a # hashtable, or defaults to ENV, the system environment. @@ -28,6 +32,10 @@ class Gem::PathSupport end self.path = env["GEM_PATH"] || ENV["GEM_PATH"] end private @@ -2,6 +2,8 @@ require "rubygems/deprecate" ## # Available list of platforms for targeting Gem installations. class Gem::Platform @@ -129,12 +131,16 @@ class Gem::Platform # Does +other+ match this platform? Two platforms match if they have the # same CPU, or either has a CPU of 'universal', they have the same OS, and # they have the same version, or either has no version. def ===(other) return nil unless Gem::Platform === other # cpu - (@cpu == 'universal' or other.cpu == 'universal' or @cpu == other.cpu) and # os @os == other.os and @@ -1,6 +1,7 @@ require 'rubygems' require 'rubygems/user_interaction' -require 'uri' require 'resolv' ## @@ -71,17 +72,7 @@ class Gem::RemoteFetcher Socket.do_not_reverse_lookup = true - @connections = {} - @requests = Hash.new 0 - @proxy_uri = - case proxy - when :no_proxy then nil - when nil then get_proxy_from_env - when URI::HTTP then proxy - else URI.parse(proxy) - end - @user_agent = user_agent - @env_no_proxy = get_no_proxy_from_env @dns = dns end @@ -200,7 +191,7 @@ class Gem::RemoteFetcher source_uri.path end - source_path = unescape source_path begin FileUtils.cp source_path, local_gem_path unless @@ -319,125 +310,6 @@ class Gem::RemoteFetcher response['content-length'].to_i end - def escape(str) - return unless str - @uri_parser ||= uri_escaper - @uri_parser.escape str - end - - def unescape(str) - return unless str - @uri_parser ||= uri_escaper - @uri_parser.unescape str - end - - def uri_escaper - URI::Parser.new - rescue NameError - URI - end - - ## - # Returns list of no_proxy entries (if any) from the environment - - def get_no_proxy_from_env - env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY'] - - return [] if env_no_proxy.nil? or env_no_proxy.empty? - - env_no_proxy.split(/\s*,\s*/) - end - - ## - # Returns an HTTP proxy URI if one is set in the environment variables. - - def get_proxy_from_env - env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] - - return nil if env_proxy.nil? or env_proxy.empty? - - uri = URI.parse(normalize_uri(env_proxy)) - - if uri and uri.user.nil? and uri.password.nil? then - # Probably we have http_proxy_* variables? - uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']) - uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']) - end - - uri - end - - ## - # Normalize the URI by adding "http://" if it is missing. - - def normalize_uri(uri) - (uri =~ /^(https?|ftp|file):/i) ? uri : "http://#{uri}" - end - - ## - # Creates or an HTTP connection based on +uri+, or retrieves an existing - # connection, using a proxy if needed. - - def connection_for(uri) - net_http_args = [uri.host, uri.port] - - if @proxy_uri and not no_proxy?(uri.host) then - net_http_args += [ - @proxy_uri.host, - @proxy_uri.port, - @proxy_uri.user, - @proxy_uri.password - ] - end - - connection_id = [Thread.current.object_id, *net_http_args].join ':' - @connections[connection_id] ||= Net::HTTP.new(*net_http_args) - connection = @connections[connection_id] - - if https?(uri) and not connection.started? then - configure_connection_for_https(connection) - end - - connection.start unless connection.started? - - connection - rescue defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : Errno::EHOSTDOWN, - Errno::EHOSTDOWN => e - raise FetchError.new(e.message, uri) - end - - def configure_connection_for_https(connection) - require 'net/https' - connection.use_ssl = true - connection.verify_mode = - Gem.configuration.ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER - store = OpenSSL::X509::Store.new - if Gem.configuration.ssl_ca_cert - if File.directory? Gem.configuration.ssl_ca_cert - store.add_path Gem.configuration.ssl_ca_cert - else - store.add_file Gem.configuration.ssl_ca_cert - end - else - store.set_default_paths - add_rubygems_trusted_certs(store) - end - connection.cert_store = store - rescue LoadError => e - raise unless (e.respond_to?(:path) && e.path == 'openssl') || - e.message =~ / -- openssl$/ - - raise Gem::Exception.new( - 'Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources') - end - - def add_rubygems_trusted_certs(store) - pattern = File.expand_path("./ssl_certs/*.pem", File.dirname(__FILE__)) - Dir.glob(pattern).each do |ssl_cert_file| - store.add_file ssl_cert_file - end - end - def correct_for_windows_path(path) if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':' path = path[1..-1] @@ -446,136 +318,13 @@ class Gem::RemoteFetcher end end - def no_proxy? host - host = host.downcase - @env_no_proxy.each do |pattern| - pattern = pattern.downcase - return true if host[-pattern.length, pattern.length ] == pattern - end - return false - end - ## # Performs a Net::HTTP request of type +request_class+ on +uri+ returning # a Net::HTTP response object. request maintains a table of persistent # connections to reduce connect overhead. def request(uri, request_class, last_modified = nil) - request = request_class.new uri.request_uri - - unless uri.nil? || uri.user.nil? || uri.user.empty? then - request.basic_auth uri.user, uri.password - end - - request.add_field 'User-Agent', @user_agent - request.add_field 'Connection', 'keep-alive' - request.add_field 'Keep-Alive', '30' - - if last_modified then - last_modified = last_modified.utc - request.add_field 'If-Modified-Since', last_modified.rfc2822 - end - - yield request if block_given? - - connection = connection_for uri - - retried = false - bad_response = false - - begin - @requests[connection.object_id] += 1 - - say "#{request.method} #{uri}" if - Gem.configuration.really_verbose - - file_name = File.basename(uri.path) - # perform download progress reporter only for gems - if request.response_body_permitted? && file_name =~ /\.gem$/ - reporter = ui.download_reporter - response = connection.request(request) do |incomplete_response| - if Net::HTTPOK === incomplete_response - reporter.fetch(file_name, incomplete_response.content_length) - downloaded = 0 - data = '' - - incomplete_response.read_body do |segment| - data << segment - downloaded += segment.length - reporter.update(downloaded) - end - reporter.done - if incomplete_response.respond_to? :body= - incomplete_response.body = data - else - incomplete_response.instance_variable_set(:@body, data) - end - end - end - else - response = connection.request request - end - - say "#{response.code} #{response.message}" if - Gem.configuration.really_verbose - - rescue Net::HTTPBadResponse - say "bad response" if Gem.configuration.really_verbose - - reset connection - - raise FetchError.new('too many bad responses', uri) if bad_response - - bad_response = true - retry - # HACK work around EOFError bug in Net::HTTP - # NOTE Errno::ECONNABORTED raised a lot on Windows, and make impossible - # to install gems. - rescue EOFError, Timeout::Error, - Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE - - requests = @requests[connection.object_id] - say "connection reset after #{requests} requests, retrying" if - Gem.configuration.really_verbose - - raise FetchError.new('too many connection resets', uri) if retried - - reset connection - - retried = true - retry - end - - response - end - - ## - # Resets HTTP connection +connection+. - - def reset(connection) - @requests.delete connection.object_id - - connection.finish - connection.start - end - - def user_agent - ua = "RubyGems/#{Gem::VERSION} #{Gem::Platform.local}" - - ruby_version = RUBY_VERSION - ruby_version += 'dev' if RUBY_LEVEL == -1 - - ua << " Ruby/#{ruby_version} (#{RUBY_RELEASE_DATE}" - if RUBY_LEVEL >= 0 then - ua << " level #{RUBY_LEVEL}" - elsif defined?(RUBY_REVISION) then - ua << " revision #{RUBY_REVISION}" - end - ua << ")" - - ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby' - - ua end def https?(uri) @@ -0,0 +1,262 @@ @@ -5,178 +5,176 @@ require 'rubygems/dependency_list' require 'rubygems/installer' require 'tsort' -module Gem - class RequestSet - include TSort - def initialize(*deps) - @dependencies = deps - yield self if block_given? - end - attr_reader :dependencies - # Declare that a gem of name +name+ with +reqs+ requirements - # is needed. - # - def gem(name, *reqs) - @dependencies << Gem::Dependency.new(name, reqs) - end - # Add +deps+ Gem::Depedency objects to the set. - # - def import(deps) - @dependencies += deps - end - # Resolve the requested dependencies and return an Array of - # Specification objects to be activated. - # - def resolve(set=nil) - r = Gem::DependencyResolver.new(@dependencies, set) - @requests = r.resolve - end - # Resolve the requested dependencies against the gems - # available via Gem.path and return an Array of Specification - # objects to be activated. - # - def resolve_current - resolve DependencyResolver::CurrentSet.new - end - # Load a dependency management file. - # - def load_gemdeps(path) - gf = GemDepedencyAPI.new(self, path) - gf.load - end - def specs - @specs ||= @requests.map { |r| r.full_spec } - end - def tsort_each_node(&block) - @requests.each(&block) - end - def tsort_each_child(node) - node.spec.dependencies.each do |dep| - next if dep.type == :development - - match = @requests.find { |r| dep.match? r.spec.name, r.spec.version } - if match - begin - yield match - rescue TSort::Cyclic - end - else - raise Gem::DependencyError, "Unresolved depedency found during sorting - #{dep}" - end - end - end - def sorted_requests - @sorted ||= strongly_connected_components.flatten end - def specs_in(dir) - Dir["#{dir}/specifications/*.gemspec"].map do |g| - Gem::Specification.load g end - end - def install_into(dir, force=true, &b) - existing = force ? [] : specs_in(dir) - dir = File.expand_path dir - installed = [] - sorted_requests.each do |req| - if existing.find { |s| s.full_name == req.spec.full_name } - b.call req, nil if b - next - end - path = req.download(dir) - inst = Gem::Installer.new path, :install_dir => dir, - :only_install_dir => true - b.call req, inst if b - inst.install - installed << req end - installed - end - def install(options, &b) - if dir = options[:install_dir] - return install_into(dir, false, &b) end - cache_dir = options[:cache_dir] || Gem.dir - specs = [] - sorted_requests.each do |req| - if req.installed? - b.call req, nil if b - next - end - path = req.download cache_dir - inst = Gem::Installer.new path, options - b.call req, inst if b - specs << inst.install - end - specs - end - # A semi-compatible DSL for Bundler's Gemfile format - # - class GemDepedencyAPI - def initialize(set, path) - @set = set - @path = path - end - def load - instance_eval File.read(@path).untaint, @path, 1 - end - # DSL - def source(url) - end - def gem(name, *reqs) - # Ignore the opts for now. - reqs.pop if reqs.last.kind_of?(Hash) - @set.gem name, *reqs - end - def platform(what) - if what == :ruby - yield - end - end - alias_method :platforms, :platform - def group(*what) end end end end @@ -0,0 +1,39 @@ @@ -12,20 +12,6 @@ begin rescue LoadError => e raise unless (e.respond_to?(:path) && e.path == 'openssl') || e.message =~ / -- openssl$/ - - module OpenSSL # :nodoc: - class Digest # :nodoc: - class SHA1 # :nodoc: - def name - 'SHA1' - end - end - end - module PKey # :nodoc: - class RSA # :nodoc: - end - end - end end ## @@ -352,17 +338,26 @@ module Gem::Security ## # Digest algorithm used to sign gems - DIGEST_ALGORITHM = OpenSSL::Digest::SHA1 ## # Used internally to select the signing digest from all computed digests - DIGEST_NAME = DIGEST_ALGORITHM.new.name # :nodoc: ## # Algorithm for creating the key pair used to sign gems - KEY_ALGORITHM = OpenSSL::PKey::RSA ## # Length of keys created by KEY_ALGORITHM @@ -370,6 +365,12 @@ module Gem::Security KEY_LENGTH = 2048 ## # One year in seconds ONE_YEAR = 86400 * 365 @@ -563,13 +564,18 @@ module Gem::Security ## # Writes +pemmable+, which must respond to +to_pem+ to +path+ with the given - # +permissions+. - def self.write pemmable, path, permissions = 0600 path = File.expand_path path open path, 'wb', permissions do |io| - io.write pemmable.to_pem end path @@ -579,8 +585,11 @@ module Gem::Security end -require 'rubygems/security/policy' -require 'rubygems/security/policies' require 'rubygems/security/signer' -require 'rubygems/security/trust_dir' @@ -1,3 +1,5 @@ ## # A Gem::Security::Policy object encapsulates the settings for verifying # signed gem files. This is the base class. You can either declare an @@ -6,6 +8,8 @@ class Gem::Security::Policy attr_reader :name attr_accessor :only_signed @@ -175,6 +179,19 @@ class Gem::Security::Policy true end def inspect # :nodoc: ("[Policy: %s - data: %p signer: %p chain: %p root: %p " + "signed-only: %p trusted-only: %p]") % [ @@ -184,16 +201,21 @@ class Gem::Security::Policy end ## - # Verifies the certificate +chain+ is valid, the +digests+ match the - # signatures +signatures+ created by the signer depending on the +policy+ - # settings. # # If +key+ is given it is used to validate the signing certificate. - def verify chain, key = nil, digests = {}, signatures = {} - if @only_signed and signatures.empty? then - raise Gem::Security::Exception, - "unsigned gems are not allowed by the #{name} policy" end opt = @opt @@ -222,7 +244,11 @@ class Gem::Security::Policy check_root chain, time if @verify_root - check_trust chain, digester, trust_dir if @only_trusted signatures.each do |file, _| digest = signer_digests[file] @@ -252,7 +278,7 @@ class Gem::Security::Policy OpenSSL::X509::Certificate.new cert_pem end - verify chain, nil, digests, signatures true end @@ -29,7 +29,7 @@ class Gem::Security::Signer # +chain+ containing X509 certificates, encoding certificates or paths to # certificates. - def initialize key, cert_chain @cert_chain = cert_chain @key = key @@ -46,7 +46,7 @@ class Gem::Security::Signer @digest_algorithm = Gem::Security::DIGEST_ALGORITHM @digest_name = Gem::Security::DIGEST_NAME - @key = OpenSSL::PKey::RSA.new File.read @key if @key and not OpenSSL::PKey::RSA === @key if @cert_chain then @@ -25,14 +25,21 @@ class Gem::Source end def <=>(other) - if !@uri - return 0 unless other.uri - return -1 - end - return 1 if !other.uri - @uri.to_s <=> other.uri.to_s end include Comparable @@ -58,8 +65,7 @@ class Gem::Source def cache_dir(uri) # Correct for windows paths escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/') - root = File.join Gem.user_home, '.gem', 'specs' - File.join root, "#{uri.host}%#{uri.port}", File.dirname(escaped_path) end def update_cache? @@ -141,4 +147,14 @@ class Gem::Source fetcher = Gem::RemoteFetcher.fetcher fetcher.download spec, @uri.to_s, dir end end @@ -0,0 +1,28 @@ @@ -0,0 +1,122 @@ @@ -0,0 +1,28 @@ @@ -1,92 +1,5 @@ require 'rubygems/source' -class Gem::Source::Local < Gem::Source - def initialize - @uri = nil - end - def load_specs(type) - names = [] - - @specs = {} - - Dir["*.gem"].each do |file| - begin - pkg = Gem::Package.new(file) - rescue SystemCallError, Gem::Package::FormatError - # ignore - else - tup = pkg.spec.name_tuple - @specs[tup] = [File.expand_path(file), pkg] - - case type - when :released - unless pkg.spec.version.prerelease? - names << pkg.spec.name_tuple - end - when :prerelease - if pkg.spec.version.prerelease? - names << pkg.spec.name_tuple - end - when :latest - tup = pkg.spec.name_tuple - - cur = names.find { |x| x.name == tup.name } - if !cur - names << tup - elsif cur.version < tup.version - names.delete cur - names << tup - end - else - names << pkg.spec.name_tuple - end - end - end - - names - end - - def find_gem(gem_name, version=Gem::Requirement.default, - prerelease=false) - load_specs :complete - - found = [] - - @specs.each do |n, data| - if n.name == gem_name - s = data[1].spec - - if version.satisfied_by?(s.version) - if prerelease - found << s - elsif !s.version.prerelease? - found << s - end - end - end - end - - found.sort_by { |s| s.version }.last - end - - def fetch_spec(name) - load_specs :complete - - if data = @specs[name] - data.last.spec - else - raise Gem::Exception, "Unable to find spec for '#{name}'" - end - end - - def download(spec, cache_dir=nil) - load_specs :complete - - @specs.each do |name, data| - return data[0] if data[1].spec == spec - end - - raise Gem::Exception, "Unable to find file for '#{spec.full_name}'" - end -end @@ -1,28 +1,4 @@ -class Gem::Source::SpecificFile < Gem::Source - def initialize(file) - @uri = nil - @path = ::File.expand_path(file) - @package = Gem::Package.new @path - @spec = @package.spec - @name = @spec.name_tuple - end - attr_reader :spec - - def load_specs(*a) - [@name] - end - - def fetch_spec(name) - return @spec if name == @name - raise Gem::Exception, "Unable to find '#{name}'" - @spec - end - - def download(spec, dir=nil) - return @path if spec == @spec - raise Gem::Exception, "Unable to download '#{spec.full_name}'" - end - -end @@ -38,7 +38,6 @@ class Gem::SpecFetcher end def initialize - @dir = File.join Gem.user_home, '.gem', 'specs' @update_cache = File.stat(Gem.user_home).uid == Process.uid @specs = {} @@ -5,10 +5,13 @@ # See LICENSE.txt for permissions. #++ require 'rubygems/version' require 'rubygems/requirement' require 'rubygems/platform' require 'rubygems/deprecate' # :stopdoc: # date.rb can't be loaded for `make install` due to miniruby @@ -45,7 +48,7 @@ class Date; end # # s.metadata = { "bugtracker" => "http://somewhere.com/blah" } -class Gem::Specification # REFACTOR: Consider breaking out this version stuff into a separate # module. There's enough special stuff around it that it may justify @@ -107,6 +110,10 @@ class Gem::Specification today = Time.now.utc TODAY = Time.utc(today.year, today.month, today.day) # :startdoc: ## @@ -156,6 +163,17 @@ class Gem::Specification :version => nil, } @@attributes = @@default_value.keys.sort_by { |s| s.to_s } @@array_attributes = @@default_value.reject { |k,v| v != [] }.keys @@nil_attributes, @@non_nil_attributes = @@default_value.keys.partition { |k| @@ -584,11 +602,6 @@ class Gem::Specification attr_writer :default_executable ## - # Path this gemspec was loaded from. This attribute is not persisted. - - attr_reader :loaded_from - - ## # Allows deinstallation of gems with legacy platforms. attr_writer :original_platform # :nodoc: @@ -615,58 +628,68 @@ class Gem::Specification attr_accessor :specification_version - class << self - def default_specifications_dir - File.join(Gem.default_dir, "specifications", "default") end - def each_spec(search_dirs) # :nodoc: - search_dirs.each { |dir| - Dir[File.join(dir, "*.gemspec")].each { |path| - spec = Gem::Specification.load path.untaint - # #load returns nil if the spec is bad, so we just ignore - # it at this stage - yield(spec) if spec - } - } end - def each_default(&block) # :nodoc: - each_spec([default_specifications_dir], - &block) end - def each_normal(&block) # :nodoc: - each_spec(dirs, &block) end end - def self._all # :nodoc: - unless defined?(@@all) && @@all then - specs = {} - each_default do |spec| - specs[spec.full_name] ||= spec end - each_normal do |spec| - specs[spec.full_name] ||= spec - end - - @@all = specs.values - - # After a reset, make sure already loaded specs - # are still marked as activated. - specs = {} - Gem.loaded_specs.each_value{|s| specs[s] = true} - @@all.each{|s| s.activated = true if specs[s]} - _resort! end - @@all end - def self._resort! # :nodoc: - @@all.sort! { |a, b| names = a.name <=> b.name next names if names.nonzero? b.version <=> a.version @@ -677,7 +700,9 @@ class Gem::Specification # Loads the default specifications. It should be called only once. def self.load_defaults - each_default do |spec| Gem.register_default_spec(spec) end end @@ -700,7 +725,9 @@ class Gem::Specification return if _all.include? spec _all << spec - _resort! end ## @@ -843,9 +870,10 @@ class Gem::Specification # amongst the specs that are not activated. def self.find_inactive_by_path path - self.find { |spec| - spec.contains_requirable_file? path unless spec.activated? } end ## @@ -937,6 +965,9 @@ class Gem::Specification file = file.dup.untaint return unless File.file?(file) code = if defined? Encoding File.read file, :mode => 'r:UTF-8:-' else @@ -950,6 +981,7 @@ class Gem::Specification if Gem::Specification === spec spec.loaded_from = file.to_s return spec end @@ -1013,6 +1045,7 @@ class Gem::Specification raise "wtf: #{spec.full_name} not in #{all_names.inspect}" unless _all.include? spec _all.delete spec end ## @@ -1037,6 +1070,8 @@ class Gem::Specification @@dirs = nil Gem.pre_reset_hooks.each { |hook| hook.call } @@all = nil unresolved = unresolved_deps unless unresolved.empty? then w = "W" + "ARN" @@ -1281,20 +1316,6 @@ class Gem::Specification end ## - # Returns the full path to the base gem directory. - # - # eg: /usr/local/lib/ruby/gems/1.8 - - def base_dir - return Gem.dir unless loaded_from - @base_dir ||= if default_gem? then - File.dirname File.dirname File.dirname loaded_from - else - File.dirname File.dirname loaded_from - end - end - - ## # Returns the full path to installed gem's bin directory. # # NOTE: do not confuse this with +bindir+, which is just 'bin', not @@ -1368,19 +1389,6 @@ class Gem::Specification end ## - # Return true if this spec can require +file+. - - def contains_requirable_file? file - root = full_gem_path - suffixes = Gem.suffixes - - require_paths.any? do |lib| - base = "#{root}/#{lib}/#{file}" - suffixes.any? { |suf| File.file? "#{base}#{suf}" } - end - end - - ## # The date this gem was created. Lazily defaults to TODAY. def date @@ -1623,35 +1631,14 @@ class Gem::Specification spec end - ## - # The full path to the gem (install path + full name). - - def full_gem_path - # TODO: This is a heavily used method by gems, so we'll need - # to aleast just alias it to #gem_dir rather than remove it. - - # TODO: also, shouldn't it default to full_name if it hasn't been written? - return @full_gem_path if defined?(@full_gem_path) && @full_gem_path - - @full_gem_path = File.expand_path File.join(gems_dir, full_name) - @full_gem_path.untaint - - return @full_gem_path if File.directory? @full_gem_path - - @full_gem_path = File.expand_path File.join(gems_dir, original_name) end - - ## - # Returns the full name (name-version) of this Gem. Platform information - # is included (name-version-platform) if it is specified and not the - # default Ruby platform. def full_name - @full_name ||= if platform == Gem::Platform::RUBY or platform.nil? then - "#{@name}-#{@version}".untaint - else - "#{@name}-#{@version}-#{platform}".untaint - end end ## @@ -1663,15 +1650,6 @@ class Gem::Specification end ## - # Returns the full path to the gems directory containing this spec's - # gem directory. eg: /usr/local/lib/ruby/1.8/gems - - def gems_dir - # TODO: this logic seems terribly broken, but tests fail if just base_dir - @gems_dir ||= File.join(loaded_from && base_dir || Gem.dir, "gems") - end - - ## # Deprecated and ignored, defaults to true. # # Formerly used to indicate this gem was RDoc-capable. @@ -1703,9 +1681,7 @@ class Gem::Specification # :startdoc: def hash # :nodoc: - @@attributes.inject(0) { |hash_code, (name, _)| - hash_code ^ self.send(name).hash - } end def init_with coder # :nodoc: @@ -1720,7 +1696,7 @@ class Gem::Specification def initialize name = nil, version = nil @loaded = false @activated = false - @loaded_from = nil @original_platform = nil @@nil_attributes.each do |key| @@ -1729,11 +1705,7 @@ class Gem::Specification @@non_nil_attributes.each do |key| default = default_value(key) - value = case default - when Time, Numeric, Symbol, true, false, nil then default - else default.dup - end - instance_variable_set "@#{key}", value end @@ -1828,28 +1800,31 @@ class Gem::Specification @licenses ||= [] end - ## - # Set the location a Specification was loaded from. +obj+ is converted - # to a String. - def loaded_from= path - @loaded_from = path.to_s - - # reset everything @loaded_from depends upon - @base_dir = nil @bin_dir = nil @cache_dir = nil @cache_file = nil @doc_dir = nil - @full_gem_path = nil @gem_dir = nil - @gems_dir = nil @ri_dir = nil @spec_dir = nil @spec_file = nil end ## # Sets the rubygems_version to the current RubyGems version. def mark_version @@ -1878,6 +1853,11 @@ class Gem::Specification end end ## # Normalize the list of files so that: # * All file lists have redundancies removed. @@ -2094,6 +2074,13 @@ class Gem::Specification end ## # Returns the full path to the directory containing this spec's # gemspec file. eg: /usr/local/lib/ruby/gems/1.8/specifications @@ -2172,6 +2159,7 @@ class Gem::Specification mark_version result = [] result << "# -*- encoding: utf-8 -*-" result << nil result << "Gem::Specification.new do |s|" @@ -2259,6 +2247,13 @@ class Gem::Specification "#<Gem::Specification name=#{@name} version=#{@version}>" end def to_yaml(opts = {}) # :nodoc: if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? then # Because the user can switch the YAML engine behind our @@ -2559,11 +2554,6 @@ class Gem::Specification end end - def default_gem? - loaded_from && - File.dirname(loaded_from) == self.class.default_specifications_dir - end - extend Gem::Deprecate # TODO: @@ -0,0 +1,112 @@ @@ -78,6 +78,23 @@ end class Gem::TestCase < MiniTest::Unit::TestCase # TODO: move to minitest def assert_path_exists path, msg = nil msg = message(msg) { "Expected path '#{path}' to exist" } @@ -200,6 +217,7 @@ class Gem::TestCase < MiniTest::Unit::TestCase @gemhome = File.join @tempdir, 'gemhome' @userhome = File.join @tempdir, 'userhome' @orig_ruby = if ENV['RUBY'] then ruby = Gem.instance_variable_get :@ruby @@ -221,6 +239,9 @@ class Gem::TestCase < MiniTest::Unit::TestCase FileUtils.mkdir_p @gemhome FileUtils.mkdir_p @userhome @default_dir = File.join @tempdir, 'default' @default_spec_dir = File.join @default_dir, "specifications", "default" Gem.instance_variable_set :@default_dir, @default_dir @@ -266,39 +287,6 @@ class Gem::TestCase < MiniTest::Unit::TestCase end @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" - - # TODO: move to installer test cases - Gem.post_build_hooks.clear - Gem.post_install_hooks.clear - Gem.done_installing_hooks.clear - Gem.post_reset_hooks.clear - Gem.post_uninstall_hooks.clear - Gem.pre_install_hooks.clear - Gem.pre_reset_hooks.clear - Gem.pre_uninstall_hooks.clear - - # TODO: move to installer test cases - Gem.post_build do |installer| - @post_build_hook_arg = installer - true - end - - Gem.post_install do |installer| - @post_install_hook_arg = installer - end - - Gem.post_uninstall do |uninstaller| - @post_uninstall_hook_arg = uninstaller - end - - Gem.pre_install do |installer| - @pre_install_hook_arg = installer - true - end - - Gem.pre_uninstall do |uninstaller| - @pre_uninstall_hook_arg = uninstaller - end end ## @@ -332,6 +320,47 @@ class Gem::TestCase < MiniTest::Unit::TestCase end Gem.instance_variable_set :@default_dir, nil end ## @@ -560,6 +589,21 @@ class Gem::TestCase < MiniTest::Unit::TestCase end end ## # Create a new spec (or gem if passed an array of files) and set it # up properly. Use this instead of util_spec and util_gem. @@ -1006,6 +1050,24 @@ Also, a list: end ## # Constructs a new Gem::Requirement. def req *requirements @@ -1074,18 +1136,18 @@ Also, a list: end ## - # Loads an RSA private key named +key_name+ in <tt>test/rubygems/</tt> - def self.load_key key_name key_file = key_path key_name key = File.read key_file - OpenSSL::PKey::RSA.new key end ## - # Returns the path tot he key named +key_name+ from <tt>test/rubygems</tt> def self.key_path key_name File.expand_path "../../../test/rubygems/#{key_name}_key.pem", __FILE__ @@ -1094,17 +1156,24 @@ Also, a list: # :stopdoc: # only available in RubyGems tests begin - PRIVATE_KEY = load_key 'private' - PRIVATE_KEY_PATH = key_path 'private' - PUBLIC_KEY = PRIVATE_KEY.public_key - PUBLIC_CERT = load_cert 'public' - PUBLIC_CERT_PATH = cert_path 'public' rescue Errno::ENOENT PRIVATE_KEY = nil PUBLIC_KEY = nil PUBLIC_CERT = nil - end end @@ -43,14 +43,15 @@ class Gem::Uninstaller def initialize(gem, options = {}) # TODO document the valid options - @gem = gem - @version = options[:version] || Gem::Requirement.default - @gem_home = File.expand_path(options[:install_dir] || Gem.dir) - @force_executables = options[:executables] - @force_all = options[:all] - @force_ignore = options[:ignore] - @bin_dir = options[:bin_dir] - @format_executable = options[:format_executable] # Indicate if development dependencies should be checked when # uninstalling. (default: false) @@ -143,7 +144,7 @@ class Gem::Uninstaller @spec = spec unless dependencies_ok? spec - unless ask_if_ok(spec) raise Gem::DependencyRemovalException, "Uninstallation aborted due to dependent gem(s)" end @@ -290,6 +291,10 @@ class Gem::Uninstaller deplist.ok_to_remove?(spec.full_name, @check_dev) end def ask_if_ok(spec) msg = [''] msg << 'You have requested to uninstall the gem:' @@ -0,0 +1,39 @@ @@ -0,0 +1,44 @@ @@ -147,13 +147,16 @@ class Gem::Version # FIX: These are only used once, in .correct?. Do they deserve to be # constants? - VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc: ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc: ## # A string representation of this Version. - attr_reader :version alias to_s version ## @@ -183,6 +186,12 @@ class Gem::Version end end ## # Constructs a Version from the +version+ string. A version string is a # series of digits or ASCII letters separated by dots. @@ -191,7 +200,8 @@ class Gem::Version raise ArgumentError, "Malformed version number string #{version}" unless self.class.correct?(version) - @version = version.to_s.dup.strip end ## @@ -42,6 +42,7 @@ module Gem::VersionOption add_option("--[no-]prerelease", "Allow prerelease versions of a gem", *wrap) do |value, options| options[:prerelease] = value end end @@ -50,14 +51,19 @@ module Gem::VersionOption def add_version_option(task = command, *wrap) OptionParser.accept Gem::Requirement do |value| - Gem::Requirement.new value end add_option('-v', '--version VERSION', Gem::Requirement, "Specify version of gem to #{task}", *wrap) do |value, options| options[:version] = value - options[:prerelease] = true if value.prerelease? end end |