lib/bundler/cli.rb in bundler-0.9.26 vs lib/bundler/cli.rb in bundler-1.0.0.beta.1

- old
+ new

@@ -5,13 +5,26 @@ # Work around a RubyGems bug Gem.configuration module Bundler class CLI < Thor + def initialize(*) + super + Bundler.ui = UI::Shell.new(shell) + Gem::DefaultUserInteraction.ui = UI::RGProxy.new(Bundler.ui) + end + check_unknown_options! unless ARGV.include?("exec") + default_task :install + desc "init", "Generates a Gemfile into the current working directory" + long_desc <<-D + Init generates a default Gemfile in the current working directory. When adding a + Gemfile to a gem with a gemspec, the --gemspec option will automatically add each + dependency listed in the gemspec file to the newly created Gemfile. + D method_option "gemspec", :type => :string, :banner => "Use the specified .gemspec to create the Gemfile" def init opts = options.dup if File.exist?("Gemfile") Bundler.ui.error "Gemfile already exists at #{Dir.pwd}/Gemfile" @@ -34,138 +47,151 @@ puts "Writing new Gemfile to #{Dir.pwd}/Gemfile" FileUtils.cp(File.expand_path('../templates/Gemfile', __FILE__), 'Gemfile') end end - def initialize(*) - super - Bundler.ui = UI::Shell.new(shell) - Gem::DefaultUserInteraction.ui = UI::RGProxy.new(Bundler.ui) - end - desc "check", "Checks if the dependencies listed in Gemfile are satisfied by currently installed gems" + long_desc <<-D + Check searches the local machine for each of the gems requested in the Gemfile. If + all gems are found, Bundler prints a success message and exits with a status of 0. + If not, the first missing gem is listed and Bundler exits status 1. + D def check - env = Bundler.runtime - # Check top level dependencies - missing = env.dependencies.select { |d| env.index.search(d).empty? } - if missing.any? - Bundler.ui.error "The following dependencies are missing" - missing.each do |d| - Bundler.ui.error " * #{d}" - end + not_installed = Bundler.definition.missing_specs + + if not_installed.any? + Bundler.ui.error "The following gems are missing" + not_installed.each { |s| Bundler.ui.error " * #{s.name} (#{s.version})" } Bundler.ui.warn "Install missing gems with `bundle install`" exit 1 else - not_installed = env.requested_specs.select { |spec| !spec.loaded_from } - - if not_installed.any? - not_installed.each { |s| Bundler.ui.error "#{s.name} (#{s.version}) is cached, but not installed" } - Bundler.ui.warn "Install missing gems with `bundle install`" - exit 1 - else - Bundler.ui.info "The Gemfile's dependencies are satisfied" - end + Bundler.ui.info "The Gemfile's dependencies are satisfied" end end desc "install", "Install the current environment to the system" - method_option "without", :type => :array, :banner => "Exclude gems that are part of the specified named group." - method_option "relock", :type => :boolean, :banner => "Unlock, install the gems, and relock." - method_option "disable-shared-gems", :type => :boolean, :banner => "Do not use any shared gems, such as the system gem repository." - method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" - method_option "no-cache", :type => :boolean, :banner => "Don't update the existing gem cache." - method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." + long_desc <<-D + Install will install all of the gems in the current bundle, making them available + for use. In a freshly checked out repository, this command will give you the same + gem versions as the last person who updated the Gemfile and ran `bundle update`. + + Passing [DIR] to install (e.g. vendor) will cause the unpacked gems to be installed + into the [DIR] directory rather than into system gems. + + If the bundle has already been installed, bundler will tell you so and then exit. + D + method_option "without", :type => :array, :banner => + "Exclude gems that are part of the specified named group." + method_option "disable-shared-gems", :type => :boolean, :banner => + "Do not use any shared gems, such as the system gem repository." + method_option "gemfile", :type => :string, :banner => + "Use the specified gemfile instead of Gemfile" + method_option "no-prune", :type => :boolean, :banner => + "Don't remove stale gems from the cache." + method_option "no-cache", :type => :boolean, :banner => + "Don't update the existing gem cache." def install(path = nil) opts = options.dup opts[:without] ||= [] opts[:without].map! { |g| g.to_sym } # Can't use Bundler.settings for this because settings needs gemfile.dirname ENV['BUNDLE_GEMFILE'] = opts[:gemfile] if opts[:gemfile] Bundler.settings[:path] = path if path - Bundler.settings[:disable_shared_gems] = '1' if options["disable-shared-gems"] + Bundler.settings[:disable_shared_gems] = '1' if options["disable-shared-gems"] || path Bundler.settings.without = opts[:without] - remove_lockfiles if options[:relock] - - begin - Installer.install(Bundler.root, Bundler.definition, opts) - rescue GemfileChanged - raise GemfileChanged, "You changed your Gemfile after locking. " + - "Please run `bundle install --relock`." - end - - lock if options[:relock] - cache if Bundler.root.join("vendor/cache").exist? && !options[:no_cache] + Installer.install(Bundler.root, Bundler.definition, opts) + cache if Bundler.root.join("vendor/cache").exist? Bundler.ui.confirm "Your bundle is complete! " + "Use `bundle show [gemname]` to see where a bundled gem is installed." rescue GemNotFound => e - if Bundler.definition.sources.empty? + if Bundler.definition.no_sources? Bundler.ui.warn "Your Gemfile doesn't have any sources. You can add one with a line like 'source :gemcutter'" end raise e end - desc "lock", "Locks the bundle to the current set of dependencies, including all child dependencies." - def lock - if locked? - Bundler.ui.info("Your bundle is already locked, relocking.") - remove_lockfiles + desc "update", "update the current environment" + long_desc <<-D + Update will install the newest versions of the gems listed in the Gemfile. Use + update when you have changed the Gemfile, or if you want to get the newest + possible versions of the gems in the bundle. + D + method_option "source", :type => :array, :banner => "Update a specific source (and all gems associated with it)" + def update(*gems) + sources = Array(options[:source]) + + if gems.empty? && sources.empty? + # We're doing a full update + FileUtils.rm Bundler.root.join("Gemfile.lock") + else + Bundler.definition(:gems => gems, :sources => sources) end - Bundler.runtime.lock - Bundler.ui.confirm("Your bundle is now locked. " + - "Use `bundle show [gemname]` to list the gems in the environment.") - rescue GemNotFound, VersionConflict => e - Bundler.ui.error(e.message) - Bundler.ui.warn "Run `bundle install` to install missing gems." - exit 128 + Installer.install Bundler.root, Bundler.definition end + desc "lock", "Locks the bundle to the current set of dependencies, including all child dependencies." + def lock + Bundler.ui.warn "Lock is deprecated. Your bundle is now locked whenever you run `bundle install`." + end + desc "unlock", "Unlock the bundle. This allows gem versions to be changed." def unlock - if locked? - remove_lockfiles - Bundler.ui.info("Your bundle is now unlocked. The dependencies may be changed.") - else - Bundler.ui.info("Your bundle is not currently locked.") - end + Bundler.ui.warn "Unlock is deprecated. To update to newer gem versions, use `bundle update`." end desc "show [GEM]", "Shows all gems that are part of the bundle, or the path to a given gem" + long_desc <<-D + Show lists the names and versions of all gems that are required by your Gemfile. + Calling show with [GEM] will list the exact location of that gem on your machine. + D def show(gem_name = nil) if gem_name Bundler.ui.info locate_gem(gem_name) else Bundler.ui.info "Gems included by the bundle:" - Bundler.runtime.specs.sort_by { |s| s.name }.each do |s| + Bundler.load.specs.sort_by { |s| s.name }.each do |s| Bundler.ui.info " * #{s.name} (#{s.version}#{s.git_version})" end end end map %w(list) => "show" desc "cache", "Cache all the gems to vendor/cache" method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." def cache - Bundler.runtime.cache - Bundler.runtime.prune_cache unless options[:no_prune] + Bundler.load.cache + Bundler.load.prune_cache unless options[:no_prune] rescue GemNotFound => e Bundler.ui.error(e.message) Bundler.ui.warn "Run `bundle install` to install missing gems." exit 128 end desc "package", "Locks and then caches all of the gems into vendor/cache" method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." + long_desc <<-D + The package command will copy the .gem files for every gem in the bundle into the + directory ./vendor/cache. If you then check that directory into your source + control repository, others who check out your source will be able to install the + bundle without having to download any additional gems. + D def package - lock + install + # TODO: move cache contents here now that all bundles are locked cache end map %w(pack) => :package desc "exec", "Run the command in context of the bundle" + long_desc <<-D + Exec runs a command, providing it access to the gems in the bundle. While using + bundle exec you can require and call the bundled gems as if they were installed + into the systemwide Rubygems repository. + D def exec(*) ARGV.delete("exec") # Set PATH paths = (ENV["PATH"] || "").split(File::PATH_SEPARATOR) @@ -183,11 +209,11 @@ ENV["RUBYOPT"] = rubyopt.join(' ') end begin # Run - Kernel.exec *ARGV + Kernel.exec(*ARGV) rescue Errno::EACCES Bundler.ui.error "bundler: not executable: #{ARGV.first}" rescue Errno::ENOENT Bundler.ui.error "bundler: command not found: #{ARGV.first}" Bundler.ui.warn "Install missing gem binaries with `bundle install`" @@ -220,22 +246,38 @@ def version Bundler.ui.info "Bundler version #{Bundler::VERSION}" end map %w(-v --version) => :version - private + desc 'viz', "Generates a visual dependency graph" + method_option :file, :type => :string, :default => 'gem_graph.png', :aliases => '-f', :banner => "Show the version with each gem name." + method_option :version, :type => :boolean, :default => false, :aliases => '-v', :banner => "Show the version with each gem name." + method_option :requirements, :type => :boolean, :default => false, :aliases => '-r', :banner => "Show the requirement for each dependency." + def viz + output_file = File.expand_path(options[:file]) + graph = Graph.new( Bundler.load ) - def locked? - File.exist?("#{Bundler.root}/Gemfile.lock") || File.exist?("#{Bundler.root}/.bundle/environment.rb") + begin + graph.viz(output_file, options[:version], options[:requirements]) + Bundler.ui.info output_file + rescue LoadError => e + Bundler.ui.error e.inspect + Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:" + Bundler.ui.warn "`gem install ruby-graphviz`" + rescue StandardError => e + if e.message =~ /GraphViz not installed or dot not in PATH/ + Bundler.ui.error e.message + Bundler.ui.warn "The ruby graphviz gem requires GraphViz to be installed" + else + raise + end + end end - def remove_lockfiles - FileUtils.rm_f "#{Bundler.root}/Gemfile.lock" - FileUtils.rm_f "#{Bundler.root}/.bundle/environment.rb" - end + private def locate_gem(name) - spec = Bundler.runtime.specs.find{|s| s.name == name } + spec = Bundler.load.specs.find{|s| s.name == name } raise GemNotFound, "Could not find gem '#{name}' in the current bundle." unless spec spec.full_gem_path end def self.printable_tasks