module Bundler
Some versions of the Bundler 1.1 RC series introduced corrupted lockfiles. There were two major problems:
-
multiple copies of the same GIT section appeared in the lockfile
-
when this happened, those sections got multiple copies of gems in those sections.
As a result, Bundler 1.1 contains code that fixes the earlier corruption. We will remove this fix-up code in Bundler 1.2.
This is the latest iteration of the gem dependency resolving algorithm. As of now, it can resolve (as a success or failure) any set of gem dependencies we throw at it in a reasonable amount of time. The most iterations I've seen it take is about 150. The actual implementation of the algorithm is not as good as it could be yet, but that can come later.
Constants
- Deprecate
- FREEBSD
- NULL
- ORIGINAL_ENV
- VERSION
We're doing this because we might write tests that deal with other versions of bundler and we are unsure how to handle this better.
- WINDOWS
- YamlSyntaxError
Attributes
Public Class Methods
Returns current version of Ruby
@return [CurrentRuby] Current version of Ruby
# File lib/bundler/current_ruby.rb, line 5 def self.current_ruby @current_ruby ||= CurrentRuby.new end
# File lib/bundler/friendly_errors.rb, line 82 def self.issues_url(exception) 'https://github.com/bundler/bundler/search?q=' "#{CGI.escape(exception.message.lines.first.chomp)}&type=Issues" end
# File lib/bundler/gem_path_manipulation.rb, line 2 def self.preserve_gem_path original_gem_path = ENV["_ORIGINAL_GEM_PATH"] gem_path = ENV["GEM_PATH"] ENV["_ORIGINAL_GEM_PATH"] = gem_path if original_gem_path.nil? || original_gem_path == "" ENV["GEM_PATH"] = original_gem_path if gem_path.nil? || gem_path == "" end
# File lib/bundler/friendly_errors.rb, line 44 def self.request_issue_report_for(e) Bundler.ui.info " #{'--- ERROR REPORT TEMPLATE -------------------------------------------------------'} - What did you do? I ran the command `#{$PROGRAM_NAME} #{ARGV.join(' ')}` - What did you expect to happen? I expected Bundler to... - What happened instead? Instead, what actually happened was... Error details #{e.class}: #{e.message} #{e.backtrace.join("\n ")} #{Bundler::Env.new.report(:print_gemfile => false).gsub(/\n/, "\n ").strip} #{'--- TEMPLATE END ----------------------------------------------------------------'} ".gsub(/^ {6}/, '') Bundler.ui.error "Unfortunately, an unexpected error occurred, and Bundler cannot continue." Bundler.ui.warn " First, try this link to see if there are any existing issue reports for this error: #{issues_url(e)} If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at: https://github.com/bundler/bundler/issues/new ".gsub(/^ {6}/, '') end
# File lib/bundler/inline.rb, line 33 def Bundler.root Pathname.pwd.expand_path end
# File lib/bundler/friendly_errors.rb, line 5 def self.with_friendly_errors yield rescue Bundler::Dsl::DSLError => e Bundler.ui.error e.message exit e.status_code rescue Bundler::BundlerError => e Bundler.ui.error e.message, :wrap => true Bundler.ui.trace e exit e.status_code rescue Thor::AmbiguousTaskError => e Bundler.ui.error e.message exit 15 rescue Thor::UndefinedTaskError => e Bundler.ui.error e.message exit 15 rescue Thor::Error => e Bundler.ui.error e.message exit 1 rescue LoadError => e raise e unless e.message =~ /cannot load such file -- openssl|openssl.so|libcrypto.so/ Bundler.ui.error "\nCould not load OpenSSL." Bundler.ui.warn " You must recompile Ruby with OpenSSL support or change the sources in your \ Gemfile from 'https' to 'http'. Instructions for compiling with OpenSSL \ using RVM are available at http://rvm.io/packages/openssl. ", :wrap => true Bundler.ui.trace e exit 1 rescue Interrupt => e Bundler.ui.error "\nQuitting..." Bundler.ui.trace e exit 1 rescue SystemExit => e exit e.status rescue Exception => e request_issue_report_for(e) exit 1 end
Public Instance Methods
# File lib/bundler.rb, line 209 def app_cache(custom_path = nil) path = custom_path || root path.join(self.settings.app_cache_path) end
# File lib/bundler.rb, line 203 def app_config_path ENV['BUNDLE_APP_CONFIG'] ? Pathname.new(ENV['BUNDLE_APP_CONFIG']).expand_path(root) : root.join('.bundle') end
Returns absolute location of where binstubs are installed to.
# File lib/bundler.rb, line 110 def bin_path @bin_path ||= begin path = settings[:bin] || "bin" path = Pathname.new(path).expand_path(root).expand_path FileUtils.mkdir_p(path) path end end
Returns absolute path of where gems are installed on the filesystem.
# File lib/bundler.rb, line 105 def bundle_path @bundle_path ||= Pathname.new(settings.path).expand_path(root) end
# File lib/bundler.rb, line 189 def cache bundle_path.join("cache/bundler") end
# File lib/bundler.rb, line 253 def clean_exec(*args) with_clean_env { Kernel.exec(*args) } end
# File lib/bundler.rb, line 249 def clean_system(*args) with_clean_env { Kernel.system(*args) } end
# File lib/bundler.rb, line 379 def clear_gemspec_cache @gemspec_cache = {} end
# File lib/bundler.rb, line 91 def configure @configured ||= configure_gem_home_and_path end
# File lib/bundler.rb, line 265 def default_bundle_dir SharedHelpers.default_bundle_dir end
# File lib/bundler.rb, line 257 def default_gemfile SharedHelpers.default_gemfile end
# File lib/bundler.rb, line 261 def default_lockfile SharedHelpers.default_lockfile end
Returns an instance of Bundler::Definition for given Gemfile and lockfile
@param unlock [Hash, Boolean, nil] Gems that have been requested
to be updated or true if all gems should be updated
@return [Bundler::Definition]
# File lib/bundler.rb, line 150 def definition(unlock = nil) @definition = nil if unlock @definition ||= begin configure upgrade_lockfile Definition.build(default_gemfile, default_lockfile, unlock) end end
# File lib/bundler.rb, line 141 def environment Bundler::Environment.new(root, definition) end
# File lib/bundler.rb, line 383 def git_present? return @git_present if defined?(@git_present) @git_present = Bundler.which("git") || Bundler.which("git.exe") end
# File lib/bundler.rb, line 177 def home bundle_path.join("bundler") end
# File lib/bundler.rb, line 181 def install_path home.join("gems") end
# File lib/bundler.rb, line 137 def load @load ||= Runtime.new(root, definition) end
# File lib/bundler.rb, line 350 def load_gemspec(file, validate = false) @gemspec_cache ||= {} key = File.expand_path(file) @gemspec_cache[key] ||= load_gemspec_uncached(file, validate) # Protect against caching side-effected gemspecs by returning a # new instance each time. @gemspec_cache[key].dup if @gemspec_cache[key] end
# File lib/bundler.rb, line 359 def load_gemspec_uncached(file, validate = false) path = Pathname.new(file) # Eval the gemspec from its parent directory, because some gemspecs # depend on "./" relative paths. SharedHelpers.chdir(path.dirname.to_s) do contents = path.read if contents[0..2] == "---" # YAML header spec = eval_yaml_gemspec(path, contents) else spec = eval_gemspec(path, contents) end Bundler.rubygems.validate(spec) if spec && validate spec end rescue Gem::InvalidSpecificationException => e Bundler.ui.warn "The gemspec at #{file} is not valid. " "The validation error was '#{e.message}'" nil end
# File lib/bundler.rb, line 344 def load_marshal(data) Marshal.load(data) rescue => e raise MarshalError, "#{e.class}: #{e.message}" end
# File lib/bundler.rb, line 159 def locked_gems return @locked_gems if defined?(@locked_gems) if Bundler.default_lockfile.exist? lock = Bundler.read_file(Bundler.default_lockfile) @locked_gems = LockfileParser.new(lock) else @locked_gems = nil end end
# File lib/bundler.rb, line 304 def mkdir_p(path) if requires_sudo? sudo "mkdir -p '#{path}'" unless File.exist?(path) else FileUtils.mkdir_p(path) end end
# File lib/bundler.rb, line 340 def read_file(file) File.open(file, "rb") { |f| f.read } end
# File lib/bundler.rb, line 133 def require(*groups) setup(*groups).require(*groups) end
# File lib/bundler.rb, line 278 def requires_sudo? return @requires_sudo if defined?(@requires_sudo_ran) if settings.allow_sudo? sudo_present = which "sudo" end if sudo_present # the bundle path and subdirectories need to be writable for Rubygems # to be able to unpack and install gems without exploding path = bundle_path path = path.parent until path.exist? # bins are written to a different location on OS X bin_dir = Pathname.new(Bundler.system_bindir) bin_dir = bin_dir.parent until bin_dir.exist? # if any directory is not writable, we need sudo files = [path, bin_dir] | Dir[path.join('build_info/*').to_s] | Dir[path.join('*').to_s] sudo_needed = files.any?{|f| !File.writable?(f) } end @requires_sudo_ran = true @requires_sudo = settings.allow_sudo? && sudo_present && sudo_needed end
# File lib/bundler.rb, line 392 def reset! @definition = nil end
# File lib/bundler.rb, line 218 def rm_rf(path) FileUtils.remove_entry_secure(path) if path && File.exist?(path) end
# File lib/bundler.rb, line 193 def root @root ||= begin default_gemfile.dirname.expand_path rescue GemfileNotFound bundle_dir = default_bundle_dir raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir Pathname.new(File.expand_path("..", bundle_dir)) end end
# File lib/bundler.rb, line 169 def ruby_scope "#{Bundler.rubygems.ruby_engine}/#{Bundler.rubygems.config_map[:ruby_version]}" end
# File lib/bundler.rb, line 388 def ruby_version @ruby_version ||= SystemRubyVersion.new end
# File lib/bundler.rb, line 222 def settings return @settings if defined?(@settings) @settings = Settings.new(app_config_path) rescue GemfileNotFound @settings = Settings.new(Pathname.new(".bundle").expand_path) end
# File lib/bundler.rb, line 119 def setup(*groups) # Just return if all groups are already loaded return @setup if defined?(@setup) definition.validate_ruby! if groups.empty? # Load all groups, but only once @setup = load.setup else load.setup(*groups) end end
# File lib/bundler.rb, line 185 def specs_path bundle_path.join("specifications") end
# File lib/bundler.rb, line 324 def sudo(str) prompt = "\n\n" + " Your user account isn't allowed to install to the system Rubygems. You can cancel this installation and run: bundle install --path vendor/bundle to install the gems into ./vendor/bundle/, or you can enter your password and install the bundled gems to Rubygems using sudo. Password: ".gsub(/^ {6}/, '').strip + " " %xsudo -p "#{prompt}" #{str}` end
# File lib/bundler.rb, line 269 def system_bindir # Gem.bindir doesn't always return the location that Rubygems will install # system binaries. If you put '-n foo' in your .gemrc, Rubygems will # install binstubs there instead. Unfortunately, Rubygems doesn't expose # that directory at all, so rather than parse .gemrc ourselves, we allow # the directory to be set as well, via `bundle config bindir foo`. Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir end
# File lib/bundler.rb, line 214 def tmp(name = Process.pid.to_s) Pathname.new(Dir.mktmpdir(["bundler", name])) end
# File lib/bundler.rb, line 95 def ui (defined?(@ui) && @ui) || (self.ui = UI::Silent.new) end
# File lib/bundler.rb, line 99 def ui=(ui) Bundler.rubygems.ui = ui ? UI::RGProxy.new(ui) : nil @ui = ui end
# File lib/bundler.rb, line 173 def user_bundle_path Pathname.new(Bundler.rubygems.user_home).join(".bundler") end
# File lib/bundler.rb, line 312 def which(executable) if File.file?(executable) && File.executable?(executable) executable elsif ENV['PATH'] path = ENV['PATH'].split(File::PATH_SEPARATOR).find do |p| abs_path = File.join(p, executable) File.file?(abs_path) && File.executable?(abs_path) end path && File.expand_path(executable, path) end end
# File lib/bundler.rb, line 237 def with_clean_env with_original_env do ENV['MANPATH'] = ENV['BUNDLE_ORIG_MANPATH'] ENV.delete_if { |k,_| k[0,7] == 'BUNDLE_' } if ENV.has_key? 'RUBYOPT' ENV['RUBYOPT'] = ENV['RUBYOPT'].sub '-rbundler/setup', '' ENV['RUBYOPT'] = ENV['RUBYOPT'].sub "-I#{File.expand_path('..', __FILE__)}", '' end yield end end
# File lib/bundler.rb, line 229 def with_original_env bundled_env = ENV.to_hash ENV.replace(ORIGINAL_ENV) yield ensure ENV.replace(bundled_env.to_hash) end
Private Instance Methods
# File lib/bundler.rb, line 435 def configure_gem_home # TODO: This mkdir_p is only needed for JRuby <= 1.5 and should go away (GH #602) FileUtils.mkdir_p bundle_path.to_s rescue nil ENV['GEM_HOME'] = File.expand_path(bundle_path, root) Bundler.rubygems.clear_paths end
# File lib/bundler.rb, line 421 def configure_gem_home_and_path blank_home = ENV['GEM_HOME'].nil? || ENV['GEM_HOME'].empty? if settings[:disable_shared_gems] ENV['GEM_PATH'] = '' elsif blank_home || Bundler.rubygems.gem_dir != bundle_path.to_s possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path] paths = possibles.flatten.compact.uniq.reject { |p| p.empty? } ENV["GEM_PATH"] = paths.join(File::PATH_SEPARATOR) end configure_gem_home bundle_path end
# File lib/bundler.rb, line 406 def eval_gemspec(path, contents) eval(contents, TOPLEVEL_BINDING, path.expand_path.to_s) rescue ScriptError, StandardError => e original_line = e.backtrace.find { |line| line.include?(path.to_s) } msg = "There was a #{e.class} while loading #{path.basename}: \n#{e.message}" msg << " from\n #{original_line}" if original_line msg << "\n" if e.is_a?(LoadError) && RUBY_VERSION >= "1.9" msg << "\nDoes it try to require a relative path? That's been removed in Ruby 1.9." end raise GemspecError, msg end
# File lib/bundler.rb, line 398 def eval_yaml_gemspec(path, contents) # If the YAML is invalid, Syck raises an ArgumentError, and Psych # raises a Psych::SyntaxError. See psyched_yaml.rb for more info. Gem::Specification.from_yaml(contents) rescue YamlSyntaxError, ArgumentError, Gem::EndOfYAMLException, Gem::Exception eval_gemspec(path, contents) end
# File lib/bundler.rb, line 443 def upgrade_lockfile lockfile = default_lockfile if lockfile.exist? && lockfile.read(3) == "---" Bundler.ui.warn "Detected Gemfile.lock generated by 0.9, deleting..." lockfile.rmtree end end