diff options
Diffstat (limited to 'lib/bundler/shared_helpers.rb')
| -rw-r--r-- | lib/bundler/shared_helpers.rb | 296 |
1 files changed, 194 insertions, 102 deletions
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index a9141a1346..2aa8abe0a0 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -1,37 +1,37 @@ # frozen_string_literal: true -require "pathname" -require "rubygems" - -require "bundler/constants" -require "bundler/rubygems_integration" -require "bundler/current_ruby" - -module Gem - class Dependency - # This is only needed for RubyGems < 1.4 - unless method_defined? :requirement - def requirement - version_requirements - end - end - end -end + +require_relative "version" +require_relative "rubygems_integration" +require_relative "current_ruby" module Bundler + autoload :WINDOWS, File.expand_path("constants", __dir__) + autoload :FREEBSD, File.expand_path("constants", __dir__) + autoload :NULL, File.expand_path("constants", __dir__) + module SharedHelpers + def root + gemfile = find_gemfile + raise GemfileNotFound, "Could not locate Gemfile" unless gemfile + Pathname.new(gemfile).expand_path.parent + end + def default_gemfile gemfile = find_gemfile raise GemfileNotFound, "Could not locate Gemfile" unless gemfile - Pathname.new(gemfile).untaint + Pathname.new(gemfile).expand_path end def default_lockfile + given = ENV["BUNDLE_LOCKFILE"] + return Pathname.new(given) if given && !given.empty? + gemfile = default_gemfile case gemfile.basename.to_s when "gems.rb" then Pathname.new(gemfile.sub(/.rb$/, ".locked")) else Pathname.new("#{gemfile}.lock") - end.untaint + end end def default_bundle_dir @@ -58,12 +58,12 @@ module Bundler def pwd Bundler.rubygems.ext_lock.synchronize do - Pathname.pwd + Dir.pwd end end def with_clean_git_env(&block) - keys = %w(GIT_DIR GIT_WORK_TREE) + keys = %w[GIT_DIR GIT_WORK_TREE] old_env = keys.inject({}) do |h, k| h.update(k => ENV[k]) end @@ -97,16 +97,17 @@ module Bundler # given block # # @example - # filesystem_access("vendor/cache", :write) do + # filesystem_access("vendor/cache", :create) do # FileUtils.mkdir_p("vendor/cache") # end # # @see {Bundler::PermissionError} def filesystem_access(path, action = :write, &block) - # Use block.call instead of yield because of a bug in Ruby 2.2.2 - # See https://github.com/bundler/bundler/issues/5341 for details - block.call(path.dup.untaint) - rescue Errno::EACCES + yield(path.dup) + rescue Errno::EACCES => e + path_basename = File.basename(path.to_s) + raise unless e.message.include?(path_basename) || action == :create + raise PermissionError.new(path, action) rescue Errno::EAGAIN raise TemporaryResourceError.new(path, action) @@ -114,42 +115,39 @@ module Bundler raise VirtualProtocolError.new rescue Errno::ENOSPC raise NoSpaceOnDeviceError.new(path, action) - rescue *[const_get_safely(:ENOTSUP, Errno)].compact + rescue Errno::ENOTSUP raise OperationNotSupportedError.new(path, action) + rescue Errno::EPERM + raise OperationNotPermittedError.new(path, action) + rescue Errno::EROFS + raise ReadOnlyFileSystemError.new(path, action) rescue Errno::EEXIST, Errno::ENOENT raise rescue SystemCallError => e - raise GenericSystemCallError.new(e, "There was an error accessing `#{path}`.") + raise GenericSystemCallError.new(e, "There was an error #{[:create, :write].include?(action) ? "creating" : "accessing"} `#{path}`.") end - def const_get_safely(constant_name, namespace) - const_in_namespace = namespace.constants.include?(constant_name.to_s) || - namespace.constants.include?(constant_name.to_sym) - return nil unless const_in_namespace - namespace.const_get(constant_name) - end - - def major_deprecation(message) + def feature_deprecated!(message) return unless prints_major_deprecations? - @major_deprecation_ui ||= Bundler::UI::Shell.new("no-color" => true) - ui = Bundler.ui.is_a?(@major_deprecation_ui.class) ? Bundler.ui : @major_deprecation_ui - ui.warn("[DEPRECATED FOR #{Bundler::VERSION.split(".").first.to_i + 1}.0] #{message}") + + Bundler.ui.warn("[DEPRECATED] #{message}") end - def print_major_deprecations! - deprecate_gemfile(find_gemfile) if find_gemfile == find_file("Gemfile") - if RUBY_VERSION < "2" - major_deprecation("Bundler will only support ruby >= 2.0, you are running #{RUBY_VERSION}") - end - return if Bundler.rubygems.provides?(">= 2") - major_deprecation("Bundler will only support rubygems >= 2.0, you are running #{Bundler.rubygems.version}") + def feature_removed!(message) + require_relative "errors" + raise RemovedError, "[REMOVED] #{message}" end - def trap(signal, override = false, &block) - prior = Signal.trap(signal) do - block.call - prior.call unless override + def print_major_deprecations! + multiple_gemfiles = search_up(".") do |dir| + gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) } + next if gemfiles.empty? + break gemfiles.size != 1 end + return unless multiple_gemfiles + message = "Multiple gemfiles (gems.rb and Gemfile) detected. " \ + "Make sure you remove Gemfile and Gemfile.lock since bundler is ignoring them in favor of gems.rb and gems.locked." + Bundler.ui.warn message end def ensure_same_dependencies(spec, old_deps, new_deps) @@ -163,22 +161,87 @@ module Bundler extra_deps = new_deps - old_deps return if extra_deps.empty? - Bundler.ui.debug "#{spec.full_name} from #{spec.remote} has either corrupted API or lockfile dependencies" \ + Bundler.ui.debug "#{spec.full_name} from #{spec.remote} has corrupted API dependencies" \ " (was expecting #{old_deps.map(&:to_s)}, but the real spec has #{new_deps.map(&:to_s)})" raise APIResponseMismatchError, - "Downloading #{spec.full_name} revealed dependencies not in the API or the lockfile (#{extra_deps.join(", ")})." \ - "\nEither installing with `--full-index` or running `bundle update #{spec.name}` should fix the problem." + "Downloading #{spec.full_name} revealed dependencies not in the API (#{extra_deps.join(", ")})." \ + "\nRunning `bundle update #{spec.name}` should fix the problem." + end + + def pretty_dependency(dep) + msg = String.new(dep.name) + msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default + + if dep.is_a?(Bundler::Dependency) + platform_string = dep.platforms.join(", ") + msg << " " << platform_string if !platform_string.empty? && platform_string != Gem::Platform::RUBY + end + + msg + end + + def md5_available? + return @md5_available if defined?(@md5_available) + @md5_available = begin + require "openssl" + ::OpenSSL::Digest.digest("MD5", "") + true + rescue LoadError + true + rescue ::OpenSSL::Digest::DigestError + false + end + end + + def digest(name) + require "digest" + Digest(name) + end + + def checksum_for_file(path, digest) + return unless path.file? + # This must use File.read instead of Digest.file().hexdigest + # because we need to preserve \n line endings on windows when calculating + # the checksum + SharedHelpers.filesystem_access(path, :read) do + File.open(path, "rb") do |f| + digest = SharedHelpers.digest(digest).new + buf = String.new(capacity: 16_384, encoding: Encoding::BINARY) + digest << buf while f.read(16_384, buf) + digest.hexdigest + end + end end - private + def write_to_gemfile(gemfile_path, contents) + filesystem_access(gemfile_path) {|g| File.open(g, "w") {|file| file.puts contents } } + end + + def relative_gemfile_path + relative_path_to(Bundler.default_gemfile) + end + + def relative_lockfile_path + relative_path_to(Bundler.default_lockfile) + end + + def relative_path_to(destination, from: pwd) + Pathname.new(destination).relative_path_from(from).to_s + rescue ArgumentError + # on Windows, if source and destination are on different drivers, there's no relative path from one to the other + destination + end + + private def validate_bundle_path - return unless Bundler.bundle_path.to_s.include?(File::PATH_SEPARATOR) - message = "Your bundle path contains a '#{File::PATH_SEPARATOR}', " \ + path_separator = Bundler.rubygems.path_separator + return unless Bundler.bundle_path.to_s.split(path_separator).size > 1 + message = "Your bundle path contains text matching #{path_separator.inspect}, " \ "which is the path separator for your system. Bundler cannot " \ "function correctly when the Bundle path contains the " \ "system's PATH separator. Please change your " \ - "bundle path to not include '#{File::PATH_SEPARATOR}'." \ + "bundle path to not match #{path_separator.inspect}." \ "\nYour current bundle path is '#{Bundler.bundle_path}'." raise Bundler::PathError, message end @@ -186,7 +249,11 @@ module Bundler def find_gemfile given = ENV["BUNDLE_GEMFILE"] return given if given && !given.empty? - find_file("Gemfile", "gems.rb") + find_file(*gemfile_names) + end + + def gemfile_names + ["gems.rb", "Gemfile"] end def find_file(*names) @@ -203,18 +270,12 @@ module Bundler def search_up(*names) previous = nil - current = File.expand_path(SharedHelpers.pwd).untaint + current = File.expand_path(SharedHelpers.pwd) until !File.directory?(current) || current == previous - if ENV["BUNDLE_SPEC_RUN"] + if ENV["BUNDLER_SPEC_RUN"] # avoid stepping above the tmp directory when testing - if !!(ENV["BUNDLE_RUBY"] && ENV["BUNDLE_GEM"]) - # for Ruby Core - gemspec = "lib/bundler.gemspec" - else - gemspec = "bundler.gemspec" - end - return nil if File.file?(File.join(current, gemspec)) + return nil if File.directory?(File.join(current, "tmp")) end names.each do |name| @@ -226,76 +287,107 @@ module Bundler end end + def set_env(key, value) + raise ArgumentError, "new key #{key}" unless EnvironmentPreserver::BUNDLER_KEYS.include?(key) + orig_key = "#{EnvironmentPreserver::BUNDLER_PREFIX}#{key}" + orig = ENV[key] + orig ||= EnvironmentPreserver::INTENTIONALLY_NIL + ENV[orig_key] ||= orig + + ENV[key] = value + end + public :set_env + def set_bundle_variables - begin - ENV["BUNDLE_BIN_PATH"] = Bundler.rubygems.bin_path("bundler", "bundle", VERSION) - rescue Gem::GemNotFoundException - if File.exist?(File.expand_path("../../../exe/bundle", __FILE__)) - ENV["BUNDLE_BIN_PATH"] = File.expand_path("../../../exe/bundle", __FILE__) - else - ENV["BUNDLE_BIN_PATH"] = File.expand_path("../../../../bin/bundle", __FILE__) - end - end + Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", bundle_bin_path + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", default_lockfile.to_s + Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION + Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__) + end + + def bundle_bin_path + # bundler exe & lib folders have same root folder, typical gem installation + exe_file = File.join(source_root, "exe/bundle") - # Set BUNDLE_GEMFILE - ENV["BUNDLE_GEMFILE"] = find_gemfile.to_s - ENV["BUNDLER_VERSION"] = Bundler::VERSION + # for Ruby core repository testing + exe_file = File.join(source_root, "libexec/bundle") unless File.exist?(exe_file) + + # bundler is a default gem, exe path is separate + exe_file = Gem.bin_path("bundler", "bundle", VERSION) unless File.exist?(exe_file) + + exe_file + end + public :bundle_bin_path + + def gemspec_path + # inside a gem repository, typical gem installation + gemspec_file = File.join(source_root, "../../specifications/bundler-#{VERSION}.gemspec") + + # for Ruby core repository testing + gemspec_file = File.expand_path("bundler.gemspec", __dir__) unless File.exist?(gemspec_file) + + # bundler is a default gem + gemspec_file = File.join(Gem.default_specifications_dir, "bundler-#{VERSION}.gemspec") unless File.exist?(gemspec_file) + + gemspec_file + end + public :gemspec_path + + def source_root + File.expand_path("../..", __dir__) end def set_path validate_bundle_path paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR) paths.unshift "#{Bundler.bundle_path}/bin" - ENV["PATH"] = paths.uniq.join(File::PATH_SEPARATOR) + Bundler::SharedHelpers.set_env "PATH", paths.uniq.join(File::PATH_SEPARATOR) end def set_rubyopt rubyopt = [ENV["RUBYOPT"]].compact - return if !rubyopt.empty? && rubyopt.first =~ %r{-rbundler/setup} - rubyopt.unshift %(-rbundler/setup) - ENV["RUBYOPT"] = rubyopt.join(" ") + setup_require = "-r#{File.expand_path("setup", __dir__)}" + return if !rubyopt.empty? && rubyopt.first.include?(setup_require) + rubyopt.unshift setup_require + Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ") end def set_rubylib rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR) - rubylib.unshift bundler_ruby_lib - ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR) + rubylib.unshift bundler_ruby_lib unless RbConfig::CONFIG["rubylibdir"] == bundler_ruby_lib + Bundler::SharedHelpers.set_env "RUBYLIB", rubylib.uniq.join(File::PATH_SEPARATOR) end def bundler_ruby_lib - File.expand_path("../..", __FILE__) + File.expand_path("..", __dir__) end def clean_load_path - # handle 1.9 where system gems are always on the load path - return unless defined?(::Gem) - - bundler_lib = bundler_ruby_lib - loaded_gem_paths = Bundler.rubygems.loaded_gem_paths $LOAD_PATH.reject! do |p| - next if File.expand_path(p).start_with?(bundler_lib) + resolved_path = resolve_path(p) + next if $LOADED_FEATURES.any? {|lf| lf.start_with?(resolved_path) } loaded_gem_paths.delete(p) end $LOAD_PATH.uniq! end + def resolve_path(path) + expanded = File.expand_path(path) + return expanded unless File.exist?(expanded) + + File.realpath(expanded) + end + def prints_major_deprecations? - require "bundler" - deprecation_release = Bundler::VERSION.split(".").drop(1).include?("99") - return false if !deprecation_release && !Bundler.settings[:major_deprecations] - require "bundler/deprecate" + return false if Bundler.settings[:silence_deprecations] + require_relative "deprecate" return false if Bundler::Deprecate.skip true end - def deprecate_gemfile(gemfile) - return unless gemfile && File.basename(gemfile) == "Gemfile" - Bundler::SharedHelpers.major_deprecation \ - "gems.rb and gems.locked will be preferred to Gemfile and Gemfile.lock." - end - extend self end end |
