├── .gitignore ├── lib ├── bundler │ ├── version.rb │ ├── vendor │ │ └── thor │ │ │ ├── version.rb │ │ │ ├── parser.rb │ │ │ ├── core_ext │ │ │ ├── file_binary_read.rb │ │ │ ├── hash_with_indifferent_access.rb │ │ │ └── ordered_hash.rb │ │ │ ├── error.rb │ │ │ ├── parser │ │ │ ├── argument.rb │ │ │ ├── option.rb │ │ │ ├── arguments.rb │ │ │ └── options.rb │ │ │ ├── shell.rb │ │ │ ├── task.rb │ │ │ ├── shell │ │ │ └── color.rb │ │ │ └── invocation.rb │ ├── templates │ │ ├── Gemfile │ │ └── Executable │ ├── setup.rb │ ├── environment.rb │ ├── settings.rb │ ├── ui.rb │ ├── installer.rb │ ├── lazy_specification.rb │ ├── remote_specification.rb │ ├── dependency.rb │ ├── runtime.rb │ ├── index.rb │ ├── lockfile_parser.rb │ ├── spec_set.rb │ ├── graph.rb │ ├── shared_helpers.rb │ ├── rubygems_ext.rb │ ├── dsl.rb │ └── definition.rb └── bundler.rb ├── spec ├── cache │ ├── git_spec.rb │ ├── path_spec.rb │ └── gems_spec.rb ├── other │ ├── help_spec.rb │ ├── ext_spec.rb │ ├── init_spec.rb │ ├── open_spec.rb │ ├── show_spec.rb │ ├── console_spec.rb │ ├── check_spec.rb │ └── exec_spec.rb ├── runtime │ ├── with_clean_env_spec.rb │ ├── platform_spec.rb │ ├── executable_spec.rb │ ├── load_spec.rb │ ├── environment_rb_spec.rb │ ├── require_spec.rb │ └── setup_spec.rb ├── support │ ├── ruby_ext.rb │ ├── sudo.rb │ ├── platforms.rb │ ├── rubygems_ext.rb │ ├── path.rb │ ├── matchers.rb │ ├── indexes.rb │ └── helpers.rb ├── install │ ├── invalid_spec.rb │ ├── deprecated_spec.rb │ ├── gems │ │ ├── locked_spec.rb │ │ ├── packed_spec.rb │ │ ├── resolving_spec.rb │ │ ├── platform_spec.rb │ │ ├── env_spec.rb │ │ ├── flex_spec.rb │ │ └── groups_spec.rb │ └── path_spec.rb ├── resolver │ ├── basic_spec.rb │ └── platform_spec.rb ├── pack │ └── gems_spec.rb ├── lock │ └── git_spec.rb ├── update │ ├── gems_spec.rb │ ├── source_spec.rb │ └── git_spec.rb ├── quality_spec.rb └── spec_helper.rb ├── bin └── bundle ├── TODO.md ├── bundler.gemspec ├── LICENSE ├── ROADMAP.md ├── Rakefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | tmp 3 | pkg 4 | *.gem 5 | *.rbc -------------------------------------------------------------------------------- /lib/bundler/version.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | VERSION = "1.0.0.beta.1" 3 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/version.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | VERSION = "0.13.6".freeze 3 | end 4 | -------------------------------------------------------------------------------- /lib/bundler/templates/Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source :gemcutter 3 | # 4 | # gem "rails" 5 | -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/parser.rb: -------------------------------------------------------------------------------- 1 | require 'thor/parser/argument' 2 | require 'thor/parser/arguments' 3 | require 'thor/parser/option' 4 | require 'thor/parser/options' 5 | -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/core_ext/file_binary_read.rb: -------------------------------------------------------------------------------- 1 | class File #:nodoc: 2 | 3 | unless File.respond_to?(:binread) 4 | def self.binread(file) 5 | File.open(file, 'rb') { |f| f.read } 6 | end 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /spec/cache/git_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | describe "bundle cache with git" do 3 | it "base_name should strip private repo uris" do 4 | source = Bundler::Source::Git.new("uri" => "git@gitthub.com:bundler.git") 5 | source.send(:base_name).should == "bundler" 6 | end 7 | end 8 | 9 | 10 | -------------------------------------------------------------------------------- /spec/other/help_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle help" do 4 | it "complains if older versions of bundler are installed" do 5 | system_gems "bundler-0.8.1" 6 | 7 | bundle "help", :expect_err => true 8 | err.should == "Please remove older versions of bundler. This can be done by running `gem cleanup bundler`." 9 | end 10 | end -------------------------------------------------------------------------------- /spec/runtime/with_clean_env_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Bundler.with_clean_env" do 4 | 5 | it "should reset and restore the environment" do 6 | gem_path = ENV['GEM_PATH'] 7 | 8 | Bundler.with_clean_env do 9 | `echo $GEM_PATH`.strip.should_not == gem_path 10 | end 11 | 12 | ENV['GEM_PATH'].should == gem_path 13 | end 14 | 15 | end -------------------------------------------------------------------------------- /spec/support/ruby_ext.rb: -------------------------------------------------------------------------------- 1 | class IO 2 | def read_available_bytes(chunk_size = 1024, select_timeout = 5) 3 | buffer = [] 4 | 5 | while self.class.select([self], nil, nil, select_timeout) 6 | begin 7 | buffer << self.readpartial(chunk_size) 8 | rescue(EOFError) 9 | break 10 | end 11 | end 12 | 13 | return buffer.join 14 | end 15 | end -------------------------------------------------------------------------------- /spec/other/ext_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Gem::Specification#match_platform" do 4 | it "works" do 5 | darwin = gem "lol", "1.0", "platform_specific-1.0-x86-darwin-10" 6 | darwin.match_platform(pl('java')).should be_false 7 | end 8 | end 9 | 10 | describe "Gem::Platform#to_generic" do 11 | it "works" do 12 | pl('x86-darwin-10').to_generic.should == pl('ruby') 13 | end 14 | end -------------------------------------------------------------------------------- /spec/install/invalid_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with deprecated features" do 4 | before :each do 5 | in_app_root 6 | end 7 | 8 | it "reports that lib is an invalid option" do 9 | gemfile <<-G 10 | gem "rack", :lib => "rack" 11 | G 12 | 13 | bundle :install 14 | out.should =~ /You passed :lib as an option for gem 'rack', but it is invalid/ 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /spec/resolver/basic_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Resolving" do 4 | 5 | before :each do 6 | @index = an_awesome_index 7 | end 8 | 9 | it "resolves a single gem" do 10 | dep "rack" 11 | 12 | should_resolve_as %w(rack-1.1) 13 | end 14 | 15 | it "resolves a gem with dependencies" do 16 | dep "actionpack" 17 | 18 | should_resolve_as %w(actionpack-2.3.5 activesupport-2.3.5 rack-1.0) 19 | end 20 | end -------------------------------------------------------------------------------- /lib/bundler/setup.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/shared_helpers' 2 | 3 | if Bundler::SharedHelpers.in_bundle? 4 | require 'bundler' 5 | begin 6 | Bundler.setup 7 | rescue Bundler::BundlerError => e 8 | puts "\e[31m#{e.message}\e[0m" 9 | exit e.status_code 10 | end 11 | 12 | # Add bundler to the load path after disabling system gems 13 | bundler_lib = File.expand_path("../..", __FILE__) 14 | $LOAD_PATH.unshift(bundler_lib) unless $LOAD_PATH.include?(bundler_lib) 15 | end -------------------------------------------------------------------------------- /spec/pack/gems_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle pack with gems" do 4 | describe "when there are only gemsources" do 5 | before :each do 6 | gemfile <<-G 7 | gem 'rack' 8 | G 9 | 10 | system_gems "rack-1.0.0" 11 | bundle :pack 12 | end 13 | 14 | it "locks the gemfile" do 15 | bundled_app("Gemfile.lock").should exist 16 | end 17 | 18 | it "caches the gems" do 19 | bundled_app("vendor/cache/rack-1.0.0.gem").should exist 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Check if an older version of bundler is installed 4 | require 'bundler' 5 | $:.each do |path| 6 | if path =~ %r'/bundler-0.(\d+)' && $1.to_i < 9 7 | abort "Please remove older versions of bundler. This can be done by running `gem cleanup bundler`." 8 | end 9 | end 10 | require 'bundler/cli' 11 | 12 | begin 13 | Bundler::CLI.start 14 | rescue Bundler::BundlerError => e 15 | Bundler.ui.error e.message 16 | exit e.status_code 17 | rescue Interrupt 18 | Bundler.ui.error "\nQuitting..." 19 | exit 1 20 | end -------------------------------------------------------------------------------- /lib/bundler/templates/Executable: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This is not actually required by the actual library 3 | # loads the bundled environment 4 | require 'rubygems' 5 | 6 | begin 7 | require 'bundler/setup' 8 | rescue LoadError 9 | # Let's not complain if bundler isn't around 10 | end 11 | 12 | base = File.basename($0) 13 | paths = ENV['PATH'].split(File::PATH_SEPARATOR) 14 | here = File.expand_path(File.dirname(__FILE__)) 15 | 16 | gem_stub = paths.find do |path| 17 | path = File.expand_path(path) 18 | 19 | next if path == here 20 | 21 | File.exist?("#{path}/#{base}") 22 | end 23 | 24 | if gem_stub 25 | load "#{gem_stub}/#{base}" 26 | else 27 | abort "The gem stub #{base} could not be found" 28 | end -------------------------------------------------------------------------------- /spec/runtime/platform_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Bundler.setup with multi platform stuff" do 4 | it "raises a friendly error when gems are missing locally" do 5 | gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rack" 8 | G 9 | 10 | lockfile <<-G 11 | GEM 12 | remote: file:#{gem_repo1}/ 13 | specs: 14 | rack (1.0) 15 | 16 | PLATFORMS 17 | #{local_tag} 18 | 19 | DEPENDENCIES 20 | rack 21 | G 22 | 23 | ruby <<-R 24 | begin 25 | require 'bundler' 26 | Bundler.setup 27 | rescue Bundler::GemNotFound => e 28 | puts "WIN" 29 | end 30 | R 31 | 32 | out.should == "WIN" 33 | end 34 | end -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Bundler TODO list 2 | 3 | - Check to make sure ~/.bundler/bin is in $PATH 4 | - Cache Git repositories 5 | - Interactive mode for bundle (install) to work out conflicts 6 | - bundle irb / bundle ruby / bundle [whatever] -> bundle exec 7 | - Make bundle (install) work when sudo might be needed 8 | - Generate a bundle stub into the application 9 | - Handle the following case (no remote fetching): 10 | 1) Depend on nokogiri, nokogiri is installed locally (ruby platform) 11 | 2) Run bundle package. nokogiri-1.4.2.gem is cached 12 | 3) Clone on jruby 13 | 4) Run `bundle install` 14 | Bundler will happily install the RUBY platform nokogiri because it 15 | is cached and bundler has not hit the remote source once so it does 16 | not know that there is a nokogiri-1.4.2-java.gem available -------------------------------------------------------------------------------- /spec/lock/git_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle lock with git gems" do 4 | before :each do 5 | build_git "foo" 6 | 7 | install_gemfile <<-G 8 | gem 'foo', :git => "#{lib_path('foo-1.0')}" 9 | G 10 | end 11 | 12 | it "doesn't break right after running lock" do 13 | should_be_installed "foo 1.0.0" 14 | end 15 | 16 | it "locks a git source to the current ref" do 17 | update_git "foo" 18 | bundle :install 19 | 20 | run <<-RUBY 21 | require 'foo' 22 | puts "WIN" unless defined?(FOO_PREV_REF) 23 | RUBY 24 | 25 | out.should == "WIN" 26 | end 27 | 28 | it "provides correct #full_gem_path" do 29 | run <<-RUBY 30 | puts Gem.source_index.find_name('foo').first.full_gem_path 31 | RUBY 32 | out.should == bundle("show foo") 33 | end 34 | end -------------------------------------------------------------------------------- /spec/cache/path_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle cache" do 4 | describe "with path sources" do 5 | it "is silent when the path is within the bundle" do 6 | build_lib "foo", :path => bundled_app("lib/foo") 7 | 8 | install_gemfile <<-G 9 | gem "foo", :path => '#{bundled_app("lib/foo")}' 10 | G 11 | 12 | bundle "cache" 13 | out.should == "Updating .gem files in vendor/cache\nRemoving outdated .gem files from vendor/cache" 14 | end 15 | 16 | it "warns when the path is outside of the bundle" do 17 | build_lib "foo" 18 | 19 | install_gemfile <<-G 20 | gem "foo", :path => '#{lib_path("foo-1.0")}' 21 | G 22 | 23 | bundle "cache" 24 | out.should include("foo at `#{lib_path("foo-1.0")}` will not be cached") 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /spec/support/sudo.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Sudo 3 | def self.sudo? 4 | @which_sudo ||= `which sudo`.strip 5 | !@which_sudo.empty? 6 | end 7 | 8 | module Describe 9 | def describe_sudo(*args, &blk) 10 | return unless Sudo.sudo? 11 | describe(*args) do 12 | before :each do 13 | pending "sudo tests are broken" 14 | chown_system_gems 15 | end 16 | 17 | instance_eval(&blk) 18 | end 19 | end 20 | end 21 | 22 | def self.included(klass) 23 | klass.extend Describe 24 | end 25 | 26 | def sudo? 27 | Sudo.sudo? 28 | end 29 | 30 | def sudo(cmd) 31 | cmd = "sudo #{cmd}" if sudo? 32 | sys_exec(cmd) 33 | end 34 | 35 | def chown_system_gems 36 | sudo "chown -R root #{system_gem_path}" 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /lib/bundler/environment.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | class Environment 3 | attr_reader :root 4 | 5 | def initialize(root, definition) 6 | @root = root 7 | @definition = definition 8 | end 9 | 10 | # TODO: Remove this method. It's used in cli.rb still 11 | def index 12 | @definition.index 13 | end 14 | 15 | def requested_specs 16 | @definition.requested_specs 17 | end 18 | 19 | def specs 20 | @definition.specs 21 | end 22 | 23 | def dependencies 24 | @definition.dependencies 25 | end 26 | 27 | def current_dependencies 28 | @definition.current_dependencies 29 | end 30 | 31 | def lock 32 | File.open(root.join('Gemfile.lock'), 'w') do |f| 33 | f.puts @definition.to_lock 34 | end 35 | end 36 | 37 | def update(*gems) 38 | # Nothing 39 | end 40 | 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/error.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | # Thor::Error is raised when it's caused by wrong usage of thor classes. Those 3 | # errors have their backtrace supressed and are nicely shown to the user. 4 | # 5 | # Errors that are caused by the developer, like declaring a method which 6 | # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we 7 | # ensure that developer errors are shown with full backtrace. 8 | # 9 | class Error < StandardError 10 | end 11 | 12 | # Raised when a task was not found. 13 | # 14 | class UndefinedTaskError < Error 15 | end 16 | 17 | # Raised when a task was found, but not invoked properly. 18 | # 19 | class InvocationError < Error 20 | end 21 | 22 | class UnknownArgumentError < Error 23 | end 24 | 25 | class RequiredArgumentMissingError < InvocationError 26 | end 27 | 28 | class MalformattedArgumentError < InvocationError 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/update/gems_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle update" do 4 | before :each do 5 | build_repo2 6 | 7 | install_gemfile <<-G 8 | source "file://#{gem_repo2}" 9 | gem "activesupport" 10 | gem "rack-obama" 11 | G 12 | end 13 | 14 | describe "with no arguments" do 15 | it "updates the entire bundle" do 16 | update_repo2 do 17 | build_gem "activesupport", "3.0" 18 | end 19 | 20 | bundle "update" 21 | should_be_installed "rack 1.2", "rack-obama 1.0", "activesupport 3.0" 22 | end 23 | end 24 | 25 | describe "with a top level dependency" do 26 | it "unlocks all child dependencies that are unrelated to other locked dependencies" do 27 | update_repo2 do 28 | build_gem "activesupport", "3.0" 29 | end 30 | 31 | bundle "update rack-obama" 32 | should_be_installed "rack 1.2", "rack-obama 1.0", "activesupport 2.3.5" 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /spec/support/platforms.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Platforms 3 | def rb 4 | Gem::Platform::RUBY 5 | end 6 | 7 | def mac 8 | Gem::Platform.new('x86-darwin-10') 9 | end 10 | 11 | def java 12 | Gem::Platform.new([nil, "java", nil]) 13 | end 14 | 15 | def linux 16 | Gem::Platform.new(['x86', 'linux', nil]) 17 | end 18 | 19 | def mswin 20 | Gem::Platform.new(['x86', 'mswin32', nil]) 21 | end 22 | 23 | def all_platforms 24 | [rb, java, linux, mswin] 25 | end 26 | 27 | def local 28 | Gem::Platform.local.to_generic 29 | end 30 | 31 | def not_local 32 | all_platforms.find { |p| p != Gem::Platform.local.to_generic } 33 | end 34 | 35 | def local_tag 36 | if RUBY_PLATFORM == "java" 37 | :jruby 38 | else 39 | :ruby 40 | end 41 | end 42 | 43 | def not_local_tag 44 | [:ruby, :jruby].find { |tag| tag != local_tag } 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /spec/install/deprecated_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with deprecated features" do 4 | before :each do 5 | in_app_root 6 | end 7 | 8 | %w( only except disable_system_gems disable_rubygems 9 | clear_sources bundle_path bin_path ).each do |deprecated| 10 | 11 | it "reports that #{deprecated} is deprecated" do 12 | gemfile <<-G 13 | #{deprecated} 14 | G 15 | 16 | bundle :install 17 | out.should =~ /'#{deprecated}' has been removed/ 18 | out.should =~ /See the README for more information/ 19 | end 20 | 21 | end 22 | 23 | 24 | %w( require_as vendored_at only except ).each do |deprecated| 25 | 26 | it "reports that :#{deprecated} is deprecated" do 27 | gemfile <<-G 28 | gem "rack", :#{deprecated} => true 29 | G 30 | 31 | bundle :install 32 | out.should =~ /Please replace :#{deprecated}|The :#{deprecated} option is no longer supported/ 33 | end 34 | 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /bundler.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $:.unshift lib unless $:.include?(lib) 4 | 5 | require 'bundler/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "bundler" 9 | s.version = Bundler::VERSION 10 | s.platform = Gem::Platform::RUBY 11 | s.authors = ["Carl Lerche", "Yehuda Katz", "André Arko"] 12 | s.email = ["carlhuda@engineyard.com"] 13 | s.homepage = "http://github.com/carlhuda/bundler" 14 | s.summary = "The best way to manage your application's dependencies" 15 | s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" 16 | 17 | s.required_rubygems_version = ">= 1.3.6" 18 | s.rubyforge_project = "bundler" 19 | 20 | s.add_development_dependency "rspec" 21 | 22 | s.files = Dir.glob("{bin,lib}/**/*") + %w(LICENSE README.md ROADMAP.md CHANGELOG.md TODO.md) 23 | s.executables = ['bundle'] 24 | s.require_path = 'lib' 25 | end -------------------------------------------------------------------------------- /lib/bundler/settings.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | class Settings 3 | def initialize(root) 4 | @root = root 5 | @config = File.exist?(config_file) ? YAML.load_file(config_file) : {} 6 | end 7 | 8 | def [](key) 9 | key = "BUNDLE_#{key.to_s.upcase}" 10 | @config[key] || ENV[key] 11 | end 12 | 13 | def []=(key, value) 14 | key = "BUNDLE_#{key.to_s.upcase}" 15 | unless @config[key] == value 16 | @config[key] = value 17 | FileUtils.mkdir_p(config_file.dirname) 18 | File.open(config_file, 'w') do |f| 19 | f.puts @config.to_yaml 20 | end 21 | end 22 | value 23 | end 24 | 25 | def without=(array) 26 | unless array.empty? && without.empty? 27 | self[:without] = array.join(":") 28 | end 29 | end 30 | 31 | def without 32 | self[:without] ? self[:without].split(":").map { |w| w.to_sym } : [] 33 | end 34 | 35 | private 36 | 37 | def config_file 38 | Pathname.new("#{@root}/.bundle/config") 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /spec/support/rubygems_ext.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Rubygems 3 | def self.setup 4 | Gem.clear_paths 5 | 6 | ENV['BUNDLE_PATH'] = nil 7 | ENV['GEM_HOME'] = ENV['GEM_PATH'] = Path.base_system_gems.to_s 8 | ENV['PATH'] = "#{Path.home}/.bundle/bin:#{Path.system_gem_path}/bin:#{ENV['PATH']}" 9 | 10 | unless File.exist?("#{Path.base_system_gems}") 11 | FileUtils.mkdir_p(Path.base_system_gems) 12 | puts "running `gem install builder rake fakeweb --no-rdoc --no-ri`" 13 | `gem install builder rake fakeweb --no-rdoc --no-ri` 14 | end 15 | 16 | ENV['HOME'] = Path.home.to_s 17 | 18 | Gem::DefaultUserInteraction.ui = Gem::SilentUI.new 19 | end 20 | 21 | def gem_command(command, args = "", options = {}) 22 | if command == :exec && !options[:no_quote] 23 | args = args.gsub(/(?=")/, "\\") 24 | args = %["#{args}"] 25 | end 26 | lib = File.join(File.dirname(__FILE__), '..', '..', 'lib') 27 | %x{#{Gem.ruby} -I#{lib} -rubygems -S gem --backtrace #{command} #{args}}.strip 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Engine Yard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /spec/other/init_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle init" do 4 | it "generates a Gemfile" do 5 | bundle :init 6 | bundled_app("Gemfile").should exist 7 | end 8 | 9 | it "does not change existing Gemfiles" do 10 | gemfile <<-G 11 | gem "rails" 12 | G 13 | 14 | lambda { 15 | bundle :init 16 | }.should_not change { File.read(bundled_app("Gemfile")) } 17 | end 18 | 19 | it "should generate from an existing gemspec" do 20 | spec_file = tmp.join('test.gemspec') 21 | File.open(spec_file, 'w') do |file| 22 | file << <<-S 23 | Gem::Specification.new do |s| 24 | s.name = 'test' 25 | s.add_dependency 'rack', '= 1.0.1' 26 | s.add_development_dependency 'rspec', '1.2' 27 | end 28 | S 29 | end 30 | 31 | bundle :init, :gemspec => spec_file 32 | 33 | gemfile = bundled_app("Gemfile").read 34 | gemfile.should =~ /source :gemcutter/ 35 | gemfile.scan(/gem "rack", "= 1.0.1"/).size.should == 1 36 | gemfile.scan(/gem "rspec", "= 1.2"/).size.should == 1 37 | gemfile.scan(/group :development/).size.should == 1 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /lib/bundler/ui.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | class UI 3 | def warn(message) 4 | end 5 | 6 | def error(message) 7 | end 8 | 9 | def info(message) 10 | end 11 | 12 | def confirm(message) 13 | end 14 | 15 | class Shell < UI 16 | def initialize(shell) 17 | @shell = shell 18 | @quiet = false 19 | end 20 | 21 | def debug(msg) 22 | @shell.say(msg) if ENV['DEBUG'] && !@quiet 23 | end 24 | 25 | def info(msg) 26 | @shell.say(msg) if !@quiet 27 | end 28 | 29 | def confirm(msg) 30 | @shell.say(msg, :green) if !@quiet 31 | end 32 | 33 | def warn(msg) 34 | @shell.say(msg, :yellow) 35 | end 36 | 37 | def error(msg) 38 | @shell.say(msg, :red) 39 | end 40 | 41 | def be_quiet! 42 | @quiet = true 43 | end 44 | end 45 | 46 | class RGProxy < Gem::SilentUI 47 | def initialize(ui) 48 | @ui = ui 49 | end 50 | 51 | def say(message) 52 | if message =~ /native extensions/ 53 | @ui.info "with native extensions " 54 | else 55 | @ui.debug(message) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/update/source_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle update" do 4 | describe "git sources" do 5 | before :each do 6 | build_repo2 7 | @git = build_git "foo", :path => lib_path("foo") do |s| 8 | s.executables = "foobar" 9 | end 10 | 11 | install_gemfile <<-G 12 | source "file://#{gem_repo2}" 13 | git "#{lib_path('foo')}" 14 | gem 'foo' 15 | gem 'rack' 16 | G 17 | end 18 | 19 | it "updates the source" do 20 | update_git "foo", :path => @git.path 21 | 22 | bundle "update --source foo" 23 | 24 | in_app_root do 25 | run <<-RUBY 26 | require 'foo' 27 | puts "WIN" if defined?(FOO_PREV_REF) 28 | RUBY 29 | 30 | out.should == "WIN" 31 | end 32 | end 33 | 34 | it "unlocks gems that were originally pulled in by the source" do 35 | update_git "foo", "2.0", :path => @git.path 36 | 37 | bundle "update --source foo" 38 | should_be_installed "foo 2.0" 39 | end 40 | 41 | it "leaves all other gems frozen" do 42 | update_repo2 43 | update_git "foo", :path => @git.path 44 | 45 | bundle "update --source foo" 46 | should_be_installed "rack 1.0" 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/other/open_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle open" do 4 | before :each do 5 | install_gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rails" 8 | G 9 | end 10 | 11 | it "opens the gem with VISUAL if set" do 12 | bundle "open rails", :env => {"EDITOR" => "echo editor", "VISUAL" => "echo visual"} 13 | out.should == "visual #{default_bundle_path('gems', 'rails-2.3.2')}" 14 | end 15 | 16 | it "opens the gem with EDITOR if set" do 17 | bundle "open rails", :env => {"EDITOR" => "echo editor", "VISUAL" => ''} 18 | out.should == "editor #{default_bundle_path('gems', 'rails-2.3.2')}" 19 | end 20 | 21 | it "complains if gem not in bundle" do 22 | bundle "open missing", :env => {"EDITOR" => "echo editor", "VISUAL" => ''} 23 | out.should match(/could not find gem 'missing'/i) 24 | end 25 | 26 | describe "while locked" do 27 | before :each do 28 | bundle :lock 29 | end 30 | 31 | it "opens the gem with EDITOR if set" do 32 | bundle "open rails", :env => {"EDITOR" => "echo editor", "VISUAL" => ''} 33 | out.should == "editor #{default_bundle_path('gems', 'rails-2.3.2')}" 34 | end 35 | 36 | it "complains if gem not in bundle" do 37 | bundle "open missing", :env => {"EDITOR" => "echo editor", "VISUAL" => ''} 38 | out.should match(/could not find gem 'missing'/i) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/quality_spec.rb: -------------------------------------------------------------------------------- 1 | describe "The library itself" do 2 | def check_for_tab_characters(filename) 3 | failing_lines = [] 4 | File.readlines(filename).each_with_index do |line,number| 5 | failing_lines << number + 1 if line =~ /\t/ 6 | end 7 | 8 | unless failing_lines.empty? 9 | "#{filename} has tab characters on lines #{failing_lines.join(', ')}" 10 | end 11 | end 12 | 13 | def check_for_extra_spaces(filename) 14 | failing_lines = [] 15 | File.readlines(filename).each_with_index do |line,number| 16 | next if line =~ /^\s+#.*\s+\n$/ 17 | failing_lines << number + 1 if line =~ /\s+\n$/ 18 | end 19 | 20 | unless failing_lines.empty? 21 | "#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}" 22 | end 23 | end 24 | 25 | def be_well_formed 26 | simple_matcher("be well formed") do |given, matcher| 27 | matcher.failure_message = given.join("\n") 28 | given.empty? 29 | end 30 | end 31 | 32 | it "has no malformed whitespace" do 33 | error_messages = [] 34 | Dir.chdir(File.expand_path("../..", __FILE__)) do 35 | `git ls-files`.split("\n").each do |filename| 36 | next if filename =~ /\.gitmodules|fixtures/ 37 | error_messages << check_for_tab_characters(filename) 38 | error_messages << check_for_extra_spaces(filename) 39 | end 40 | end 41 | error_messages.compact.should be_well_formed 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/support/path.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Path 3 | def root 4 | @root ||= Pathname.new(File.expand_path("../../..", __FILE__)) 5 | end 6 | 7 | def tmp(*path) 8 | root.join("tmp", *path) 9 | end 10 | 11 | def home(*path) 12 | tmp.join("home", *path) 13 | end 14 | 15 | def default_bundle_path(*path) 16 | system_gem_path(*path) 17 | end 18 | 19 | def bundled_app(*path) 20 | root = tmp.join("bundled_app") 21 | FileUtils.mkdir_p(root) 22 | root.join(*path) 23 | end 24 | 25 | alias bundled_app1 bundled_app 26 | 27 | def bundled_app2(*path) 28 | root = tmp.join("bundled_app2") 29 | FileUtils.mkdir_p(root) 30 | root.join(*path) 31 | end 32 | 33 | def cached_gem(path) 34 | bundled_app("vendor/cache/#{path}.gem") 35 | end 36 | 37 | def base_system_gems 38 | tmp.join("gems/base") 39 | end 40 | 41 | def gem_repo1(*args) 42 | tmp("gems/remote1", *args) 43 | end 44 | 45 | def gem_repo2(*args) 46 | tmp("gems/remote2", *args) 47 | end 48 | 49 | def gem_repo3(*args) 50 | tmp("gems/remote3", *args) 51 | end 52 | 53 | def system_gem_path(*path) 54 | tmp("gems/system", *path) 55 | end 56 | 57 | def lib_path(*args) 58 | tmp("libs", *args) 59 | end 60 | 61 | def bundler_path 62 | Pathname.new(File.expand_path('../../../lib', __FILE__)) 63 | end 64 | 65 | extend self 66 | end 67 | end -------------------------------------------------------------------------------- /spec/install/gems/locked_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with gem sources" do 4 | describe "when locked and installed with --without" do 5 | before(:each) do 6 | build_repo2 7 | system_gems "rack-0.9.1" do 8 | install_gemfile <<-G, :without => :rack 9 | source "file://#{gem_repo2}" 10 | gem "rack" 11 | 12 | group :rack do 13 | gem "rack_middleware" 14 | end 15 | G 16 | 17 | bundle :lock 18 | end 19 | end 20 | 21 | it "uses the correct versions even if --without was used on the original" do 22 | should_be_installed "rack 0.9.1" 23 | should_not_be_installed "rack_middleware 1.0" 24 | simulate_new_machine 25 | 26 | bundle :install 27 | 28 | should_be_installed "rack 0.9.1" 29 | should_be_installed "rack_middleware 1.0" 30 | end 31 | 32 | it "regenerates the environment.rb if install is called twice on a locked repo" do 33 | run "begin; require 'rack_middleware'; rescue LoadError; puts 'WIN'; end", :lite_runtime => true 34 | check out.should == "WIN" 35 | 36 | bundle :install 37 | 38 | run "require 'rack_middleware'; puts RACK_MIDDLEWARE", :lite_runtime => true 39 | out.should == "1.0" 40 | end 41 | 42 | it "does not hit the remote a second time" do 43 | FileUtils.rm_rf gem_repo2 44 | bundle "install --without rack" 45 | err.should be_empty 46 | end 47 | end 48 | end -------------------------------------------------------------------------------- /spec/resolver/platform_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Resolving platform craziness" do 4 | describe "with semi real cases" do 5 | before :each do 6 | @index = an_awesome_index 7 | end 8 | 9 | it "resolves a simple multi platform gem" do 10 | dep "nokogiri" 11 | platforms "ruby", "java" 12 | 13 | should_resolve_as %w(nokogiri-1.4.2 nokogiri-1.4.2-java weakling-0.0.3) 14 | end 15 | 16 | it "doesn't pull gems when it doesn't exist for the current platform" do 17 | dep "nokogiri" 18 | platforms "ruby" 19 | 20 | should_resolve_as %w(nokogiri-1.4.2) 21 | end 22 | 23 | it "doesn't pulls gems when the version is available for all requested platforms" do 24 | dep "nokogiri" 25 | platforms "mswin32" 26 | 27 | should_resolve_as %w(nokogiri-1.4.2.1-x86-mswin32) 28 | end 29 | end 30 | 31 | describe "with conflicting cases" do 32 | before :each do 33 | @index = build_index do 34 | gem "foo", "1.0.0" do 35 | dep "bar", ">= 0" 36 | end 37 | 38 | gem 'bar', "1.0.0" do 39 | dep "baz", "~> 1.0.0" 40 | end 41 | 42 | gem "bar", "1.0.0", "java" do 43 | dep "baz", " ~> 1.1.0" 44 | end 45 | 46 | gem "baz", %w(1.0.0 1.1.0 1.2.0) 47 | end 48 | end 49 | 50 | it "does something" do 51 | platforms "ruby", "java" 52 | dep "foo" 53 | 54 | should_conflict_on "baz" 55 | end 56 | end 57 | end -------------------------------------------------------------------------------- /spec/install/gems/packed_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with gem sources" do 4 | describe "when cached and locked" do 5 | it "does not hit the remote at all" do 6 | build_repo2 7 | install_gemfile <<-G 8 | source "file://#{gem_repo2}" 9 | gem "rack" 10 | G 11 | 12 | bundle :pack 13 | simulate_new_machine 14 | FileUtils.rm_rf gem_repo2 15 | 16 | bundle :install 17 | should_be_installed "rack 1.0.0" 18 | end 19 | 20 | it "does not reinstall already-installed gems" do 21 | install_gemfile <<-G 22 | source "file://#{gem_repo1}" 23 | gem "rack" 24 | G 25 | bundle :pack 26 | 27 | build_gem "rack", "1.0.0", :path => bundled_app('vendor/cache') do |s| 28 | s.write "lib/rack.rb", "raise 'omg'" 29 | end 30 | 31 | bundle :install 32 | err.should be_empty 33 | should_be_installed "rack 1.0" 34 | end 35 | 36 | it "ignores cached gems for the wrong platform" do 37 | install_gemfile <<-G 38 | Gem.platforms = [Gem::Platform.new('#{java}')] 39 | source "file://#{gem_repo1}" 40 | gem "platform_specific" 41 | G 42 | bundle :pack 43 | simulate_new_machine 44 | 45 | install_gemfile <<-G 46 | Gem.platforms = [Gem::Platform.new('#{rb}')] 47 | source "file://#{gem_repo1}" 48 | gem "platform_specific" 49 | G 50 | run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" 51 | out.should == "1.0.0 RUBY" 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /lib/bundler/installer.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems/dependency_installer' 2 | 3 | module Bundler 4 | class Installer < Environment 5 | def self.install(root, definition, options = {}) 6 | installer = new(root, definition) 7 | installer.run(options) 8 | installer 9 | end 10 | 11 | def run(options) 12 | if dependencies.empty? 13 | Bundler.ui.warn "The Gemfile specifies no dependencies" 14 | return 15 | end 16 | 17 | # Since we are installing, we can resolve the definition 18 | # using remote specs 19 | @definition.resolve_remotely! 20 | 21 | # Ensure that BUNDLE_PATH exists 22 | FileUtils.mkdir_p(Bundler.bundle_path) 23 | 24 | # Must install gems in the order that the resolver provides 25 | # as dependencies might actually affect the installation of 26 | # the gem. 27 | specs.each do |spec| 28 | spec.source.fetch(spec) if spec.source.respond_to?(:fetch) 29 | 30 | # unless requested_specs.include?(spec) 31 | # Bundler.ui.debug " * Not in requested group; skipping." 32 | # next 33 | # end 34 | 35 | spec.source.install(spec) 36 | Bundler.ui.info "" 37 | generate_bundler_executable_stubs(spec) 38 | FileUtils.rm_rf(Bundler.tmp) 39 | end 40 | 41 | lock 42 | end 43 | 44 | private 45 | 46 | def generate_bundler_executable_stubs(spec) 47 | spec.executables.each do |executable| 48 | next if executable == "bundle" 49 | File.open "#{Bundler.bin_path}/#{executable}", 'w', 0755 do |f| 50 | f.puts File.read(File.expand_path('../templates/Executable', __FILE__)) 51 | end 52 | end 53 | end 54 | 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # 1.0 2 | 3 | - No breaking changes to the Gemfile are expected 4 | - We expect to modify the format of Gemfile.lock. 5 | - This should be the final change 6 | - This means you will not be able to upgrade a locked app 7 | directly from 0.9 to 1.0. 8 | - Bundler will automatically generate Gemfile.lock when any 9 | resolve is successful. 10 | - This means the bundle lock command will no longer be needed. 11 | - Bundler will conservatively update Gemfile.lock from the 12 | last successful resolve if the Gemfile has been modified since 13 | the last use of bundler. 14 | - This means that adding a new gem to the Gemfile that does not 15 | conflict with existing gems will not force an update of other 16 | gems. 17 | - This also means that we will not force an update to previously 18 | resolved dependencies as long as they are compatible with some 19 | valid version of the new dependency. 20 | - When removing a gem, bundle install will simply remove it, without 21 | recalculating all dependencies. 22 | - We will be adding `bundle update` for the case where you -do- 23 | wish to re-resolve all dependencies and update everything to the 24 | latest version. 25 | - bundle update will also take a gem name, if you want to force 26 | an update to just a single gem (and its dependencies). 27 | - There will be a way to install dependencies that require build options 28 | - We will add groups that are opt-in at install-time, rather than opt-out. 29 | - We will reduce open bug count to 0 for the final 1.0 release. 30 | - Some additional features that require more thought. For details, 31 | see http://github.com/carlhuda/bundler/issues/labels/1.0 32 | 33 | # 1.1 34 | 35 | - Stop upgrading 0.9 lockfiles 36 | - Delete vestigial gems installed into ~/.bundle/ by 0.9 -------------------------------------------------------------------------------- /spec/update/git_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle update" do 4 | describe "git sources" do 5 | it "floats on a branch when :branch is used" do 6 | build_git "foo", "1.0" 7 | update_git "foo", :branch => "omg" 8 | 9 | install_gemfile <<-G 10 | git "#{lib_path('foo-1.0')}", :branch => "omg" 11 | gem 'foo' 12 | G 13 | 14 | update_git "foo", :branch => "omg" do |s| 15 | s.write "lib/foo.rb", "FOO = '1.1'" 16 | end 17 | 18 | bundle "update" 19 | 20 | should_be_installed "foo 1.1" 21 | end 22 | 23 | it "floats on a branch when :branch is used and the source is specified in the update" do 24 | build_git "foo", "1.0", :path => lib_path("foo") 25 | update_git "foo", :branch => "omg", :path => lib_path("foo") 26 | 27 | install_gemfile <<-G 28 | git "#{lib_path('foo')}", :branch => "omg" do 29 | gem 'foo' 30 | end 31 | G 32 | 33 | update_git "foo", :branch => "omg", :path => lib_path("foo") do |s| 34 | s.write "lib/foo.rb", "FOO = '1.1'" 35 | end 36 | 37 | bundle "update --source foo" 38 | 39 | should_be_installed "foo 1.1" 40 | end 41 | 42 | it "notices when you change the repo url in the Gemfile" do 43 | build_git "foo", :path => lib_path("foo_one") 44 | build_git "foo", :path => lib_path("foo_two") 45 | 46 | install_gemfile <<-G 47 | gem "foo", "1.0", :git => "#{lib_path('foo_one')}" 48 | G 49 | 50 | FileUtils.rm_rf lib_path("foo_one") 51 | 52 | install_gemfile <<-G 53 | gem "foo", "1.0", :git => "#{lib_path('foo_two')}" 54 | G 55 | 56 | err.should be_empty 57 | out.should include("Fetching #{lib_path}/foo_two") 58 | out.should include("Your bundle is complete!") 59 | end 60 | end 61 | end -------------------------------------------------------------------------------- /lib/bundler/lazy_specification.rb: -------------------------------------------------------------------------------- 1 | require "uri" 2 | require "rubygems/spec_fetcher" 3 | 4 | module Bundler 5 | class LazySpecification 6 | include Gem::MatchPlatform 7 | 8 | attr_reader :name, :version, :dependencies, :platform 9 | attr_accessor :source 10 | 11 | def initialize(name, version, platform, source = nil) 12 | @name = name 13 | @version = version 14 | @dependencies = [] 15 | @platform = platform 16 | @source = source 17 | @specification = nil 18 | end 19 | 20 | def full_name 21 | if platform == Gem::Platform::RUBY or platform.nil? then 22 | "#{@name}-#{@version}" 23 | else 24 | "#{@name}-#{@version}-#{platform}" 25 | end 26 | end 27 | 28 | def satisfies?(dependency) 29 | @name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version)) 30 | end 31 | 32 | def to_lock 33 | if platform == Gem::Platform::RUBY or platform.nil? 34 | out = " #{name} (#{version})\n" 35 | else 36 | out = " #{name} (#{version}-#{platform})\n" 37 | end 38 | 39 | dependencies.sort_by {|d| d.name }.each do |dep| 40 | next if dep.type == :development 41 | out << " #{dep.to_lock}\n" 42 | end 43 | 44 | out 45 | end 46 | 47 | def __materialize__ 48 | @specification = source[self] 49 | end 50 | 51 | def respond_to?(*args) 52 | super || @specification.respond_to?(*args) 53 | end 54 | 55 | private 56 | 57 | def method_missing(method, *args, &blk) 58 | if Gem::Specification.new.respond_to?(method) 59 | raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification 60 | @specification.send(method, *args, &blk) 61 | else 62 | super 63 | end 64 | end 65 | 66 | end 67 | end -------------------------------------------------------------------------------- /lib/bundler/remote_specification.rb: -------------------------------------------------------------------------------- 1 | require "uri" 2 | require "rubygems/spec_fetcher" 3 | 4 | module Bundler 5 | # Represents a lazily loaded gem specification, where the full specification 6 | # is on the source server in rubygems' "quick" index. The proxy object is to 7 | # be seeded with what we're given from the source's abbreviated index - the 8 | # full specification will only be fetched when necesary. 9 | class RemoteSpecification 10 | include Gem::MatchPlatform 11 | 12 | attr_reader :name, :version, :platform 13 | attr_accessor :source 14 | 15 | def initialize(name, version, platform, source_uri) 16 | @name = name 17 | @version = version 18 | @platform = platform 19 | @source_uri = source_uri 20 | end 21 | 22 | # Needed before installs, since the arch matters then and quick 23 | # specs don't bother to include the arch in the platform string 24 | def fetch_platform 25 | @platform = _remote_specification.platform 26 | end 27 | 28 | def full_name 29 | if platform == Gem::Platform::RUBY or platform.nil? then 30 | "#{@name}-#{@version}" 31 | else 32 | "#{@name}-#{@version}-#{platform}" 33 | end 34 | end 35 | 36 | # Because Rubyforge cannot be trusted to provide valid specifications 37 | # once the remote gem is downloaded, the backend specification will 38 | # be swapped out. 39 | def __swap__(spec) 40 | @specification = spec 41 | end 42 | 43 | private 44 | 45 | def _remote_specification 46 | @specification ||= begin 47 | Gem::SpecFetcher.new.fetch_spec([@name, @version, @platform], URI(@source_uri.to_s)) 48 | end 49 | end 50 | 51 | def method_missing(method, *args, &blk) 52 | if Gem::Specification.new.respond_to?(method) 53 | _remote_specification.send(method, *args, &blk) 54 | else 55 | super 56 | end 57 | end 58 | end 59 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/parser/argument.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Argument #:nodoc: 3 | VALID_TYPES = [ :numeric, :hash, :array, :string ] 4 | 5 | attr_reader :name, :description, :required, :type, :default, :banner 6 | alias :human_name :name 7 | 8 | def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil) 9 | class_name = self.class.name.split("::").last 10 | 11 | raise ArgumentError, "#{class_name} name can't be nil." if name.nil? 12 | raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) 13 | 14 | @name = name.to_s 15 | @description = description 16 | @required = required || false 17 | @type = (type || :string).to_sym 18 | @default = default 19 | @banner = banner || default_banner 20 | 21 | validate! # Trigger specific validations 22 | end 23 | 24 | def usage 25 | required? ? banner : "[#{banner}]" 26 | end 27 | 28 | def required? 29 | required 30 | end 31 | 32 | def show_default? 33 | case default 34 | when Array, String, Hash 35 | !default.empty? 36 | else 37 | default 38 | end 39 | end 40 | 41 | protected 42 | 43 | def validate! 44 | raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? 45 | end 46 | 47 | def valid_type?(type) 48 | VALID_TYPES.include?(type.to_sym) 49 | end 50 | 51 | def default_banner 52 | case type 53 | when :boolean 54 | nil 55 | when :string, :default 56 | human_name.upcase 57 | when :numeric 58 | "N" 59 | when :hash 60 | "key:value" 61 | when :array 62 | "one two three" 63 | end 64 | end 65 | 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path('..', __FILE__) 2 | $:.unshift File.expand_path('../../lib', __FILE__) 3 | 4 | require 'fileutils' 5 | require 'rubygems' 6 | require 'bundler' 7 | require 'spec' 8 | 9 | begin 10 | require 'differ' 11 | rescue LoadError 12 | abort "You need the `differ' gem installed to run the tests" 13 | end 14 | 15 | Dir["#{File.expand_path('../support', __FILE__)}/*.rb"].each do |file| 16 | require file 17 | end 18 | 19 | $debug = false 20 | $show_err = true 21 | 22 | Differ.format = :color 23 | 24 | Spec::Rubygems.setup 25 | FileUtils.rm_rf(Spec::Path.gem_repo1) 26 | 27 | Spec::Runner.configure do |config| 28 | config.include Spec::Builders 29 | config.include Spec::Helpers 30 | config.include Spec::Indexes 31 | config.include Spec::Matchers 32 | config.include Spec::Path 33 | config.include Spec::Rubygems 34 | config.include Spec::Platforms 35 | config.include Spec::Sudo 36 | 37 | original_wd = Dir.pwd 38 | original_path = ENV['PATH'] 39 | original_gem_home = ENV['GEM_HOME'] 40 | 41 | def pending_jruby_shebang_fix 42 | pending "JRuby executables do not have a proper shebang" if RUBY_PLATFORM == "java" 43 | end 44 | 45 | def check(*args) 46 | # suppresses ruby warnings about "useless use of == in void context" 47 | # e.g. check foo.should == bar 48 | end 49 | 50 | def pending_bundle_update 51 | pending "bundle install does NOT update the git ref anymore. This is a bundle update feature" 52 | end 53 | 54 | config.before :all do 55 | build_repo1 56 | end 57 | 58 | config.before :each do 59 | reset! 60 | system_gems [] 61 | in_app_root 62 | end 63 | 64 | config.after :each do 65 | Gem.platforms = nil 66 | Dir.chdir(original_wd) 67 | # Reset ENV 68 | ENV['PATH'] = original_path 69 | ENV['GEM_HOME'] = original_gem_home 70 | ENV['GEM_PATH'] = original_gem_home 71 | ENV['BUNDLE_PATH'] = nil 72 | ENV['BUNDLE_GEMFILE'] = nil 73 | ENV['BUNDLER_TEST'] = nil 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/runtime/executable_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Running commands" do 4 | it "runs the bundled command when in the bundle" do 5 | install_gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rack" 8 | G 9 | 10 | build_gem "rack", "2.0", :to_system => true do |s| 11 | s.executables = "rackup" 12 | end 13 | 14 | gembin "rackup" 15 | out.should == "1.0.0" 16 | end 17 | 18 | it "runs the system command when out of the bundle" do 19 | install_gemfile <<-G 20 | source "file://#{gem_repo1}" 21 | gem "rack" 22 | G 23 | 24 | build_gem "rack", "2.0", :to_system => true do |s| 25 | s.executables = "rackup" 26 | end 27 | 28 | Dir.chdir(tmp) do 29 | gembin "rackup" 30 | out.should == "2.0" 31 | end 32 | end 33 | 34 | it "works with gems in path" do 35 | build_lib "rack", :path => lib_path("rack") do |s| 36 | s.executables = 'rackup' 37 | end 38 | 39 | install_gemfile <<-G 40 | gem "rack", :path => "#{lib_path('rack')}" 41 | G 42 | 43 | build_gem 'rack', '2.0', :to_system => true do |s| 44 | s.executables = 'rackup' 45 | end 46 | 47 | gembin "rackup" 48 | out.should == '1.0' 49 | end 50 | 51 | it "blows up when running outside of the directory" do 52 | build_lib "rack", :path => lib_path("rack") do |s| 53 | s.executables = 'rackup' 54 | end 55 | 56 | install_gemfile <<-G 57 | gem "rack", :path => "#{lib_path('rack')}" 58 | G 59 | 60 | build_gem 'rack', '2.0', :to_system => true do |s| 61 | s.executables = 'rackup' 62 | end 63 | 64 | Dir.chdir(tmp) do 65 | gembin "rackup" 66 | out.should == '2.0' 67 | end 68 | end 69 | 70 | it "don't bundle da bundla" do 71 | build_gem "bundler", Bundler::VERSION, :to_system => true do |s| 72 | s.executables = "bundle" 73 | end 74 | 75 | install_gemfile <<-G 76 | source "file://#{gem_repo1}" 77 | gem "bundler" 78 | G 79 | 80 | home(".bundler/bin/bundle").should_not exist 81 | end 82 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module CoreExt #:nodoc: 3 | 4 | # A hash with indifferent access and magic predicates. 5 | # 6 | # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true 7 | # 8 | # hash[:foo] #=> 'bar' 9 | # hash['foo'] #=> 'bar' 10 | # hash.foo? #=> true 11 | # 12 | class HashWithIndifferentAccess < ::Hash #:nodoc: 13 | 14 | def initialize(hash={}) 15 | super() 16 | hash.each do |key, value| 17 | self[convert_key(key)] = value 18 | end 19 | end 20 | 21 | def [](key) 22 | super(convert_key(key)) 23 | end 24 | 25 | def []=(key, value) 26 | super(convert_key(key), value) 27 | end 28 | 29 | def delete(key) 30 | super(convert_key(key)) 31 | end 32 | 33 | def values_at(*indices) 34 | indices.collect { |key| self[convert_key(key)] } 35 | end 36 | 37 | def merge(other) 38 | dup.merge!(other) 39 | end 40 | 41 | def merge!(other) 42 | other.each do |key, value| 43 | self[convert_key(key)] = value 44 | end 45 | self 46 | end 47 | 48 | protected 49 | 50 | def convert_key(key) 51 | key.is_a?(Symbol) ? key.to_s : key 52 | end 53 | 54 | # Magic predicates. For instance: 55 | # 56 | # options.force? # => !!options['force'] 57 | # options.shebang # => "/usr/lib/local/ruby" 58 | # options.test_framework?(:rspec) # => options[:test_framework] == :rspec 59 | # 60 | def method_missing(method, *args, &block) 61 | method = method.to_s 62 | if method =~ /^(\w+)\?$/ 63 | if args.empty? 64 | !!self[$1] 65 | else 66 | self[$1] == args.first 67 | end 68 | else 69 | self[method] 70 | end 71 | end 72 | 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/other/show_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle show" do 4 | before :each do 5 | install_gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rails" 8 | G 9 | end 10 | 11 | it "prints path if gem exists in bundle" do 12 | bundle "show rails" 13 | out.should == default_bundle_path('gems', 'rails-2.3.2').to_s 14 | end 15 | 16 | it "complains if gem not in bundle" do 17 | bundle "show missing" 18 | out.should =~ /could not find gem 'missing'/i 19 | end 20 | 21 | describe "while locked" do 22 | before :each do 23 | bundle :lock 24 | end 25 | 26 | it "prints path if gem exists in bundle" do 27 | bundle "show rails" 28 | out.should == default_bundle_path('gems', 'rails-2.3.2').to_s 29 | end 30 | 31 | it "complains if gem not in bundle" do 32 | bundle "show missing" 33 | out.should =~ /could not find gem 'missing'/i 34 | end 35 | end 36 | 37 | end 38 | 39 | describe "bundle show with a git repo" do 40 | before :each do 41 | @git = build_git "foo", "1.0" 42 | end 43 | 44 | it "prints out git info" do 45 | install_gemfile <<-G 46 | gem "foo", :git => "#{lib_path('foo-1.0')}" 47 | G 48 | should_be_installed "foo 1.0" 49 | 50 | bundle :show 51 | out.should include("foo (1.0 #{@git.ref_for('master', 6)}") 52 | end 53 | 54 | it "prints out branch names other than master" do 55 | update_git "foo", :branch => "omg" do |s| 56 | s.write "lib/foo.rb", "FOO = '1.0.omg'" 57 | end 58 | @revision = revision_for(lib_path("foo-1.0"))[0...6] 59 | 60 | install_gemfile <<-G 61 | gem "foo", :git => "#{lib_path('foo-1.0')}", :branch => "omg" 62 | G 63 | should_be_installed "foo 1.0.omg" 64 | 65 | bundle :show 66 | out.should include("foo (1.0 #{@git.ref_for('omg', 6)}") 67 | end 68 | 69 | it "doesn't print the branch when tied to a ref" do 70 | sha = revision_for(lib_path("foo-1.0")) 71 | install_gemfile <<-G 72 | gem "foo", :git => "#{lib_path('foo-1.0')}", :ref => "#{sha}" 73 | G 74 | 75 | bundle :show 76 | out.should include("foo (1.0 #{sha[0..6]})") 77 | end 78 | end -------------------------------------------------------------------------------- /spec/install/gems/resolving_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with gem sources" do 4 | describe "install time dependencies" do 5 | it "installs gems with implicit rake dependencies" do 6 | install_gemfile <<-G 7 | source "file://#{gem_repo1}" 8 | gem "with_implicit_rake_dep" 9 | gem "another_implicit_rake_dep" 10 | gem "rake" 11 | G 12 | 13 | run <<-R 14 | require 'implicit_rake_dep' 15 | require 'another_implicit_rake_dep' 16 | puts IMPLICIT_RAKE_DEP 17 | puts ANOTHER_IMPLICIT_RAKE_DEP 18 | R 19 | out.should == "YES\nYES" 20 | end 21 | 22 | it "installs gems with a dependency with no type" do 23 | build_repo2 24 | 25 | path = "#{gem_repo2}/#{Gem::MARSHAL_SPEC_DIR}/actionpack-2.3.2.gemspec.rz" 26 | spec = Marshal.load(Gem.inflate(File.read(path))) 27 | spec.dependencies.each do |d| 28 | d.instance_variable_set(:@type, :fail) 29 | end 30 | File.open(path, 'w') do |f| 31 | f.write Gem.deflate(Marshal.dump(spec)) 32 | end 33 | 34 | install_gemfile <<-G 35 | source "file://#{gem_repo2}" 36 | gem "actionpack", "2.3.2" 37 | G 38 | 39 | should_be_installed "actionpack 2.3.2", "activesupport 2.3.2" 40 | end 41 | 42 | describe "with crazy rubygem plugin stuff" do 43 | it "installs plugins" do 44 | install_gemfile <<-G 45 | source "file://#{gem_repo1}" 46 | gem "net_b" 47 | G 48 | 49 | should_be_installed "net_b 1.0" 50 | end 51 | 52 | it "installs plugins depended on by other plugins" do 53 | install_gemfile <<-G 54 | source "file://#{gem_repo1}" 55 | gem "net_a" 56 | G 57 | 58 | should_be_installed "net_a 1.0", "net_b 1.0" 59 | end 60 | 61 | it "installs multiple levels of dependencies" do 62 | install_gemfile <<-G 63 | source "file://#{gem_repo1}" 64 | gem "net_c" 65 | gem "net_e" 66 | G 67 | 68 | should_be_installed "net_a 1.0", "net_b 1.0", "net_c 1.0", "net_d 1.0", "net_e 1.0" 69 | end 70 | end 71 | end 72 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/shell.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | require 'thor/shell/color' 3 | 4 | class Thor 5 | module Base 6 | # Returns the shell used in all Thor classes. If you are in a Unix platform 7 | # it will use a colored log, otherwise it will use a basic one without color. 8 | # 9 | def self.shell 10 | @shell ||= if Config::CONFIG['host_os'] =~ /mswin|mingw/ 11 | Thor::Shell::Basic 12 | else 13 | Thor::Shell::Color 14 | end 15 | end 16 | 17 | # Sets the shell used in all Thor classes. 18 | # 19 | def self.shell=(klass) 20 | @shell = klass 21 | end 22 | end 23 | 24 | module Shell 25 | SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table] 26 | 27 | # Add shell to initialize config values. 28 | # 29 | # ==== Configuration 30 | # shell:: An instance of the shell to be used. 31 | # 32 | # ==== Examples 33 | # 34 | # class MyScript < Thor 35 | # argument :first, :type => :numeric 36 | # end 37 | # 38 | # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new 39 | # 40 | def initialize(args=[], options={}, config={}) 41 | super 42 | self.shell = config[:shell] 43 | self.shell.base ||= self if self.shell.respond_to?(:base) 44 | end 45 | 46 | # Holds the shell for the given Thor instance. If no shell is given, 47 | # it gets a default shell from Thor::Base.shell. 48 | def shell 49 | @shell ||= Thor::Base.shell.new 50 | end 51 | 52 | # Sets the shell for this thor class. 53 | def shell=(shell) 54 | @shell = shell 55 | end 56 | 57 | # Common methods that are delegated to the shell. 58 | SHELL_DELEGATED_METHODS.each do |method| 59 | module_eval <<-METHOD, __FILE__, __LINE__ 60 | def #{method}(*args) 61 | shell.#{method}(*args) 62 | end 63 | METHOD 64 | end 65 | 66 | # Yields the given block with padding. 67 | def with_padding 68 | shell.padding += 1 69 | yield 70 | ensure 71 | shell.padding -= 1 72 | end 73 | 74 | protected 75 | 76 | # Allow shell to be shared between invocations. 77 | # 78 | def _shared_configuration #:nodoc: 79 | super.merge!(:shell => self.shell) 80 | end 81 | 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Matchers 3 | def have_dep(*args) 4 | simple_matcher "have dependency" do |given, matcher| 5 | dep = Bundler::Dependency.new(*args) 6 | 7 | # given.length == args.length / 2 8 | given.length == 1 && given.all? { |d| d == dep } 9 | end 10 | end 11 | 12 | def have_gem(*args) 13 | simple_matcher "have gem" do |given, matcher| 14 | given.length == args.length && given.all? { |g| args.include?(g.full_name) } 15 | end 16 | end 17 | 18 | def have_rubyopts(*args) 19 | args = args.flatten 20 | args = args.first.split(/\s+/) if args.size == 1 21 | 22 | simple_matcher "have options #{args.join(' ')}" do |actual| 23 | actual = actual.split(/\s+/) if actual.is_a?(String) 24 | args.all? {|arg| actual.include?(arg) } && actual.uniq.size == actual.size 25 | end 26 | end 27 | 28 | def should_be_installed(*names) 29 | opts = names.last.is_a?(Hash) ? names.pop : {} 30 | groups = Array(opts[:groups]) 31 | groups << opts 32 | names.each do |name| 33 | name, version, platform = name.split(/\s+/) 34 | version_const = name == 'bundler' ? 'Bundler::VERSION' : Spec::Builders.constantize(name) 35 | run "require '#{name}.rb'; puts #{version_const}", *groups 36 | actual_version, actual_platform = out.split(/\s+/) 37 | Gem::Version.new(actual_version).should == Gem::Version.new(version) 38 | actual_platform.should == platform 39 | end 40 | end 41 | 42 | alias should_be_available should_be_installed 43 | 44 | def should_not_be_installed(*names) 45 | opts = names.last.is_a?(Hash) ? names.pop : {} 46 | groups = opts[:groups] || [] 47 | names.each do |name| 48 | name, version = name.split(/\s+/) 49 | run <<-R, *groups 50 | begin 51 | require '#{name}' 52 | puts #{Spec::Builders.constantize(name)} 53 | rescue LoadError, NameError 54 | puts "WIN" 55 | end 56 | R 57 | if version.nil? || out == "WIN" 58 | out.should == "WIN" 59 | else 60 | Gem::Version.new(out).should_not == Gem::Version.new(version) 61 | end 62 | end 63 | end 64 | 65 | def should_be_locked 66 | bundled_app("Gemfile.lock").should exist 67 | end 68 | end 69 | end -------------------------------------------------------------------------------- /lib/bundler/dependency.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems/dependency' 2 | require 'bundler/shared_helpers' 3 | 4 | module Bundler 5 | class Dependency < Gem::Dependency 6 | attr_reader :autorequire 7 | attr_reader :groups 8 | attr_reader :platforms 9 | 10 | PLATFORM_MAP = { 11 | :ruby => Gem::Platform::RUBY, 12 | :ruby_18 => Gem::Platform::RUBY, 13 | :ruby_19 => Gem::Platform::RUBY, 14 | :jruby => Gem::Platform::JAVA, 15 | :mswin => Gem::Platform::MSWIN 16 | } 17 | 18 | def initialize(name, version, options = {}, &blk) 19 | super(name, version) 20 | 21 | @autorequire = nil 22 | @groups = Array(options["group"] || :default).map { |g| g.to_sym } 23 | @source = options["source"] 24 | @platforms = Array(options["platforms"]) 25 | @env = options["env"] 26 | 27 | if options.key?('require') 28 | @autorequire = Array(options['require'] || []) 29 | end 30 | end 31 | 32 | def gem_platforms(valid_platforms) 33 | return valid_platforms if @platforms.empty? 34 | 35 | platforms = [] 36 | @platforms.each do |p| 37 | platform = PLATFORM_MAP[p] 38 | next unless valid_platforms.include?(platform) 39 | platforms |= [platform] 40 | end 41 | platforms 42 | end 43 | 44 | def should_include? 45 | current_env? && current_platform? 46 | end 47 | 48 | def current_env? 49 | return true unless @env 50 | if Hash === @env 51 | @env.all? do |key, val| 52 | ENV[key.to_s] && (String === val ? ENV[key.to_s] == val : ENV[key.to_s] =~ val) 53 | end 54 | else 55 | ENV[@env.to_s] 56 | end 57 | end 58 | 59 | def current_platform? 60 | return true if @platforms.empty? 61 | @platforms.any? { |p| send("#{p}?") } 62 | end 63 | 64 | def to_lock 65 | out = " #{name}" 66 | 67 | unless requirement == Gem::Requirement.default 68 | out << " (#{requirement.to_s})" 69 | end 70 | 71 | out << '!' if source 72 | 73 | out << "\n" 74 | end 75 | 76 | private 77 | 78 | def ruby? 79 | !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx") 80 | end 81 | 82 | def ruby_18? 83 | ruby? && RUBY_VERSION < "1.9" 84 | end 85 | 86 | def ruby_19? 87 | ruby? && RUBY_VERSION >= "1.9" 88 | end 89 | 90 | def jruby? 91 | defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby" 92 | end 93 | 94 | def mswin? 95 | # w0t? 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/core_ext/ordered_hash.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module CoreExt #:nodoc: 3 | 4 | if RUBY_VERSION >= '1.9' 5 | class OrderedHash < ::Hash 6 | end 7 | else 8 | # This class is based on the Ruby 1.9 ordered hashes. 9 | # 10 | # It keeps the semantics and most of the efficiency of normal hashes 11 | # while also keeping track of the order in which elements were set. 12 | # 13 | class OrderedHash #:nodoc: 14 | include Enumerable 15 | 16 | Node = Struct.new(:key, :value, :next, :prev) 17 | 18 | def initialize 19 | @hash = {} 20 | end 21 | 22 | def [](key) 23 | @hash[key] && @hash[key].value 24 | end 25 | 26 | def []=(key, value) 27 | if node = @hash[key] 28 | node.value = value 29 | else 30 | node = Node.new(key, value) 31 | 32 | if @first.nil? 33 | @first = @last = node 34 | else 35 | node.prev = @last 36 | @last.next = node 37 | @last = node 38 | end 39 | end 40 | 41 | @hash[key] = node 42 | value 43 | end 44 | 45 | def delete(key) 46 | if node = @hash[key] 47 | prev_node = node.prev 48 | next_node = node.next 49 | 50 | next_node.prev = prev_node if next_node 51 | prev_node.next = next_node if prev_node 52 | 53 | @first = next_node if @first == node 54 | @last = prev_node if @last == node 55 | 56 | value = node.value 57 | end 58 | 59 | @hash.delete(key) 60 | value 61 | end 62 | 63 | def keys 64 | self.map { |k, v| k } 65 | end 66 | 67 | def values 68 | self.map { |k, v| v } 69 | end 70 | 71 | def each 72 | return unless @first 73 | yield [@first.key, @first.value] 74 | node = @first 75 | yield [node.key, node.value] while node = node.next 76 | self 77 | end 78 | 79 | def merge(other) 80 | hash = self.class.new 81 | 82 | self.each do |key, value| 83 | hash[key] = value 84 | end 85 | 86 | other.each do |key, value| 87 | hash[key] = value 88 | end 89 | 90 | hash 91 | end 92 | 93 | def empty? 94 | @hash.empty? 95 | end 96 | end 97 | end 98 | 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /spec/install/gems/platform_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install across platforms" do 4 | it "maintains the same lockfile if all gems are compatible across platforms" do 5 | lockfile <<-G 6 | GEM 7 | remote: file:#{gem_repo1}/ 8 | specs: 9 | rack (0.9.1) 10 | 11 | PLATFORMS 12 | #{not_local} 13 | 14 | DEPENDENCIES 15 | rack 16 | G 17 | 18 | install_gemfile <<-G 19 | source "file://#{gem_repo1}" 20 | 21 | gem "rack" 22 | G 23 | 24 | should_be_installed "rack 0.9.1" 25 | end 26 | 27 | it "pulls in the correct platform specific gem" do 28 | lockfile <<-G 29 | GEM 30 | remote: file:#{gem_repo1} 31 | specs: 32 | platform_specific (1.0) 33 | platform_specific (1.0-java) 34 | platform_specific (1.0-x86-mswin32) 35 | 36 | PLATFORMS 37 | ruby 38 | 39 | DEPENDENCIES 40 | platform_specific 41 | G 42 | 43 | install_gemfile <<-G 44 | Gem.platforms = [Gem::Platform::RUBY, Gem::Platform.new('#{java}')] 45 | source "file://#{gem_repo1}" 46 | 47 | gem "platform_specific" 48 | G 49 | 50 | should_be_installed "platform_specific 1.0 JAVA" 51 | end 52 | 53 | it "works with gems that have different dependencies" do 54 | install_gemfile <<-G 55 | Gem.platforms = [Gem::Platform::RUBY, Gem::Platform.new('#{java}')] 56 | source "file://#{gem_repo1}" 57 | 58 | gem "nokogiri" 59 | G 60 | 61 | should_be_installed "nokogiri 1.4.2 JAVA", "weakling 0.0.3", :platform => "java" 62 | 63 | simulate_new_machine 64 | 65 | install_gemfile <<-G 66 | Gem.platforms = [Gem::Platform::RUBY] 67 | source "file://#{gem_repo1}" 68 | 69 | gem "nokogiri" 70 | G 71 | 72 | should_be_installed "nokogiri 1.4.2" 73 | should_not_be_installed "weakling" 74 | end 75 | end 76 | 77 | # TODO: Don't make the tests hardcoded to a platform 78 | describe "bundle install with platform conditionals" do 79 | it "installs gems tagged w/ the current platform" do 80 | install_gemfile <<-G 81 | source "file://#{gem_repo1}" 82 | 83 | platforms :#{local_tag} do 84 | gem "nokogiri" 85 | end 86 | G 87 | 88 | should_be_installed "nokogiri 1.4.2" 89 | end 90 | 91 | it "doesn't install gems tagged w/ a different platform" do 92 | install_gemfile <<-G 93 | source "file://#{gem_repo1}" 94 | 95 | platforms :#{not_local_tag} do 96 | gem "nokogiri" 97 | end 98 | G 99 | 100 | should_not_be_installed "nokogiri" 101 | end 102 | end -------------------------------------------------------------------------------- /lib/bundler/runtime.rb: -------------------------------------------------------------------------------- 1 | require "digest/sha1" 2 | 3 | module Bundler 4 | class Runtime < Environment 5 | include SharedHelpers 6 | 7 | def initialize(*) 8 | super 9 | lock 10 | end 11 | 12 | def setup(*groups) 13 | # Has to happen first 14 | clean_load_path 15 | 16 | specs = groups.any? ? @definition.specs_for(groups) : requested_specs 17 | 18 | cripple_rubygems(specs) 19 | 20 | # Activate the specs 21 | specs.each do |spec| 22 | unless spec.loaded_from 23 | raise GemNotFound, "#{spec.full_name} is missing. Run `bundle` to get it." 24 | end 25 | 26 | Gem.loaded_specs[spec.name] = spec 27 | spec.load_paths.each do |path| 28 | $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path) 29 | end 30 | end 31 | self 32 | end 33 | 34 | def require(*groups) 35 | groups.map! { |g| g.to_sym } 36 | groups = [:default] if groups.empty? 37 | 38 | @definition.dependencies.each do |dep| 39 | # Skip the dependency if it is not in any of the requested 40 | # groups 41 | next unless (dep.groups & groups).any? 42 | 43 | begin 44 | # Loop through all the specified autorequires for the 45 | # dependency. If there are none, use the dependency's name 46 | # as the autorequire. 47 | Array(dep.autorequire || dep.name).each do |file| 48 | Kernel.require file 49 | end 50 | rescue LoadError 51 | # Only let a LoadError through if the autorequire was explicitly 52 | # specified by the user. 53 | raise if dep.autorequire 54 | end 55 | end 56 | end 57 | 58 | def dependencies_for(*groups) 59 | if groups.empty? 60 | dependencies 61 | else 62 | dependencies.select { |d| (groups & d.groups).any? } 63 | end 64 | end 65 | 66 | alias gems specs 67 | 68 | def cache 69 | FileUtils.mkdir_p(cache_path) 70 | 71 | Bundler.ui.info "Updating .gem files in vendor/cache" 72 | specs.each do |spec| 73 | spec.source.cache(spec) if spec.source.respond_to?(:cache) 74 | end 75 | end 76 | 77 | def prune_cache 78 | FileUtils.mkdir_p(cache_path) 79 | 80 | Bundler.ui.info "Removing outdated .gem files from vendor/cache" 81 | Pathname.glob(cache_path.join("*.gem").to_s).each do |gem_path| 82 | cached_spec = Gem::Format.from_file_by_path(gem_path).spec 83 | next unless Gem::Platform.match(cached_spec.platform) 84 | unless specs.any?{|s| s.full_name == cached_spec.full_name } 85 | Bundler.ui.info " * #{File.basename(gem_path)}" 86 | gem_path.rmtree 87 | end 88 | end 89 | end 90 | 91 | private 92 | 93 | def cache_path 94 | root.join("vendor/cache") 95 | end 96 | 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/other/console_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle console" do 4 | before :each do 5 | install_gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rack" 8 | gem "activesupport", :group => :test 9 | gem "rack_middleware", :group => :development 10 | G 11 | end 12 | 13 | it "starts IRB with the default group loaded" do 14 | bundle "console" do |input| 15 | input.puts("puts RACK") 16 | input.puts("exit") 17 | end 18 | out.should include("0.9.1") 19 | end 20 | 21 | it "doesn't load any other groups" do 22 | bundle "console" do |input| 23 | input.puts("puts ACTIVESUPPORT") 24 | input.puts("exit") 25 | end 26 | out.should include("NameError") 27 | end 28 | 29 | describe "when given a group" do 30 | it "loads the given group" do 31 | bundle "console test" do |input| 32 | input.puts("puts ACTIVESUPPORT") 33 | input.puts("exit") 34 | end 35 | out.should include("2.3.5") 36 | end 37 | 38 | it "loads the default group" do 39 | bundle "console test" do |input| 40 | input.puts("puts RACK") 41 | input.puts("exit") 42 | end 43 | out.should include("0.9.1") 44 | end 45 | 46 | it "doesn't load other groups" do 47 | bundle "console test" do |input| 48 | input.puts("puts RACK_MIDDLEWARE") 49 | input.puts("exit") 50 | end 51 | out.should include("NameError") 52 | end 53 | end 54 | 55 | describe "when locked" do 56 | before :each do 57 | bundle :lock 58 | end 59 | 60 | it "starts IRB with the default group loaded" do 61 | bundle :console do |input| 62 | input.puts("puts RACK") 63 | input.puts("exit") 64 | end 65 | out.should include("0.9.1") 66 | end 67 | 68 | it "doesn't load any other groups" do 69 | bundle :console do |input| 70 | input.puts("puts ACTIVESUPPORT") 71 | input.puts("exit") 72 | end 73 | out.should include("NameError") 74 | end 75 | 76 | describe "and given a group" do 77 | it "loads the given group" do 78 | bundle "console test" do |input| 79 | input.puts("puts ACTIVESUPPORT") 80 | input.puts("exit") 81 | end 82 | out.should include("2.3.5") 83 | end 84 | 85 | it "loads the default group" do 86 | bundle "console test" do |input| 87 | input.puts("puts RACK") 88 | input.puts("exit") 89 | end 90 | out.should include("0.9.1") 91 | end 92 | 93 | it "doesn't load other groups" do 94 | bundle "console test" do |input| 95 | input.puts("puts RACK_MIDDLEWARE") 96 | input.puts("exit") 97 | end 98 | out.should include("NameError") 99 | end 100 | end 101 | end 102 | end -------------------------------------------------------------------------------- /lib/bundler/index.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | class Index 3 | def self.build 4 | i = new 5 | yield i 6 | i 7 | end 8 | 9 | def initialize 10 | @cache = {} 11 | @specs = Hash.new { |h,k| h[k] = [] } 12 | end 13 | 14 | def initialize_copy(o) 15 | super 16 | @cache = {} 17 | @specs = Hash.new { |h,k| h[k] = [] } 18 | merge!(o) 19 | end 20 | 21 | def empty? 22 | each { return false } 23 | true 24 | end 25 | 26 | def search(query) 27 | case query 28 | when Gem::Specification, RemoteSpecification, LazySpecification then search_by_spec(query) 29 | when String then @specs[query] 30 | else search_by_dependency(query) 31 | end 32 | end 33 | 34 | def search_for_all_platforms(dependency) 35 | specs = @specs[dependency.name] 36 | 37 | wants_prerelease = dependency.requirement.prerelease? 38 | only_prerelease = specs.all? {|spec| spec.version.prerelease? } 39 | found = specs.select { |spec| dependency =~ spec } 40 | 41 | unless wants_prerelease || only_prerelease 42 | found.reject! { |spec| spec.version.prerelease? } 43 | end 44 | 45 | found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] } 46 | end 47 | 48 | def sources 49 | @specs.values.map do |specs| 50 | specs.map{|s| s.source.class } 51 | end.flatten.uniq 52 | end 53 | 54 | alias [] search 55 | 56 | def <<(spec) 57 | arr = @specs[spec.name] 58 | 59 | arr.delete_if do |s| 60 | s.version == spec.version && s.platform == spec.platform 61 | end 62 | 63 | arr << spec 64 | spec 65 | end 66 | 67 | def each(&blk) 68 | @specs.values.each do |specs| 69 | specs.each(&blk) 70 | end 71 | end 72 | 73 | def use(other) 74 | return unless other 75 | other.each do |s| 76 | next if search_by_spec(s).any? 77 | @specs[s.name] << s 78 | end 79 | self 80 | end 81 | 82 | private 83 | 84 | def search_by_spec(spec) 85 | @specs[spec.name].select do |s| 86 | s.version == spec.version && Gem::Platform.new(s.platform) == Gem::Platform.new(spec.platform) 87 | end 88 | end 89 | 90 | def search_by_dependency(dependency) 91 | @cache[dependency.hash] ||= begin 92 | specs = @specs[dependency.name] 93 | 94 | wants_prerelease = dependency.requirement.prerelease? 95 | only_prerelease = specs.all? {|spec| spec.version.prerelease? } 96 | found = specs.select { |spec| dependency =~ spec && Gem::Platform.match(spec.platform) } 97 | 98 | unless wants_prerelease || only_prerelease 99 | found.reject! { |spec| spec.version.prerelease? } 100 | end 101 | 102 | found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] } 103 | end 104 | end 105 | end 106 | end -------------------------------------------------------------------------------- /spec/runtime/load_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Bundler.load" do 4 | before :each do 5 | system_gems "rack-1.0.0" 6 | # clear memoized method results 7 | # TODO: Don't reset internal ivars 8 | Bundler.instance_eval do 9 | @load = nil 10 | @runtime = nil 11 | @definition = nil 12 | end 13 | end 14 | 15 | it "provides a list of the env dependencies" do 16 | gemfile <<-G 17 | source "file://#{gem_repo1}" 18 | gem "rack" 19 | G 20 | 21 | env = Bundler.load 22 | env.dependencies.should have_dep("rack", ">= 0") 23 | end 24 | 25 | it "provides a list of the resolved gems" do 26 | gemfile <<-G 27 | source "file://#{gem_repo1}" 28 | gem "rack" 29 | G 30 | 31 | env = Bundler.load 32 | env.gems.should have_gem("rack-1.0.0") 33 | end 34 | 35 | it "raises an exception if the default gemfile is not found" do 36 | lambda { 37 | Bundler.load 38 | }.should raise_error(Bundler::GemfileNotFound, /could not locate gemfile/i) 39 | end 40 | 41 | it "raises an exception if a specified gemfile is not found" do 42 | lambda { 43 | ENV['BUNDLE_GEMFILE'] = "omg.rb" 44 | Bundler.load 45 | }.should raise_error(Bundler::GemfileNotFound, /omg\.rb/) 46 | end 47 | 48 | describe "when called twice" do 49 | it "doesn't try to load the runtime twice" do 50 | system_gems "rack-1.0.0", "activesupport-2.3.5" 51 | gemfile <<-G 52 | gem "rack" 53 | gem "activesupport", :group => :test 54 | G 55 | 56 | ruby <<-RUBY 57 | require "bundler" 58 | Bundler.setup :default 59 | Bundler.require :default 60 | puts RACK 61 | begin 62 | require "activesupport" 63 | rescue LoadError 64 | puts "no activesupport" 65 | end 66 | RUBY 67 | 68 | out.split("\n").should == ["1.0.0", "no activesupport"] 69 | end 70 | end 71 | 72 | describe "when locked" do 73 | before :each do 74 | install_gemfile <<-G 75 | source "file://#{gem_repo1}" 76 | gem "activesupport" 77 | G 78 | bundle :lock 79 | end 80 | 81 | # This is obviously not true on 1.9 thanks to the AWEOME! gem prelude :'( 82 | it "does not invoke setup inside env.rb" do 83 | ruby <<-RUBY 84 | require 'bundler' 85 | Bundler.load 86 | puts $LOAD_PATH.grep(/activesupport/i) 87 | RUBY 88 | 89 | out.should == "" 90 | end if RUBY_VERSION < "1.9" 91 | end 92 | 93 | describe "not hurting brittle rubygems" do 94 | it "does not inject #source into the generated YAML of the gem specs" do 95 | system_gems "activerecord-2.3.2", "activesupport-2.3.2" 96 | gemfile <<-G 97 | gem "activerecord" 98 | G 99 | 100 | Bundler.load.specs.each do |spec| 101 | spec.to_yaml.should_not =~ /^\s+source:/ 102 | spec.to_yaml.should_not =~ /^\s+groups:/ 103 | end 104 | end 105 | end 106 | 107 | end 108 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | $:.unshift File.expand_path("../lib", __FILE__) 3 | 4 | require 'rubygems' 5 | require 'rubygems/specification' 6 | require 'bundler' 7 | 8 | def gemspec 9 | @gemspec ||= begin 10 | file = File.expand_path('../bundler.gemspec', __FILE__) 11 | eval(File.read(file), binding, file) 12 | end 13 | end 14 | 15 | begin 16 | require 'spec/rake/spectask' 17 | rescue LoadError 18 | raise 'Run `gem install rspec` to be able to run specs' 19 | else 20 | task :clear_tmp do 21 | FileUtils.rm_rf(File.expand_path("../tmp", __FILE__)) 22 | end 23 | 24 | desc "Run specs" 25 | Spec::Rake::SpecTask.new do |t| 26 | t.spec_files = FileList['spec/**/*_spec.rb'] 27 | t.spec_opts = %w(-fs --color) 28 | t.warning = true 29 | end 30 | task :spec 31 | end 32 | 33 | 34 | # Rubygems 1.3.5, 1.3.6, and HEAD specs 35 | rubyopt = ENV["RUBYOPT"] 36 | %w(master REL_1_3_5 REL_1_3_6).each do |rg| 37 | desc "Run specs with Rubygems #{rg}" 38 | Spec::Rake::SpecTask.new("spec_gems_#{rg}") do |t| 39 | t.spec_files = FileList['spec/**/*_spec.rb'] 40 | t.spec_opts = %w(-fs --color) 41 | t.warning = true 42 | end 43 | 44 | task "rubygems_#{rg}" do 45 | unless File.directory?("tmp/rubygems_#{rg}") 46 | system("git clone git://github.com/jbarnette/rubygems.git tmp/rubygems_#{rg} && cd tmp/rubygems_#{rg} && git reset --hard #{rg}") 47 | end 48 | ENV["RUBYOPT"] = "-I#{File.expand_path("tmp/rubygems_#{rg}/lib")} #{rubyopt}" 49 | end 50 | 51 | task "spec_gems_#{rg}" => "rubygems_#{rg}" 52 | task :ci => "spec_gems_#{rg}" 53 | end 54 | 55 | 56 | # Ruby 1.8.6, 1.8.7, and 1.9.2 specs 57 | task "ensure_rvm" do 58 | raise "RVM is not available" unless File.exist?(File.expand_path("~/.rvm/scripts/rvm")) 59 | end 60 | 61 | %w(1.8.6-p399 1.8.7-p249 1.9.2-head).each do |ruby| 62 | ruby_cmd = File.expand_path("~/.rvm/bin/ruby-#{ruby}") 63 | 64 | desc "Run specs on Ruby #{ruby}" 65 | Spec::Rake::SpecTask.new("spec_ruby_#{ruby}") do |t| 66 | t.spec_files = FileList['spec/**/*_spec.rb'] 67 | t.spec_opts = %w(-fs --color) 68 | t.warning = true 69 | t.ruby_cmd = ruby_cmd 70 | end 71 | 72 | task "ensure_ruby_#{ruby}" do 73 | raise "Could not find Ruby #{ruby} at #{ruby_cmd}" unless File.exist?(ruby_cmd) 74 | end 75 | 76 | task "ensure_ruby_#{ruby}" => "ensure_rvm" 77 | task "spec_ruby_#{ruby}" => "ensure_ruby_#{ruby}" 78 | task :ci => "spec_ruby_#{ruby}" 79 | end 80 | 81 | begin 82 | require 'rake/gempackagetask' 83 | rescue LoadError 84 | task(:gem) { $stderr.puts '`gem install rake` to package gems' } 85 | else 86 | Rake::GemPackageTask.new(gemspec) do |pkg| 87 | pkg.gem_spec = gemspec 88 | end 89 | task :gem => :gemspec 90 | end 91 | 92 | desc "install the gem locally" 93 | task :install => :package do 94 | sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}} 95 | end 96 | 97 | desc "validate the gemspec" 98 | task :gemspec do 99 | gemspec.validate 100 | end 101 | 102 | task :package => :gemspec 103 | task :default => :spec 104 | -------------------------------------------------------------------------------- /spec/install/gems/env_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with ENV conditionals" do 4 | describe "when just setting an ENV key as a string" do 5 | before :each do 6 | gemfile <<-G 7 | source "file://#{gem_repo1}" 8 | 9 | env "BUNDLER_TEST" do 10 | gem "rack" 11 | end 12 | G 13 | end 14 | 15 | it "excludes the gems when the ENV variable is not set" do 16 | bundle :install 17 | should_not_be_installed "rack" 18 | end 19 | 20 | it "includes the gems when the ENV variable is set" do 21 | ENV['BUNDLER_TEST'] = '1' 22 | bundle :install 23 | should_be_installed "rack 1.0" 24 | end 25 | end 26 | 27 | describe "when just setting an ENV key as a symbol" do 28 | before :each do 29 | gemfile <<-G 30 | source "file://#{gem_repo1}" 31 | 32 | env :BUNDLER_TEST do 33 | gem "rack" 34 | end 35 | G 36 | end 37 | 38 | it "excludes the gems when the ENV variable is not set" do 39 | bundle :install 40 | should_not_be_installed "rack" 41 | end 42 | 43 | it "includes the gems when the ENV variable is set" do 44 | ENV['BUNDLER_TEST'] = '1' 45 | bundle :install 46 | should_be_installed "rack 1.0" 47 | end 48 | end 49 | 50 | describe "when setting a string to match the env" do 51 | before :each do 52 | gemfile <<-G 53 | source "file://#{gem_repo1}" 54 | 55 | env "BUNDLER_TEST" => "foo" do 56 | gem "rack" 57 | end 58 | G 59 | end 60 | 61 | it "excludes the gems when the ENV variable is not set" do 62 | bundle :install 63 | should_not_be_installed "rack" 64 | end 65 | 66 | it "excludes the gems when the ENV variable is set but does not match the condition" do 67 | ENV['BUNDLER_TEST'] = '1' 68 | bundle :install 69 | should_not_be_installed "rack" 70 | end 71 | 72 | it "includes the gems when the ENV variable is set and matches the condition" do 73 | ENV['BUNDLER_TEST'] = 'foo' 74 | bundle :install 75 | should_be_installed "rack 1.0" 76 | end 77 | end 78 | 79 | describe "when setting a regex to match the env" do 80 | before :each do 81 | gemfile <<-G 82 | source "file://#{gem_repo1}" 83 | 84 | env "BUNDLER_TEST" => /foo/ do 85 | gem "rack" 86 | end 87 | G 88 | end 89 | 90 | it "excludes the gems when the ENV variable is not set" do 91 | bundle :install 92 | should_not_be_installed "rack" 93 | end 94 | 95 | it "excludes the gems when the ENV variable is set but does not match the condition" do 96 | ENV['BUNDLER_TEST'] = 'fo' 97 | bundle :install 98 | should_not_be_installed "rack" 99 | end 100 | 101 | it "includes the gems when the ENV variable is set and matches the condition" do 102 | ENV['BUNDLER_TEST'] = 'foobar' 103 | bundle :install 104 | should_be_installed "rack 1.0" 105 | end 106 | end 107 | end -------------------------------------------------------------------------------- /lib/bundler/lockfile_parser.rb: -------------------------------------------------------------------------------- 1 | require "strscan" 2 | 3 | module Bundler 4 | class LockfileParser 5 | attr_reader :sources, :dependencies, :specs, :platforms 6 | 7 | def initialize(lockfile) 8 | @platforms = [] 9 | @sources = [] 10 | @dependencies = [] 11 | @specs = [] 12 | @state = :source 13 | 14 | lockfile.split(/\n+/).each do |line| 15 | if line == "DEPENDENCIES" 16 | @state = :dependency 17 | elsif line == "PLATFORMS" 18 | @state = :platform 19 | else 20 | send("parse_#{@state}", line) 21 | end 22 | end 23 | end 24 | 25 | private 26 | 27 | TYPES = { 28 | "GIT" => Bundler::Source::Git, 29 | "GEM" => Bundler::Source::Rubygems, 30 | "PATH" => Bundler::Source::Path 31 | } 32 | 33 | def parse_source(line) 34 | case line 35 | when "GIT", "GEM", "PATH" 36 | @current_source = nil 37 | @opts, @type = {}, line 38 | when " specs:" 39 | @current_source = TYPES[@type].from_lock(@opts) 40 | @sources << @current_source 41 | when /^ ([a-z]+): (.*)$/i 42 | if @opts[$1] 43 | @opts[$1] = Array(@opts[$1]) 44 | @opts[$1] << $2 45 | else 46 | @opts[$1] = $2 47 | end 48 | else 49 | parse_spec(line) 50 | end 51 | end 52 | 53 | NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?' 54 | 55 | def parse_dependency(line) 56 | if line =~ %r{^ {2}#{NAME_VERSION}(!)?$} 57 | name, version, pinned = $1, $2, $3 58 | 59 | dep = Bundler::Dependency.new(name, version) 60 | 61 | if pinned 62 | dep.source = @specs.find { |s| s.name == dep.name }.source 63 | 64 | # Path sources need to know what the default name / version 65 | # to use in the case that there are no gemspecs present. A fake 66 | # gemspec is created based on the version set on the dependency 67 | # TODO: Use the version from the spec instead of from the dependency 68 | if version =~ /^= (.+)$/ && dep.source.is_a?(Bundler::Source::Path) 69 | dep.source.name = name 70 | dep.source.version = $1 71 | end 72 | end 73 | 74 | @dependencies << dep 75 | end 76 | end 77 | 78 | def parse_spec(line) 79 | if line =~ %r{^ {4}#{NAME_VERSION}$} 80 | name, version = $1, Gem::Version.new($2) 81 | platform = $3 ? Gem::Platform.new($3) : Gem::Platform::RUBY 82 | @current_spec = LazySpecification.new(name, version, platform) 83 | @current_spec.source = @current_source 84 | @specs << @current_spec 85 | elsif line =~ %r{^ {6}#{NAME_VERSION}$} 86 | name, version = $1, $2 87 | version = version.split(',').map { |d| d.strip } if version 88 | dep = Gem::Dependency.new(name, version) 89 | @current_spec.dependencies << dep 90 | end 91 | end 92 | 93 | def parse_platform(line) 94 | if line =~ /^ (.*)$/ 95 | @platforms << Gem::Platform.new($1) 96 | end 97 | end 98 | 99 | end 100 | end -------------------------------------------------------------------------------- /lib/bundler/spec_set.rb: -------------------------------------------------------------------------------- 1 | require 'tsort' 2 | 3 | module Bundler 4 | class SpecSet 5 | include TSort, Enumerable 6 | 7 | def initialize(specs) 8 | @specs = specs.sort_by { |s| s.name } 9 | end 10 | 11 | def each 12 | sorted.each { |s| yield s } 13 | end 14 | 15 | def length 16 | @specs.length 17 | end 18 | 19 | def for(dependencies, skip = [], check = false, match_current_platform = false) 20 | handled, deps, specs = {}, dependencies.dup, [] 21 | 22 | until deps.empty? 23 | dep = deps.shift 24 | next if handled[dep] || skip.include?(dep.name) 25 | 26 | spec = lookup[dep.name].find do |s| 27 | match_current_platform ? 28 | Gem::Platform.match(s.platform) : 29 | s.match_platform(dep.__platform) 30 | end 31 | 32 | handled[dep] = true 33 | 34 | if spec 35 | specs << spec 36 | 37 | spec.dependencies.each do |d| 38 | next if d.type == :development 39 | d = DepProxy.new(d, dep.__platform) unless match_current_platform 40 | deps << d 41 | end 42 | elsif check 43 | return false 44 | end 45 | end 46 | 47 | check ? true : SpecSet.new(specs) 48 | end 49 | 50 | def valid_for?(deps) 51 | self.for(deps, [], true) 52 | end 53 | 54 | def [](key) 55 | key = key.name if key.respond_to?(:name) 56 | lookup[key].reverse 57 | end 58 | 59 | def to_a 60 | sorted.dup 61 | end 62 | 63 | def to_hash 64 | lookup.dup 65 | end 66 | 67 | def materialize(deps, missing_specs = nil) 68 | materialized = self.for(deps, [], false, true).to_a 69 | materialized.map! do |s| 70 | next s unless s.is_a?(LazySpecification) 71 | spec = s.__materialize__ 72 | if missing_specs 73 | missing_specs << s unless spec 74 | else 75 | raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec 76 | end 77 | spec if spec 78 | end 79 | SpecSet.new(materialized.compact) 80 | end 81 | 82 | def names 83 | lookup.keys 84 | end 85 | 86 | def select!(names) 87 | @lookup = nil 88 | @sorted = nil 89 | 90 | @specs.delete_if { |s| !names.include?(s.name) } 91 | self 92 | end 93 | 94 | private 95 | 96 | def sorted 97 | rake = @specs.find { |s| s.name == 'rake' } 98 | @sorted ||= ([rake] + tsort).compact.uniq 99 | end 100 | 101 | def lookup 102 | @lookup ||= begin 103 | lookup = Hash.new { |h,k| h[k] = [] } 104 | specs = @specs.sort_by do |s| 105 | s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s 106 | end 107 | specs.reverse_each do |s| 108 | lookup[s.name] << s 109 | end 110 | lookup 111 | end 112 | end 113 | 114 | def tsort_each_node 115 | @specs.each { |s| yield s } 116 | end 117 | 118 | def tsort_each_child(s) 119 | s.dependencies.sort_by { |d| d.name }.each do |d| 120 | next if d.type == :development 121 | lookup[d.name].each { |s| yield s } 122 | end 123 | end 124 | end 125 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/task.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Task < Struct.new(:name, :description, :long_description, :usage, :options) 3 | FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ 4 | 5 | # A dynamic task that handles method missing scenarios. 6 | class Dynamic < Task 7 | def initialize(name, options=nil) 8 | super(name.to_s, "A dynamically-generated task", name.to_s, name.to_s, options) 9 | end 10 | 11 | def run(instance, args=[]) 12 | if (instance.methods & [name.to_s, name.to_sym]).empty? 13 | super 14 | else 15 | instance.class.handle_no_task_error(name) 16 | end 17 | end 18 | end 19 | 20 | def initialize(name, description, long_description, usage, options=nil) 21 | super(name.to_s, description, long_description, usage, options || {}) 22 | end 23 | 24 | def initialize_copy(other) #:nodoc: 25 | super(other) 26 | self.options = other.options.dup if other.options 27 | end 28 | 29 | # By default, a task invokes a method in the thor class. You can change this 30 | # implementation to create custom tasks. 31 | def run(instance, args=[]) 32 | public_method?(instance) ? 33 | instance.send(name, *args) : instance.class.handle_no_task_error(name) 34 | rescue ArgumentError => e 35 | handle_argument_error?(instance, e, caller) ? 36 | instance.class.handle_argument_error(self, e) : (raise e) 37 | rescue NoMethodError => e 38 | handle_no_method_error?(instance, e, caller) ? 39 | instance.class.handle_no_task_error(name) : (raise e) 40 | end 41 | 42 | # Returns the formatted usage by injecting given required arguments 43 | # and required options into the given usage. 44 | def formatted_usage(klass, namespace=true) 45 | namespace = klass.namespace unless namespace == false 46 | 47 | # Add namespace 48 | formatted = if namespace 49 | "#{namespace.gsub(/^(default|thor:runner:)/,'')}:" 50 | else 51 | "" 52 | end 53 | 54 | # Add usage with required arguments 55 | formatted << if klass && !klass.arguments.empty? 56 | usage.to_s.gsub(/^#{name}/) do |match| 57 | match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ') 58 | end 59 | else 60 | usage.to_s 61 | end 62 | 63 | # Add required options 64 | formatted << " #{required_options}" 65 | 66 | # Strip and go! 67 | formatted.strip 68 | end 69 | 70 | protected 71 | 72 | def not_debugging?(instance) 73 | !(instance.class.respond_to?(:debugging) && instance.class.debugging) 74 | end 75 | 76 | def required_options 77 | @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ") 78 | end 79 | 80 | # Given a target, checks if this class name is not a private/protected method. 81 | def public_method?(instance) #:nodoc: 82 | collection = instance.private_methods + instance.protected_methods 83 | (collection & [name.to_s, name.to_sym]).empty? 84 | end 85 | 86 | def sans_backtrace(backtrace, caller) #:nodoc: 87 | saned = backtrace.reject { |frame| frame =~ FILE_REGEXP } 88 | saned -= caller 89 | end 90 | 91 | def handle_argument_error?(instance, error, caller) 92 | not_debugging?(instance) && error.message =~ /wrong number of arguments/ && 93 | sans_backtrace(error.backtrace, caller).empty? 94 | end 95 | 96 | def handle_no_method_error?(instance, error, caller) 97 | not_debugging?(instance) && 98 | error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ 99 | end 100 | 101 | end 102 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/parser/option.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Option < Argument #:nodoc: 3 | attr_reader :aliases, :group 4 | 5 | VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] 6 | 7 | def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil) 8 | super(name, description, required, type, default, banner) 9 | @aliases = [*aliases].compact 10 | @group = group.to_s.capitalize if group 11 | end 12 | 13 | # This parse quick options given as method_options. It makes several 14 | # assumptions, but you can be more specific using the option method. 15 | # 16 | # parse :foo => "bar" 17 | # #=> Option foo with default value bar 18 | # 19 | # parse [:foo, :baz] => "bar" 20 | # #=> Option foo with default value bar and alias :baz 21 | # 22 | # parse :foo => :required 23 | # #=> Required option foo without default value 24 | # 25 | # parse :foo => 2 26 | # #=> Option foo with default value 2 and type numeric 27 | # 28 | # parse :foo => :numeric 29 | # #=> Option foo without default value and type numeric 30 | # 31 | # parse :foo => true 32 | # #=> Option foo with default value true and type boolean 33 | # 34 | # The valid types are :boolean, :numeric, :hash, :array and :string. If none 35 | # is given a default type is assumed. This default type accepts arguments as 36 | # string (--foo=value) or booleans (just --foo). 37 | # 38 | # By default all options are optional, unless :required is given. 39 | # 40 | def self.parse(key, value) 41 | if key.is_a?(Array) 42 | name, *aliases = key 43 | else 44 | name, aliases = key, [] 45 | end 46 | 47 | name = name.to_s 48 | default = value 49 | 50 | type = case value 51 | when Symbol 52 | default = nil 53 | 54 | if VALID_TYPES.include?(value) 55 | value 56 | elsif required = (value == :required) 57 | :string 58 | end 59 | when TrueClass, FalseClass 60 | :boolean 61 | when Numeric 62 | :numeric 63 | when Hash, Array, String 64 | value.class.name.downcase.to_sym 65 | end 66 | 67 | self.new(name.to_s, nil, required, type, default, nil, nil, aliases) 68 | end 69 | 70 | def switch_name 71 | @switch_name ||= dasherized? ? name : dasherize(name) 72 | end 73 | 74 | def human_name 75 | @human_name ||= dasherized? ? undasherize(name) : name 76 | end 77 | 78 | def usage(padding=0) 79 | sample = if banner && !banner.to_s.empty? 80 | "#{switch_name}=#{banner}" 81 | else 82 | switch_name 83 | end 84 | 85 | sample = "[#{sample}]" unless required? 86 | 87 | if aliases.empty? 88 | (" " * padding) << sample 89 | else 90 | "#{aliases.join(', ')}, #{sample}" 91 | end 92 | end 93 | 94 | # Allow some type predicates as: boolean?, string? and etc. 95 | # 96 | def method_missing(method, *args, &block) 97 | given = method.to_s.sub(/\?$/, '').to_sym 98 | if valid_type?(given) 99 | self.type == given 100 | else 101 | super 102 | end 103 | end 104 | 105 | protected 106 | 107 | def validate! 108 | raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? 109 | end 110 | 111 | def valid_type?(type) 112 | VALID_TYPES.include?(type.to_sym) 113 | end 114 | 115 | def dasherized? 116 | name.index('-') == 0 117 | end 118 | 119 | def undasherize(str) 120 | str.sub(/^-{1,2}/, '') 121 | end 122 | 123 | def dasherize(str) 124 | (str.length > 1 ? "--" : "-") + str.gsub('_', '-') 125 | end 126 | 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/bundler/graph.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | class Graph 3 | 4 | USER_OPTIONS = {:style => 'filled, bold', :fillcolor => '#cccccc'}.freeze 5 | 6 | def initialize(env) 7 | @env = env 8 | end 9 | 10 | def nodes 11 | populate 12 | @nodes 13 | end 14 | 15 | def groups 16 | populate 17 | @groups 18 | end 19 | 20 | def viz(output_file, show_gem_versions = false, show_dependency_requirements = false) 21 | require 'graphviz' 22 | populate 23 | 24 | graph_viz = GraphViz::new('Gemfile', {:concentrate => true, :dpi => 65 } ) 25 | graph_viz.edge[:fontname] = graph_viz.node[:fontname] = 'Arial, Helvetica, SansSerif' 26 | graph_viz.edge[:fontsize] = 12 27 | 28 | viz_nodes = {} 29 | 30 | # populate all of the nodes 31 | nodes.each do |name, node| 32 | label = name.dup 33 | label << "\n#{node.version}" if show_gem_versions 34 | options = { :label => label } 35 | options.merge!( USER_OPTIONS ) if node.is_user 36 | viz_nodes[name] = graph_viz.add_node( name, options ) 37 | end 38 | 39 | group_nodes = {} 40 | @groups.each do |name, dependencies| 41 | group_nodes[name] = graph_viz.add_node(name.to_s, { :shape => 'folder', :fontsize => 16 }.merge(USER_OPTIONS)) 42 | dependencies.each do |dependency| 43 | options = { :weight => 2 } 44 | if show_dependency_requirements && (dependency.requirement.to_s != ">= 0") 45 | options[:label] = dependency.requirement.to_s 46 | end 47 | graph_viz.add_edge( group_nodes[name], viz_nodes[dependency.name], options ) 48 | end 49 | end 50 | 51 | @groups.keys.select { |group| group != :default }.each do |group| 52 | graph_viz.add_edge( group_nodes[group], group_nodes[:default], { :weight => 2 } ) 53 | end 54 | 55 | viz_nodes.each do |name, node| 56 | nodes[name].dependencies.each do |dependency| 57 | options = { } 58 | if nodes[dependency.name].is_user 59 | options[:constraint] = false 60 | end 61 | if show_dependency_requirements && (dependency.requirement.to_s != ">= 0") 62 | options[:label] = dependency.requirement.to_s 63 | end 64 | 65 | graph_viz.add_edge( node, viz_nodes[dependency.name], options ) 66 | end 67 | end 68 | 69 | graph_viz.output( :png => output_file ) 70 | end 71 | 72 | private 73 | 74 | def populate 75 | return if @populated 76 | 77 | # hash of name => GraphNode 78 | @nodes = {} 79 | @groups = {} 80 | 81 | # Populate @nodes 82 | @env.specs.each { |spec| @nodes[spec.name] = GraphNode.new(spec.name, spec.version) } 83 | 84 | # For gems in Gemfile, add details 85 | @env.current_dependencies.each do |dependency| 86 | node = @nodes[dependency.name] 87 | node.is_user = true 88 | 89 | dependency.groups.each do |group| 90 | if @groups.has_key? group 91 | group = @groups[group] 92 | else 93 | group = @groups[group] = [] 94 | end 95 | group << dependency 96 | end 97 | end 98 | 99 | # walk though a final time and add edges 100 | @env.specs.each do |spec| 101 | 102 | from = @nodes[spec.name] 103 | spec.runtime_dependencies.each do |dependency| 104 | from.dependencies << dependency 105 | end 106 | 107 | end 108 | 109 | @nodes.freeze 110 | @groups.freeze 111 | @populated = true 112 | end 113 | 114 | end 115 | 116 | # Add version info 117 | class GraphNode 118 | 119 | def initialize(name, version) 120 | @name = name 121 | @version = version 122 | @is_user = false 123 | @dependencies = [] 124 | end 125 | 126 | attr_reader :name, :dependencies, :version 127 | attr_accessor :is_user 128 | 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/support/indexes.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Indexes 3 | def dep(name, reqs = nil) 4 | @deps ||= [] 5 | @deps << Bundler::Dependency.new(name, :version => reqs) 6 | end 7 | 8 | def platform(*args) 9 | @platforms ||= [] 10 | @platforms.concat args.map { |p| Gem::Platform.new(p) } 11 | end 12 | 13 | alias platforms platform 14 | 15 | def resolve(deps) 16 | Bundler::Resolver.resolve(deps, @index) 17 | end 18 | 19 | def should_resolve_as(specs) 20 | @platforms ||= ['ruby'] 21 | deps = [] 22 | @deps.each do |d| 23 | @platforms.each do |p| 24 | deps << Bundler::DepProxy.new(d, p) 25 | end 26 | end 27 | 28 | got = resolve(deps).for(deps) 29 | got = got.map { |s| s.full_name }.sort 30 | 31 | got.should == specs.sort 32 | end 33 | 34 | def should_conflict_on(names) 35 | @platforms ||= ['ruby'] 36 | deps = [] 37 | @deps.each do |d| 38 | @platforms.each do |p| 39 | deps << Bundler::DepProxy.new(d, p) 40 | end 41 | end 42 | 43 | begin 44 | got = resolve(deps).for(deps) 45 | flunk "The resolve succeeded with: #{got.map { |s| s.full_name }.sort.inspect}" 46 | rescue Bundler::VersionConflict => e 47 | Array(names).sort.should == e.conflicts.sort 48 | end 49 | end 50 | 51 | def gem(*args, &blk) 52 | build_spec(*args, &blk).first 53 | end 54 | 55 | def an_awesome_index 56 | build_index do 57 | gem "rack", %w(0.8 0.9 0.9.1 0.9.2 1.0 1.1) 58 | gem "rack-mount", %w(0.4 0.5 0.5.1 0.5.2 0.6) 59 | 60 | # --- Rails 61 | versions "1.2.3 2.2.3 2.3.5 3.0.0.beta 3.0.0.beta1" do |version| 62 | gem "activesupport", version 63 | gem "actionpack", version do 64 | dep "activesupport", version 65 | if version >= v('3.0.0.beta') 66 | dep "rack", '~> 1.1' 67 | dep "rack-mount", ">= 0.5" 68 | elsif version > v('2.3') then dep "rack", '~> 1.0.0' 69 | elsif version > v('2.0.0') then dep "rack", '~> 0.9.0' 70 | end 71 | end 72 | gem "activerecord", version do 73 | dep "activesupport", version 74 | dep "arel", ">= 0.2" if version >= v('3.0.0.beta') 75 | end 76 | gem "actionmailer", version do 77 | dep "activesupport", version 78 | dep "actionmailer", version 79 | end 80 | if version < v('3.0.0.beta') 81 | gem "railties", version do 82 | dep "activerecord", version 83 | dep "actionpack", version 84 | dep "actionmailer", version 85 | dep "activesupport", version 86 | end 87 | else 88 | gem "railties", version 89 | gem "rails", version do 90 | dep "activerecord", version 91 | dep "actionpack", version 92 | dep "actionmailer", version 93 | dep "activesupport", version 94 | dep "railties", version 95 | end 96 | end 97 | end 98 | 99 | versions '1.0 1.2 1.2.1 1.2.2 1.3 1.3.0.1 1.3.5 1.4.0 1.4.2 1.4.2.1' do |version| 100 | platforms "ruby java mswin32" do |platform| 101 | next if version == v('1.4.2.1') && platform != pl('x86-mswin32') 102 | next if version == v('1.4.2') && platform == pl('x86-mswin32') 103 | gem "nokogiri", version, platform do 104 | dep "weakling", ">= 0.0.3" if platform =~ 'java' 105 | end 106 | end 107 | end 108 | 109 | versions '0.0.1 0.0.2 0.0.3' do |version| 110 | gem "weakling", version #, pl('java') 111 | end 112 | 113 | # --- Rails related 114 | versions '1.2.3 2.2.3 2.3.5' do |version| 115 | gem "activemerchant", version do 116 | dep "activesupport", ">= #{version}" 117 | end 118 | end 119 | end 120 | end 121 | end 122 | end -------------------------------------------------------------------------------- /spec/install/gems/flex_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle flex_install" do 4 | it "installs the gems as expected" do 5 | install_gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem 'rack' 8 | G 9 | 10 | should_be_installed "rack 1.0.0" 11 | should_be_locked 12 | end 13 | 14 | it "installs even when the lockfile is invalid" do 15 | install_gemfile <<-G 16 | source "file://#{gem_repo1}" 17 | gem 'rack' 18 | G 19 | 20 | should_be_installed "rack 1.0.0" 21 | should_be_locked 22 | 23 | gemfile <<-G 24 | source "file://#{gem_repo1}" 25 | gem 'rack', '1.0' 26 | G 27 | 28 | bundle :install 29 | should_be_installed "rack 1.0.0" 30 | should_be_locked 31 | end 32 | 33 | it "keeps child dependencies at the same version" do 34 | build_repo2 35 | 36 | install_gemfile <<-G 37 | source "file://#{gem_repo2}" 38 | gem "rack-obama" 39 | G 40 | 41 | should_be_installed "rack 1.0.0", "rack-obama 1.0.0" 42 | 43 | update_repo2 44 | install_gemfile <<-G 45 | source "file://#{gem_repo2}" 46 | gem "rack-obama", "1.0" 47 | G 48 | 49 | should_be_installed "rack 1.0.0", "rack-obama 1.0.0" 50 | end 51 | 52 | describe "adding new gems" do 53 | it "installs added gems without updating previously installed gems" do 54 | build_repo2 55 | 56 | install_gemfile <<-G 57 | source "file://#{gem_repo2}" 58 | gem 'rack' 59 | G 60 | 61 | update_repo2 62 | 63 | install_gemfile <<-G 64 | source "file://#{gem_repo2}" 65 | gem 'rack' 66 | gem 'activesupport', '2.3.5' 67 | G 68 | 69 | should_be_installed "rack 1.0.0", 'activesupport 2.3.5' 70 | end 71 | 72 | it "keeps child dependencies pinned" do 73 | build_repo2 74 | 75 | install_gemfile <<-G 76 | source "file://#{gem_repo2}" 77 | gem "rack-obama" 78 | G 79 | 80 | update_repo2 81 | 82 | install_gemfile <<-G 83 | source "file://#{gem_repo2}" 84 | gem "rack-obama" 85 | gem "thin" 86 | G 87 | 88 | should_be_installed "rack 1.0.0", 'rack-obama 1.0', 'thin 1.0' 89 | end 90 | end 91 | 92 | describe "removing gems" do 93 | it "removes gems without changing the versions of remaining gems" do 94 | build_repo2 95 | install_gemfile <<-G 96 | source "file://#{gem_repo2}" 97 | gem 'rack' 98 | gem 'activesupport', '2.3.5' 99 | G 100 | 101 | update_repo2 102 | 103 | install_gemfile <<-G 104 | source "file://#{gem_repo2}" 105 | gem 'rack' 106 | G 107 | 108 | should_be_installed "rack 1.0.0" 109 | should_not_be_installed "activesupport 2.3.5" 110 | 111 | install_gemfile <<-G 112 | source "file://#{gem_repo2}" 113 | gem 'rack' 114 | gem 'activesupport', '2.3.2' 115 | G 116 | 117 | should_be_installed "rack 1.0.0", 'activesupport 2.3.2' 118 | end 119 | 120 | it "removes top level dependencies when removed from the Gemfile while leaving other dependencies intact" do 121 | build_repo2 122 | install_gemfile <<-G 123 | source "file://#{gem_repo2}" 124 | gem 'rack' 125 | gem 'activesupport', '2.3.5' 126 | G 127 | 128 | update_repo2 129 | 130 | install_gemfile <<-G 131 | source "file://#{gem_repo2}" 132 | gem 'rack' 133 | G 134 | 135 | should_not_be_installed "activesupport 2.3.5" 136 | end 137 | 138 | it "removes child dependencies" do 139 | build_repo2 140 | install_gemfile <<-G 141 | source "file://#{gem_repo2}" 142 | gem 'rack-obama' 143 | gem 'activesupport' 144 | G 145 | 146 | should_be_installed "rack 1.0.0", "rack-obama 1.0.0", "activesupport 2.3.5" 147 | 148 | update_repo2 149 | install_gemfile <<-G 150 | source "file://#{gem_repo2}" 151 | gem 'activesupport' 152 | G 153 | 154 | should_be_installed 'activesupport 2.3.5' 155 | should_not_be_installed "rack-obama", "rack" 156 | end 157 | end 158 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/shell/color.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell/basic' 2 | 3 | class Thor 4 | module Shell 5 | # Inherit from Thor::Shell::Basic and add set_color behavior. Check 6 | # Thor::Shell::Basic to see all available methods. 7 | # 8 | class Color < Basic 9 | # Embed in a String to clear all previous ANSI sequences. 10 | CLEAR = "\e[0m" 11 | # The start of an ANSI bold sequence. 12 | BOLD = "\e[1m" 13 | 14 | # Set the terminal's foreground ANSI color to black. 15 | BLACK = "\e[30m" 16 | # Set the terminal's foreground ANSI color to red. 17 | RED = "\e[31m" 18 | # Set the terminal's foreground ANSI color to green. 19 | GREEN = "\e[32m" 20 | # Set the terminal's foreground ANSI color to yellow. 21 | YELLOW = "\e[33m" 22 | # Set the terminal's foreground ANSI color to blue. 23 | BLUE = "\e[34m" 24 | # Set the terminal's foreground ANSI color to magenta. 25 | MAGENTA = "\e[35m" 26 | # Set the terminal's foreground ANSI color to cyan. 27 | CYAN = "\e[36m" 28 | # Set the terminal's foreground ANSI color to white. 29 | WHITE = "\e[37m" 30 | 31 | # Set the terminal's background ANSI color to black. 32 | ON_BLACK = "\e[40m" 33 | # Set the terminal's background ANSI color to red. 34 | ON_RED = "\e[41m" 35 | # Set the terminal's background ANSI color to green. 36 | ON_GREEN = "\e[42m" 37 | # Set the terminal's background ANSI color to yellow. 38 | ON_YELLOW = "\e[43m" 39 | # Set the terminal's background ANSI color to blue. 40 | ON_BLUE = "\e[44m" 41 | # Set the terminal's background ANSI color to magenta. 42 | ON_MAGENTA = "\e[45m" 43 | # Set the terminal's background ANSI color to cyan. 44 | ON_CYAN = "\e[46m" 45 | # Set the terminal's background ANSI color to white. 46 | ON_WHITE = "\e[47m" 47 | 48 | # Set color by using a string or one of the defined constants. If a third 49 | # option is set to true, it also adds bold to the string. This is based 50 | # on Highline implementation and it automatically appends CLEAR to the end 51 | # of the returned String. 52 | # 53 | def set_color(string, color, bold=false) 54 | color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) 55 | bold = bold ? BOLD : "" 56 | "#{bold}#{color}#{string}#{CLEAR}" 57 | end 58 | 59 | protected 60 | 61 | # Overwrite show_diff to show diff with colors if Diff::LCS is 62 | # available. 63 | # 64 | def show_diff(destination, content) #:nodoc: 65 | if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil? 66 | actual = File.binread(destination).to_s.split("\n") 67 | content = content.to_s.split("\n") 68 | 69 | Diff::LCS.sdiff(actual, content).each do |diff| 70 | output_diff_line(diff) 71 | end 72 | else 73 | super 74 | end 75 | end 76 | 77 | def output_diff_line(diff) #:nodoc: 78 | case diff.action 79 | when '-' 80 | say "- #{diff.old_element.chomp}", :red, true 81 | when '+' 82 | say "+ #{diff.new_element.chomp}", :green, true 83 | when '!' 84 | say "- #{diff.old_element.chomp}", :red, true 85 | say "+ #{diff.new_element.chomp}", :green, true 86 | else 87 | say " #{diff.old_element.chomp}", nil, true 88 | end 89 | end 90 | 91 | # Check if Diff::LCS is loaded. If it is, use it to create pretty output 92 | # for diff. 93 | # 94 | def diff_lcs_loaded? #:nodoc: 95 | return true if defined?(Diff::LCS) 96 | return @diff_lcs_loaded unless @diff_lcs_loaded.nil? 97 | 98 | @diff_lcs_loaded = begin 99 | require 'diff/lcs' 100 | true 101 | rescue LoadError 102 | false 103 | end 104 | end 105 | 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/parser/arguments.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Arguments #:nodoc: 3 | NUMERIC = /(\d*\.\d+|\d+)/ 4 | 5 | # Receives an array of args and returns two arrays, one with arguments 6 | # and one with switches. 7 | # 8 | def self.split(args) 9 | arguments = [] 10 | 11 | args.each do |item| 12 | break if item =~ /^-/ 13 | arguments << item 14 | end 15 | 16 | return arguments, args[Range.new(arguments.size, -1)] 17 | end 18 | 19 | def self.parse(*args) 20 | to_parse = args.pop 21 | new(*args).parse(to_parse) 22 | end 23 | 24 | # Takes an array of Thor::Argument objects. 25 | # 26 | def initialize(arguments=[]) 27 | @assigns, @non_assigned_required = {}, [] 28 | @switches = arguments 29 | 30 | arguments.each do |argument| 31 | if argument.default 32 | @assigns[argument.human_name] = argument.default 33 | elsif argument.required? 34 | @non_assigned_required << argument 35 | end 36 | end 37 | end 38 | 39 | def parse(args) 40 | @pile = args.dup 41 | 42 | @switches.each do |argument| 43 | break unless peek 44 | @non_assigned_required.delete(argument) 45 | @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name) 46 | end 47 | 48 | check_requirement! 49 | @assigns 50 | end 51 | 52 | private 53 | 54 | def no_or_skip?(arg) 55 | arg =~ /^--(no|skip)-([-\w]+)$/ 56 | $2 57 | end 58 | 59 | def last? 60 | @pile.empty? 61 | end 62 | 63 | def peek 64 | @pile.first 65 | end 66 | 67 | def shift 68 | @pile.shift 69 | end 70 | 71 | def unshift(arg) 72 | unless arg.kind_of?(Array) 73 | @pile.unshift(arg) 74 | else 75 | @pile = arg + @pile 76 | end 77 | end 78 | 79 | def current_is_value? 80 | peek && peek.to_s !~ /^-/ 81 | end 82 | 83 | # Runs through the argument array getting strings that contains ":" and 84 | # mark it as a hash: 85 | # 86 | # [ "name:string", "age:integer" ] 87 | # 88 | # Becomes: 89 | # 90 | # { "name" => "string", "age" => "integer" } 91 | # 92 | def parse_hash(name) 93 | return shift if peek.is_a?(Hash) 94 | hash = {} 95 | 96 | while current_is_value? && peek.include?(?:) 97 | key, value = shift.split(':') 98 | hash[key] = value 99 | end 100 | hash 101 | end 102 | 103 | # Runs through the argument array getting all strings until no string is 104 | # found or a switch is found. 105 | # 106 | # ["a", "b", "c"] 107 | # 108 | # And returns it as an array: 109 | # 110 | # ["a", "b", "c"] 111 | # 112 | def parse_array(name) 113 | return shift if peek.is_a?(Array) 114 | array = [] 115 | 116 | while current_is_value? 117 | array << shift 118 | end 119 | array 120 | end 121 | 122 | # Check if the peek is numeric format and return a Float or Integer. 123 | # Otherwise raises an error. 124 | # 125 | def parse_numeric(name) 126 | return shift if peek.is_a?(Numeric) 127 | 128 | unless peek =~ NUMERIC && $& == peek 129 | raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}" 130 | end 131 | 132 | $&.index('.') ? shift.to_f : shift.to_i 133 | end 134 | 135 | # Parse string: 136 | # for --string-arg, just return the current value in the pile 137 | # for --no-string-arg, nil 138 | # 139 | def parse_string(name) 140 | if no_or_skip?(name) 141 | nil 142 | else 143 | shift 144 | end 145 | end 146 | 147 | # Raises an error if @non_assigned_required array is not empty. 148 | # 149 | def check_requirement! 150 | unless @non_assigned_required.empty? 151 | names = @non_assigned_required.map do |o| 152 | o.respond_to?(:switch_name) ? o.switch_name : o.human_name 153 | end.join("', '") 154 | 155 | class_name = self.class.name.split('::').last.downcase 156 | raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'" 157 | end 158 | end 159 | 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /spec/install/path_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with explicit source paths" do 4 | it "fetches gems" do 5 | build_lib "foo" 6 | 7 | install_gemfile <<-G 8 | path "#{lib_path('foo-1.0')}" 9 | gem 'foo' 10 | G 11 | 12 | should_be_installed("foo 1.0") 13 | end 14 | 15 | it "supports pinned paths" do 16 | build_lib "foo" 17 | 18 | install_gemfile <<-G 19 | gem 'foo', :path => "#{lib_path('foo-1.0')}" 20 | G 21 | 22 | should_be_installed("foo 1.0") 23 | end 24 | 25 | it "supports relative paths" do 26 | build_lib "foo" 27 | 28 | relative_path = lib_path('foo-1.0').relative_path_from(Pathname.new(Dir.pwd)) 29 | 30 | install_gemfile <<-G 31 | gem 'foo', :path => "#{relative_path}" 32 | G 33 | 34 | should_be_installed("foo 1.0") 35 | end 36 | 37 | it "expands paths" do 38 | build_lib "foo" 39 | 40 | relative_path = lib_path('foo-1.0').relative_path_from(Pathname.new("~").expand_path) 41 | 42 | install_gemfile <<-G 43 | gem 'foo', :path => "~/#{relative_path}" 44 | G 45 | 46 | should_be_installed("foo 1.0") 47 | end 48 | 49 | it "installs dependencies from the path even if a newer gem is available elsewhere" do 50 | system_gems "rack-1.0.0" 51 | 52 | build_lib "rack", "1.0", :path => lib_path('nested/bar') do |s| 53 | s.write "lib/rack.rb", "puts 'WIN OVERRIDE'" 54 | end 55 | 56 | build_lib "foo", :path => lib_path('nested') do |s| 57 | s.add_dependency "rack", "= 1.0" 58 | end 59 | 60 | install_gemfile <<-G 61 | source "file://#{gem_repo1}" 62 | gem "foo", :path => "#{lib_path('nested')}" 63 | G 64 | 65 | run "require 'rack'" 66 | out.should == 'WIN OVERRIDE' 67 | end 68 | 69 | it "works" do 70 | build_gem "foo", "1.0.0", :to_system => true do |s| 71 | s.write "lib/foo.rb", "puts 'FAIL'" 72 | end 73 | 74 | build_lib "omg", "1.0", :path => lib_path("omg") do |s| 75 | s.add_dependency "foo" 76 | end 77 | 78 | build_lib "foo", "1.0.0", :path => lib_path("omg/foo") 79 | 80 | install_gemfile <<-G 81 | gem "omg", :path => "#{lib_path('omg')}" 82 | G 83 | 84 | should_be_installed "foo 1.0" 85 | end 86 | 87 | it "sets up executables" do 88 | pending_jruby_shebang_fix 89 | 90 | build_lib "foo" do |s| 91 | s.executables = "foobar" 92 | end 93 | 94 | install_gemfile <<-G 95 | path "#{lib_path('foo-1.0')}" 96 | gem 'foo' 97 | G 98 | 99 | bundle "exec foobar" 100 | out.should == "1.0" 101 | end 102 | 103 | it "removes the .gem file after installing" do 104 | build_lib "foo" 105 | 106 | install_gemfile <<-G 107 | gem 'foo', :path => "#{lib_path('foo-1.0')}" 108 | G 109 | 110 | lib_path('foo-1.0').join('foo-1.0.gem').should_not exist 111 | end 112 | 113 | describe "block syntax" do 114 | it "pulls all gems from a path block" do 115 | build_lib "omg" 116 | build_lib "hi2u" 117 | 118 | install_gemfile <<-G 119 | path "#{lib_path}" do 120 | gem "omg" 121 | gem "hi2u" 122 | end 123 | G 124 | 125 | should_be_installed "omg 1.0", "hi2u 1.0" 126 | end 127 | end 128 | 129 | describe "when locked" do 130 | it "keeps source pinning" do 131 | build_lib "foo", "1.0", :path => lib_path('foo') 132 | build_lib "omg", "1.0", :path => lib_path('omg') 133 | build_lib "foo", "1.0", :path => lib_path('omg/foo') do |s| 134 | s.write "lib/foo.rb", "puts 'FAIL'" 135 | end 136 | 137 | install_gemfile <<-G 138 | gem "foo", :path => "#{lib_path('foo')}" 139 | gem "omg", :path => "#{lib_path('omg')}" 140 | G 141 | 142 | bundle :lock 143 | 144 | should_be_installed "foo 1.0" 145 | end 146 | 147 | it "works when the path does not have a gemspec" do 148 | build_lib "foo", :gemspec => false 149 | 150 | gemfile <<-G 151 | gem "foo", "1.0", :path => "#{lib_path('foo-1.0')}" 152 | G 153 | 154 | should_be_installed "foo 1.0" 155 | 156 | bundle :lock 157 | 158 | should_be_installed "foo 1.0" 159 | end 160 | end 161 | 162 | it "installs executable stubs" do 163 | build_lib "foo" do |s| 164 | s.executables = ['foo'] 165 | end 166 | 167 | install_gemfile <<-G 168 | gem "foo", :path => "#{lib_path('foo-1.0')}" 169 | G 170 | 171 | bundle "exec foo" 172 | out.should == "1.0" 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /spec/cache/gems_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle cache" do 4 | 5 | describe "when there are only gemsources" do 6 | before :each do 7 | gemfile <<-G 8 | gem 'rack' 9 | G 10 | 11 | system_gems "rack-1.0.0" 12 | bundle :cache 13 | end 14 | 15 | it "copies the .gem file to vendor/cache" do 16 | bundled_app("vendor/cache/rack-1.0.0.gem").should exist 17 | end 18 | 19 | it "uses the cache as a source when installing gems" do 20 | system_gems [] 21 | bundle :install 22 | 23 | should_be_installed("rack 1.0.0") 24 | end 25 | 26 | it "does not reinstall gems from the cache if they exist on the system" do 27 | build_gem "rack", "1.0.0", :path => bundled_app('vendor/cache') do |s| 28 | s.write "lib/rack.rb", "RACK = 'FAIL'" 29 | end 30 | 31 | install_gemfile <<-G 32 | gem "rack" 33 | G 34 | 35 | should_be_installed("rack 1.0.0") 36 | end 37 | 38 | it "does not reinstall gems from the cache if they exist in the bundle" do 39 | system_gems [] 40 | install_gemfile <<-G 41 | gem "rack" 42 | G 43 | 44 | build_gem "rack", "1.0.0", :path => bundled_app('vendor/cache') do |s| 45 | s.write "lib/rack.rb", "RACK = 'FAIL'" 46 | end 47 | 48 | bundle :install 49 | should_be_installed("rack 1.0.0") 50 | end 51 | end 52 | 53 | describe "when there are also git sources" do 54 | it "still works" do 55 | build_git "foo" 56 | system_gems "rack-1.0.0" 57 | 58 | install_gemfile <<-G 59 | git "#{lib_path("foo-1.0")}" 60 | gem 'rack' 61 | gem 'foo' 62 | G 63 | 64 | bundle :cache 65 | 66 | system_gems [] 67 | bundle :install 68 | 69 | should_be_installed("rack 1.0.0", "foo 1.0") 70 | end 71 | end 72 | 73 | describe "when previously cached" do 74 | before :each do 75 | build_repo2 76 | install_gemfile <<-G 77 | source "file://#{gem_repo2}" 78 | gem "rack" 79 | gem "actionpack" 80 | G 81 | bundle :cache 82 | cached_gem("rack-1.0.0").should exist 83 | cached_gem("actionpack-2.3.2").should exist 84 | cached_gem("activesupport-2.3.2").should exist 85 | end 86 | 87 | it "re-caches during install" do 88 | cached_gem("rack-1.0.0").rmtree 89 | bundle :install 90 | out.should include("Updating .gem files in vendor/cache") 91 | cached_gem("rack-1.0.0").should exist 92 | end 93 | 94 | it "adds and removes when gems are updated" do 95 | update_repo2 96 | bundle 'update' 97 | cached_gem("rack-1.2").should exist 98 | cached_gem("rack-1.0.0").should_not exist 99 | end 100 | 101 | it "adds new gems and dependencies" do 102 | install_gemfile <<-G 103 | source "file://#{gem_repo2}" 104 | gem "rails" 105 | G 106 | cached_gem("rails-2.3.2").should exist 107 | cached_gem("activerecord-2.3.2").should exist 108 | end 109 | 110 | it "removes .gems for removed gems and dependencies" do 111 | install_gemfile <<-G 112 | source "file://#{gem_repo2}" 113 | gem "rack" 114 | G 115 | cached_gem("rack-1.0.0").should exist 116 | cached_gem("actionpack-2.3.2").should_not exist 117 | cached_gem("activesupport-2.3.2").should_not exist 118 | end 119 | 120 | it "doesn't remove gems that are for another platform" do 121 | install_gemfile <<-G 122 | Gem.platforms = [Gem::Platform.new('#{java}')] 123 | source "file://#{gem_repo1}" 124 | gem "platform_specific" 125 | G 126 | bundle :cache 127 | cached_gem("platform_specific-1.0-java").should exist 128 | 129 | simulate_new_machine 130 | install_gemfile <<-G 131 | source "file://#{gem_repo1}" 132 | gem "platform_specific" 133 | G 134 | cached_gem("platform_specific-1.0-#{Gem::Platform.local}").should exist 135 | cached_gem("platform_specific-1.0-java").should exist 136 | end 137 | 138 | it "doesn't remove gems with mismatched :rubygems_version or :date" do 139 | cached_gem("rack-1.0.0").rmtree 140 | build_gem "rack", "1.0.0", 141 | :path => bundled_app('vendor/cache'), 142 | :rubygems_version => "1.3.2" 143 | simulate_new_machine 144 | 145 | bundle :install 146 | cached_gem("rack-1.0.0").should exist 147 | end 148 | 149 | it "handles directories and non .gem files in the cache" do 150 | bundled_app("vendor/cache/foo").mkdir 151 | File.open(bundled_app("vendor/cache/bar"), 'w'){|f| f.write("not a gem") } 152 | bundle :cache 153 | end 154 | end 155 | 156 | end -------------------------------------------------------------------------------- /lib/bundler/shared_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'rubygems' 3 | Gem.source_index # ensure Rubygems is fully loaded in Ruby 1.9 4 | 5 | module Gem 6 | class Dependency 7 | if !instance_methods.map { |m| m.to_s }.include?("requirement") 8 | def requirement 9 | version_requirements 10 | end 11 | end 12 | end 13 | end 14 | 15 | module Bundler 16 | module SharedHelpers 17 | attr_accessor :gem_loaded 18 | 19 | def default_gemfile 20 | gemfile = find_gemfile 21 | gemfile or raise GemfileNotFound, "Could not locate Gemfile" 22 | Pathname.new(gemfile) 23 | end 24 | 25 | def in_bundle? 26 | find_gemfile 27 | end 28 | 29 | private 30 | 31 | def find_gemfile 32 | return ENV['BUNDLE_GEMFILE'] if ENV['BUNDLE_GEMFILE'] 33 | 34 | previous = nil 35 | current = File.expand_path(Dir.pwd) 36 | 37 | until !File.directory?(current) || current == previous 38 | filename = File.join(current, 'Gemfile') 39 | return filename if File.file?(filename) 40 | current, previous = File.expand_path("..", current), current 41 | end 42 | end 43 | 44 | def clean_load_path 45 | # handle 1.9 where system gems are always on the load path 46 | if defined?(::Gem) 47 | me = File.expand_path("../../", __FILE__) 48 | $LOAD_PATH.reject! do |p| 49 | next if File.expand_path(p).include?(me) 50 | p != File.dirname(__FILE__) && 51 | Gem.path.any? { |gp| p.include?(gp) } 52 | end 53 | $LOAD_PATH.uniq! 54 | end 55 | end 56 | 57 | def reverse_rubygems_kernel_mixin 58 | # Disable rubygems' gem activation system 59 | ::Kernel.class_eval do 60 | if private_method_defined?(:gem_original_require) 61 | alias rubygems_require require 62 | alias require gem_original_require 63 | end 64 | 65 | undef gem 66 | end 67 | end 68 | 69 | def cripple_rubygems(specs) 70 | reverse_rubygems_kernel_mixin 71 | 72 | executables = specs.map { |s| s.executables }.flatten 73 | Gem.source_index # ensure RubyGems is fully loaded 74 | 75 | ::Kernel.class_eval do 76 | private 77 | def gem(*) ; end 78 | end 79 | 80 | ::Kernel.send(:define_method, :gem) do |dep, *reqs| 81 | if executables.include? File.basename(caller.first.split(':').first) 82 | return 83 | end 84 | opts = reqs.last.is_a?(Hash) ? reqs.pop : {} 85 | 86 | unless dep.respond_to?(:name) && dep.respond_to?(:requirement) 87 | dep = Gem::Dependency.new(dep, reqs) 88 | end 89 | 90 | spec = specs.find { |s| s.name == dep.name } 91 | 92 | if spec.nil? 93 | e = Gem::LoadError.new "#{dep.name} is not part of the bundle. Add it to Gemfile." 94 | e.name = dep.name 95 | e.version_requirement = dep.requirement 96 | raise e 97 | elsif dep !~ spec 98 | e = Gem::LoadError.new "can't activate #{dep}, already activated #{spec.full_name}. " \ 99 | "Make sure all dependencies are added to Gemfile." 100 | e.name = dep.name 101 | e.version_requirement = dep.requirement 102 | raise e 103 | end 104 | 105 | true 106 | end 107 | 108 | # === Following hacks are to improve on the generated bin wrappers === 109 | 110 | # Yeah, talk about a hack 111 | source_index_class = (class << Gem::SourceIndex ; self ; end) 112 | source_index_class.send(:define_method, :from_gems_in) do |*args| 113 | source_index = Gem::SourceIndex.new 114 | source_index.spec_dirs = *args 115 | source_index.add_specs(*specs) 116 | source_index 117 | end 118 | 119 | # OMG more hacks 120 | gem_class = (class << Gem ; self ; end) 121 | gem_class.send(:define_method, :bin_path) do |name, *args| 122 | exec_name, *reqs = args 123 | 124 | spec = nil 125 | 126 | if exec_name 127 | spec = specs.find { |s| s.executables.include?(exec_name) } 128 | spec or raise Gem::Exception, "can't find executable #{exec_name}" 129 | else 130 | spec = specs.find { |s| s.name == name } 131 | exec_name = spec.default_executable or raise Gem::Exception, "no default executable for #{spec.full_name}" 132 | end 133 | 134 | gem_bin = File.join(spec.full_gem_path, spec.bindir, exec_name) 135 | gem_from_path_bin = File.join(File.dirname(spec.loaded_from), spec.bindir, exec_name) 136 | File.exist?(gem_bin) ? gem_bin : gem_from_path_bin 137 | end 138 | 139 | Gem.clear_paths 140 | end 141 | 142 | extend self 143 | end 144 | end -------------------------------------------------------------------------------- /spec/runtime/environment_rb_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "environment.rb file" do 4 | 5 | describe "with git gems that don't have gemspecs" do 6 | before :each do 7 | build_git "no-gemspec", :gemspec => false 8 | 9 | install_gemfile <<-G 10 | gem "no-gemspec", "1.0", :git => "#{lib_path('no-gemspec-1.0')}" 11 | G 12 | 13 | bundle :lock 14 | end 15 | 16 | it "loads the library via a virtual spec" do 17 | run <<-R, :lite_runtime => true 18 | require 'no-gemspec' 19 | puts NOGEMSPEC 20 | R 21 | 22 | out.should == "1.0" 23 | end 24 | end 25 | 26 | describe "with bundled and system gems" do 27 | before :each do 28 | system_gems "rack-1.0.0" 29 | 30 | install_gemfile <<-G 31 | source "file://#{gem_repo1}" 32 | 33 | gem "activesupport", "2.3.5" 34 | G 35 | 36 | bundle :lock 37 | end 38 | 39 | it "does not pull in system gems" do 40 | run <<-R, :lite_runtime => true 41 | require 'rubygems' 42 | 43 | begin; 44 | require 'rack' 45 | rescue LoadError 46 | puts 'WIN' 47 | end 48 | R 49 | 50 | out.should == "WIN" 51 | end 52 | 53 | it "provides a gem method" do 54 | run <<-R, :lite_runtime => true 55 | gem 'activesupport' 56 | require 'activesupport' 57 | puts ACTIVESUPPORT 58 | R 59 | 60 | out.should == "2.3.5" 61 | end 62 | 63 | it "raises an exception if gem is used to invoke a system gem not in the bundle" do 64 | run <<-R, :lite_runtime => true 65 | begin 66 | gem 'rack' 67 | rescue LoadError => e 68 | puts e.message 69 | end 70 | R 71 | 72 | out.should == "rack is not part of the bundle. Add it to Gemfile." 73 | end 74 | 75 | it "sets GEM_HOME appropriately" do 76 | run "puts ENV['GEM_HOME']", :lite_runtime => true 77 | out.should == default_bundle_path.to_s 78 | end 79 | end 80 | 81 | describe "with system gems in the bundle" do 82 | before :each do 83 | system_gems "rack-1.0.0" 84 | 85 | install_gemfile <<-G 86 | source "file://#{gem_repo1}" 87 | gem "rack", "1.0.0" 88 | gem "activesupport", "2.3.5" 89 | G 90 | 91 | bundle :lock 92 | end 93 | 94 | it "sets GEM_PATH appropriately" do 95 | run "puts Gem.path", :lite_runtime => true 96 | paths = out.split("\n") 97 | paths.should include(system_gem_path.to_s) 98 | paths.should include(default_bundle_path.to_s) 99 | end 100 | end 101 | 102 | describe "with a gemspec that requires other files" do 103 | before :each do 104 | build_git "bar", :gemspec => false do |s| 105 | s.write "lib/bar/version.rb", %{BAR_VERSION = '1.0'} 106 | s.write "bar.gemspec", <<-G 107 | lib = File.expand_path('../lib/', __FILE__) 108 | $:.unshift lib unless $:.include?(lib) 109 | require 'bar/version' 110 | 111 | Gem::Specification.new do |s| 112 | s.name = 'bar' 113 | s.version = BAR_VERSION 114 | s.summary = 'Bar' 115 | s.files = Dir["lib/**/*.rb"] 116 | end 117 | G 118 | end 119 | 120 | gemfile <<-G 121 | gem "bar", :git => "#{lib_path('bar-1.0')}" 122 | G 123 | end 124 | 125 | it "evals each gemspec in the context of its parent directory" do 126 | bundle :install 127 | bundle :lock 128 | run "require 'bar'; puts BAR", :lite_runtime => true 129 | out.should == "1.0" 130 | end 131 | 132 | it "error intelligently if the gemspec has a LoadError" do 133 | update_git "bar", :gemspec => false do |s| 134 | s.write "bar.gemspec", "require 'foobarbaz'" 135 | end 136 | bundle :install 137 | out.should include("was a LoadError while evaluating bar.gemspec") 138 | out.should include("try to require a relative path") 139 | end 140 | 141 | it "evals each gemspec with a binding from the top level" do 142 | bundle "install" 143 | 144 | ruby <<-RUBY 145 | require 'bundler' 146 | def Bundler.require(path) 147 | raise "LOSE" 148 | end 149 | Bundler.load 150 | RUBY 151 | 152 | err.should be_empty 153 | out.should be_empty 154 | end 155 | end 156 | 157 | describe "when Bundler is bundled" do 158 | it "doesn't blow up" do 159 | install_gemfile <<-G 160 | gem "bundler", :path => "#{File.expand_path("..", lib)}" 161 | G 162 | bundle :lock 163 | 164 | bundle %|exec ruby -e "require 'bundler'; Bundler.setup"| 165 | err.should be_empty 166 | end 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /lib/bundler/rubygems_ext.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | if defined?(Gem::QuickLoader) 4 | # Gem Prelude makes me a sad panda :'( 5 | Gem::QuickLoader.load_full_rubygems_library 6 | end 7 | 8 | require 'rubygems' 9 | require 'rubygems/specification' 10 | 11 | module Bundler 12 | class DepProxy 13 | 14 | attr_reader :required_by, :__platform, :dep 15 | 16 | def initialize(dep, platform) 17 | @dep, @__platform, @required_by = dep, platform, [] 18 | end 19 | 20 | def hash 21 | @hash ||= dep.hash 22 | end 23 | 24 | def ==(o) 25 | dep == o.dep && __platform == o.__platform 26 | end 27 | 28 | alias eql? == 29 | 30 | def type 31 | @dep.type 32 | end 33 | 34 | def to_s 35 | @dep.to_s 36 | end 37 | 38 | private 39 | 40 | def method_missing(*args) 41 | @dep.send(*args) 42 | end 43 | 44 | end 45 | end 46 | 47 | module Gem 48 | @loaded_stacks = Hash.new { |h,k| h[k] = [] } 49 | 50 | module MatchPlatform 51 | def match_platform(p) 52 | Gem::Platform::RUBY == platform or 53 | platform.nil? or p == platform or 54 | Gem::Platform.new(platform).to_generic == p 55 | end 56 | end 57 | 58 | class Specification 59 | attr_accessor :source, :location, :relative_loaded_from 60 | 61 | alias_method :rg_full_gem_path, :full_gem_path 62 | alias_method :rg_loaded_from, :loaded_from 63 | 64 | include MatchPlatform 65 | 66 | def full_gem_path 67 | source.respond_to?(:path) ? 68 | Pathname.new(loaded_from).dirname.expand_path.to_s : 69 | rg_full_gem_path 70 | end 71 | 72 | def loaded_from 73 | relative_loaded_from ? 74 | source.path.join(relative_loaded_from).to_s : 75 | rg_loaded_from 76 | end 77 | 78 | def load_paths 79 | require_paths.map do |require_path| 80 | if require_path.include?(full_gem_path) 81 | require_path 82 | else 83 | File.join(full_gem_path, require_path) 84 | end 85 | end 86 | end 87 | 88 | def groups 89 | @groups ||= [] 90 | end 91 | 92 | def git_version 93 | if @loaded_from && File.exist?(File.join(full_gem_path, ".git")) 94 | sha = Dir.chdir(full_gem_path){ `git rev-parse HEAD`.strip } 95 | " #{sha[0..6]}" 96 | end 97 | end 98 | 99 | def to_gemfile(path = nil) 100 | gemfile = "source :gemcutter\n" 101 | gemfile << dependencies_to_gemfile(nondevelopment_dependencies) 102 | unless development_dependencies.empty? 103 | gemfile << "\n" 104 | gemfile << dependencies_to_gemfile(development_dependencies, :development) 105 | end 106 | gemfile 107 | end 108 | 109 | def nondevelopment_dependencies 110 | dependencies - development_dependencies 111 | end 112 | 113 | def add_bundler_dependencies(*groups) 114 | groups = [:default] if groups.empty? 115 | Bundler.definition.dependencies.each do |dep| 116 | if dep.groups.include?(:development) 117 | self.add_development_dependency(dep.name, dep.requirement.to_s) 118 | elsif (dep.groups & groups).any? 119 | self.add_dependency(dep.name, dep.requirement.to_s) 120 | end 121 | end 122 | end 123 | 124 | private 125 | 126 | def dependencies_to_gemfile(dependencies, group = nil) 127 | gemfile = '' 128 | if dependencies.any? 129 | gemfile << "group :#{group} do\n" if group 130 | dependencies.each do |dependency| 131 | gemfile << ' ' if group 132 | gemfile << %|gem "#{dependency.name}"| 133 | req = dependency.requirements_list.first 134 | gemfile << %|, "#{req}"| if req 135 | gemfile << "\n" 136 | end 137 | gemfile << "end\n" if group 138 | end 139 | gemfile 140 | end 141 | 142 | end 143 | 144 | class Dependency 145 | attr_accessor :source, :groups 146 | 147 | alias eql? == 148 | 149 | def to_yaml_properties 150 | instance_variables.reject { |p| ["@source", "@groups"].include?(p.to_s) } 151 | end 152 | 153 | def to_lock 154 | out = " #{name}" 155 | unless requirement == Gem::Requirement.default 156 | out << " (#{requirement.to_s})" 157 | end 158 | out 159 | end 160 | end 161 | 162 | class Platform 163 | JAVA = Gem::Platform.new('java') 164 | MSWIN = Gem::Platform.new('mswin32') 165 | MING = Gem::Platform.new('x86-mingw32') 166 | 167 | GENERIC_CACHE = {} 168 | 169 | class << RUBY 170 | def to_generic ; self ; end 171 | end 172 | 173 | GENERICS = [JAVA, MSWIN, MING, RUBY] 174 | 175 | def hash 176 | @cpu.hash + @os.hash + @version.hash 177 | end 178 | 179 | alias eql? == 180 | 181 | def to_generic 182 | GENERIC_CACHE[self] ||= GENERICS.find { |p| self =~ p } || RUBY 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /spec/runtime/require_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Bundler.require" do 4 | before :each do 5 | build_lib "one", "1.0.0" do |s| 6 | s.write "lib/baz.rb", "puts 'baz'" 7 | s.write "lib/qux.rb", "puts 'qux'" 8 | end 9 | 10 | build_lib "two", "1.0.0" do |s| 11 | s.write "lib/two.rb", "puts 'two'" 12 | s.add_dependency "three", "= 1.0.0" 13 | end 14 | 15 | build_lib "three", "1.0.0" do |s| 16 | s.write "lib/three.rb", "puts 'three'" 17 | s.add_dependency "seven", "= 1.0.0" 18 | end 19 | 20 | build_lib "four", "1.0.0" do |s| 21 | s.write "lib/four.rb", "puts 'four'" 22 | end 23 | 24 | build_lib "five", "1.0.0", :no_default => true do |s| 25 | s.write "lib/mofive.rb", "puts 'five'" 26 | end 27 | 28 | build_lib "six", "1.0.0" do |s| 29 | s.write "lib/six.rb", "puts 'six'" 30 | end 31 | 32 | build_lib "seven", "1.0.0" do |s| 33 | s.write "lib/seven.rb", "puts 'seven'" 34 | end 35 | 36 | gemfile <<-G 37 | path "#{lib_path}" 38 | gem "one", :group => :bar, :require => %w(baz qux) 39 | gem "two" 40 | gem "three", :group => :not 41 | gem "four", :require => false 42 | gem "five" 43 | gem "six", :group => "string" 44 | gem "seven", :group => :not 45 | G 46 | end 47 | 48 | it "requires the gems" do 49 | # default group 50 | run "Bundler.require" 51 | check out.should == "two" 52 | 53 | # specific group 54 | run "Bundler.require(:bar)" 55 | check out.should == "baz\nqux" 56 | 57 | # default and specific group 58 | run "Bundler.require(:default, :bar)" 59 | check out.should == "baz\nqux\ntwo" 60 | 61 | # specific group given as a string 62 | run "Bundler.require('bar')" 63 | check out.should == "baz\nqux" 64 | 65 | # specific group declared as a string 66 | run "Bundler.require(:string)" 67 | check out.should == "six" 68 | 69 | # required in resolver order instead of gemfile order 70 | run("Bundler.require(:not)") 71 | out.split("\n").sort.should == ['seven', 'three'] 72 | end 73 | 74 | it "allows requiring gems with non standard names explicitly" do 75 | run "Bundler.require ; require 'mofive'" 76 | out.should == "two\nfive" 77 | end 78 | 79 | it "raises an exception if a require is specified but the file does not exist" do 80 | gemfile <<-G 81 | path "#{lib_path}" 82 | gem "two", :require => 'fail' 83 | G 84 | 85 | run <<-R 86 | begin 87 | Bundler.require 88 | rescue LoadError => e 89 | puts e.message 90 | end 91 | R 92 | out.should == 'no such file to load -- fail' 93 | end 94 | 95 | describe "using bundle exec" do 96 | it "requires the locked gems" do 97 | bundle :lock 98 | 99 | bundle "exec ruby -e 'Bundler.require'" 100 | check out.should == "two" 101 | 102 | bundle "exec ruby -e 'Bundler.require(:bar)'" 103 | check out.should == "baz\nqux" 104 | 105 | bundle "exec ruby -e 'Bundler.require(:default, :bar)'" 106 | out.should == "baz\nqux\ntwo" 107 | end 108 | end 109 | 110 | describe "order" do 111 | before(:each) do 112 | build_lib "one", "1.0.0" do |s| 113 | s.write "lib/one.rb", <<-ONE 114 | if defined?(Two) 115 | Two.two 116 | else 117 | puts "two_not_loaded" 118 | end 119 | puts 'one' 120 | ONE 121 | end 122 | 123 | build_lib "two", "1.0.0" do |s| 124 | s.write "lib/two.rb", <<-TWO 125 | module Two 126 | def self.two 127 | puts 'module_two' 128 | end 129 | end 130 | puts 'two' 131 | TWO 132 | end 133 | end 134 | 135 | it "works when the gems are in the Gemfile in the correct order" do 136 | gemfile <<-G 137 | path "#{lib_path}" 138 | gem "two" 139 | gem "one" 140 | G 141 | 142 | run "Bundler.require" 143 | check out.should == "two\nmodule_two\none" 144 | end 145 | 146 | it "fails when the gems are in the Gemfile in the wrong order" do 147 | gemfile <<-G 148 | path "#{lib_path}" 149 | gem "one" 150 | gem "two" 151 | G 152 | 153 | run "Bundler.require" 154 | check out.should == "two_not_loaded\none\ntwo" 155 | end 156 | 157 | describe "when locked" do 158 | it "works when the gems are in the Gemfile in the correct order" do 159 | gemfile <<-G 160 | path "#{lib_path}" 161 | gem "two" 162 | gem "one" 163 | G 164 | 165 | bundle :lock 166 | 167 | run "Bundler.require" 168 | check out.should == "two\nmodule_two\none" 169 | end 170 | 171 | it "fails when the gems are in the Gemfile in the wrong order" do 172 | gemfile <<-G 173 | path "#{lib_path}" 174 | gem "one" 175 | gem "two" 176 | G 177 | 178 | bundle :lock 179 | 180 | run "Bundler.require" 181 | check out.should == "two_not_loaded\none\ntwo" 182 | end 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/parser/options.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | # This is a modified version of Daniel Berger's Getopt::Long class, licensed 3 | # under Ruby's license. 4 | # 5 | class Options < Arguments #:nodoc: 6 | LONG_RE = /^(--\w+(?:-\w+)*)$/ 7 | SHORT_RE = /^(-[a-z])$/i 8 | EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i 9 | SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args 10 | SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i 11 | 12 | # Receives a hash and makes it switches. 13 | def self.to_switches(options) 14 | options.map do |key, value| 15 | case value 16 | when true 17 | "--#{key}" 18 | when Array 19 | "--#{key} #{value.map{ |v| v.inspect }.join(' ')}" 20 | when Hash 21 | "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}" 22 | when nil, false 23 | "" 24 | else 25 | "--#{key} #{value.inspect}" 26 | end 27 | end.join(" ") 28 | end 29 | 30 | # Takes a hash of Thor::Option and a hash with defaults. 31 | def initialize(hash_options={}, defaults={}) 32 | options = hash_options.values 33 | super(options) 34 | 35 | # Add defaults 36 | defaults.each do |key, value| 37 | @assigns[key.to_s] = value 38 | @non_assigned_required.delete(hash_options[key]) 39 | end 40 | 41 | @shorts, @switches, @unknown = {}, {}, [] 42 | 43 | options.each do |option| 44 | @switches[option.switch_name] = option 45 | 46 | option.aliases.each do |short| 47 | @shorts[short.to_s] ||= option.switch_name 48 | end 49 | end 50 | end 51 | 52 | def parse(args) 53 | @pile = args.dup 54 | 55 | while peek 56 | if current_is_switch? 57 | case shift 58 | when SHORT_SQ_RE 59 | unshift($1.split('').map { |f| "-#{f}" }) 60 | next 61 | when EQ_RE, SHORT_NUM 62 | unshift($2) 63 | switch = $1 64 | when LONG_RE, SHORT_RE 65 | switch = $1 66 | end 67 | 68 | switch = normalize_switch(switch) 69 | option = switch_option(switch) 70 | @assigns[option.human_name] = parse_peek(switch, option) 71 | elsif current_is_switch_formatted? 72 | @unknown << shift 73 | else 74 | shift 75 | end 76 | end 77 | 78 | check_requirement! 79 | 80 | assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) 81 | assigns.freeze 82 | assigns 83 | end 84 | 85 | def check_unknown! 86 | raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty? 87 | end 88 | 89 | protected 90 | 91 | # Returns true if the current value in peek is a registered switch. 92 | # 93 | def current_is_switch? 94 | case peek 95 | when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM 96 | switch?($1) 97 | when SHORT_SQ_RE 98 | $1.split('').any? { |f| switch?("-#{f}") } 99 | end 100 | end 101 | 102 | def switch_formatted?(arg) 103 | case arg 104 | when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE 105 | true 106 | else 107 | false 108 | end 109 | end 110 | 111 | def current_is_switch_formatted? 112 | switch_formatted? peek 113 | end 114 | 115 | def switch?(arg) 116 | switch_option(arg) || @shorts.key?(arg) 117 | end 118 | 119 | def switch_option(arg) 120 | if match = no_or_skip?(arg) 121 | @switches[arg] || @switches["--#{match}"] 122 | else 123 | @switches[arg] 124 | end 125 | end 126 | 127 | # Check if the given argument is actually a shortcut. 128 | # 129 | def normalize_switch(arg) 130 | @shorts.key?(arg) ? @shorts[arg] : arg 131 | end 132 | 133 | # Parse boolean values which can be given as --foo=true, --foo or --no-foo. 134 | # 135 | def parse_boolean(switch) 136 | if current_is_value? 137 | ["true", "TRUE", "t", "T", true].include?(shift) 138 | else 139 | @switches.key?(switch) || !no_or_skip?(switch) 140 | end 141 | end 142 | 143 | # Parse the value at the peek analyzing if it requires an input or not. 144 | # 145 | def parse_peek(switch, option) 146 | if current_is_switch_formatted? || last? 147 | if option.boolean? 148 | # No problem for boolean types 149 | elsif no_or_skip?(switch) 150 | return nil # User set value to nil 151 | elsif option.string? && !option.required? 152 | # Return the default if there is one, else the human name 153 | return option.default || option.human_name 154 | else 155 | raise MalformattedArgumentError, "No value provided for option '#{switch}'" 156 | end 157 | end 158 | 159 | @non_assigned_required.delete(option) 160 | send(:"parse_#{option.type}", switch) 161 | end 162 | 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /spec/other/check_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle check" do 4 | it "returns success when the Gemfile is satisfied" do 5 | install_gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rails" 8 | G 9 | 10 | bundle :check, :exit_status => true 11 | check @exitstatus.should == 0 12 | out.should == "The Gemfile's dependencies are satisfied" 13 | end 14 | 15 | it "shows what is missing with the current Gemfile if it is not satisfied" do 16 | gemfile <<-G 17 | source "file://#{gem_repo1}" 18 | gem "rails" 19 | G 20 | 21 | bundle :check, :exit_status => true 22 | check @exitstatus.should > 0 23 | out.should include("rails (>= 0, runtime)") 24 | end 25 | 26 | it "shows missing child dependencies" do 27 | system_gems "missing_dep-1.0" 28 | gemfile <<-G 29 | gem "missing_dep" 30 | G 31 | 32 | bundle :check 33 | out.should include('not_here (>= 0, runtime) not found in any of the sources') 34 | out.should include('required by missing_dep (>= 0, runtime)') 35 | end 36 | 37 | it "provides debug information when there is a resolving problem" do 38 | install_gemfile <<-G 39 | source "file://#{gem_repo1}" 40 | gem 'rails' 41 | G 42 | install_gemfile <<-G 43 | source "file://#{gem_repo1}" 44 | gem 'rails_fail' 45 | G 46 | 47 | gemfile <<-G 48 | source "file://#{gem_repo1}" 49 | gem "rails" 50 | gem "rails_fail" 51 | G 52 | 53 | bundle :check 54 | out.should include('Conflict on: "activesupport"') 55 | end 56 | 57 | it "remembers --without option from install" do 58 | gemfile <<-G 59 | source "file://#{gem_repo1}" 60 | group :foo do 61 | gem "rack" 62 | end 63 | G 64 | 65 | bundle "install --without foo" 66 | bundle "check", :exit_status => true 67 | check @exitstatus.should == 0 68 | out.should include("The Gemfile's dependencies are satisfied") 69 | end 70 | 71 | it "ensures that gems are actually installed and not just cached" do 72 | gemfile <<-G 73 | source "file://#{gem_repo1}" 74 | gem "rack", :group => :foo 75 | G 76 | 77 | bundle "install --without foo" 78 | 79 | gemfile <<-G 80 | source "file://#{gem_repo1}" 81 | gem "rack" 82 | G 83 | 84 | bundle "check", :exit_status => true 85 | out.should include("* rack (1.0.0)") 86 | @exitstatus.should == 1 87 | end 88 | 89 | it "ignores missing gems restricted to other platforms" do 90 | system_gems "rack-1.0.0" 91 | 92 | gemfile <<-G 93 | source "file://#{gem_repo1}" 94 | gem "rack" 95 | platforms :#{not_local_tag} do 96 | gem "activesupport" 97 | end 98 | G 99 | 100 | lockfile <<-G 101 | GEM 102 | remote: file:#{gem_repo1}/ 103 | specs: 104 | activesupport (2.3.5) 105 | rack (1.0.0) 106 | 107 | PLATFORMS 108 | #{local} 109 | #{not_local} 110 | 111 | DEPENDENCIES 112 | rack 113 | activesupport 114 | G 115 | 116 | bundle :check 117 | out.should == "The Gemfile's dependencies are satisfied" 118 | end 119 | 120 | it "works with env conditionals" do 121 | system_gems "rack-1.0.0" 122 | 123 | gemfile <<-G 124 | source "file://#{gem_repo1}" 125 | gem "rack" 126 | env :NOT_GOING_TO_BE_SET do 127 | gem "activesupport" 128 | end 129 | G 130 | 131 | lockfile <<-G 132 | GEM 133 | remote: file:#{gem_repo1}/ 134 | specs: 135 | activesupport (2.3.5) 136 | rack (1.0.0) 137 | 138 | PLATFORMS 139 | #{local} 140 | #{not_local} 141 | 142 | DEPENDENCIES 143 | rack 144 | activesupport 145 | G 146 | 147 | bundle :check 148 | out.should == "The Gemfile's dependencies are satisfied" 149 | end 150 | 151 | it "outputs an error when the default Gemfile is not found" do 152 | bundle :check, :exit_status => true 153 | check @exitstatus.should == 10 154 | out.should include("Could not locate Gemfile") 155 | end 156 | 157 | it "should not crash when called multiple times on a new machine" do 158 | gemfile <<-G 159 | gem 'rails', '3.0.0.beta3' 160 | gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git' 161 | G 162 | 163 | simulate_new_machine 164 | bundle "check" 165 | last_out = out 166 | 3.times do |i| 167 | bundle :check 168 | out.should == last_out 169 | err.should be_empty 170 | end 171 | end 172 | 173 | describe "when locked" do 174 | before :each do 175 | system_gems "rack-1.0.0" 176 | install_gemfile <<-G 177 | source "file://#{gem_repo1}" 178 | gem "rack", "1.0" 179 | G 180 | end 181 | 182 | it "returns success when the Gemfile is satisfied" do 183 | bundle :install 184 | bundle :check, :exit_status => true 185 | check @exitstatus.should == 0 186 | out.should == "The Gemfile's dependencies are satisfied" 187 | end 188 | 189 | it "shows what is missing with the current Gemfile if it is not satisfied" do 190 | simulate_new_machine 191 | bundle :check 192 | out.should match(/The following gems are missing/) 193 | out.should include("* rack (1.0") 194 | end 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /spec/other/exec_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle exec" do 4 | before :each do 5 | system_gems "rack-1.0.0", "rack-0.9.1" 6 | end 7 | 8 | it "activates the correct gem" do 9 | gemfile <<-G 10 | gem "rack", "0.9.1" 11 | G 12 | 13 | bundle "exec rackup" 14 | out.should == "0.9.1" 15 | end 16 | 17 | it "works when the bins are in ~/.bundle" do 18 | install_gemfile <<-G 19 | gem "rack" 20 | G 21 | 22 | bundle "exec rackup" 23 | out.should == "1.0.0" 24 | end 25 | 26 | it "works when running from a random directory" do 27 | install_gemfile <<-G 28 | gem "rack" 29 | G 30 | 31 | bundle "exec 'cd #{tmp('gems')} && rackup'" 32 | 33 | out.should == "1.0.0" 34 | end 35 | 36 | it "handles different versions in different bundles" do 37 | build_repo2 do 38 | build_gem "rack_two", "1.0.0" do |s| 39 | s.executables = "rackup" 40 | end 41 | end 42 | 43 | install_gemfile <<-G 44 | source "file://#{gem_repo1}" 45 | gem "rack", "0.9.1" 46 | G 47 | 48 | Dir.chdir bundled_app2 do 49 | install_gemfile bundled_app2('Gemfile'), <<-G 50 | source "file://#{gem_repo2}" 51 | gem "rack_two", "1.0.0" 52 | G 53 | end 54 | 55 | bundle "exec rackup" 56 | 57 | check out.should == "0.9.1" 58 | 59 | Dir.chdir bundled_app2 do 60 | bundle "exec rackup" 61 | out.should == "1.0.0" 62 | end 63 | end 64 | 65 | it "handles gems installed with --without" do 66 | install_gemfile <<-G, :without => :middleware 67 | source "file://#{gem_repo1}" 68 | gem "rack" # rack 0.9.1 and 1.0 exist 69 | 70 | group :middleware do 71 | gem "rack_middleware" # rack_middleware depends on rack 0.9.1 72 | end 73 | G 74 | 75 | bundle "exec rackup" 76 | 77 | check out.should == "0.9.1" 78 | should_not_be_installed "rack_middleware 1.0" 79 | end 80 | 81 | it "should not duplicate already exec'ed RUBYOPT or PATH" do 82 | install_gemfile <<-G 83 | gem "rack" 84 | G 85 | 86 | rubyopt = "-I#{bundler_path} -rbundler/setup" 87 | 88 | bundle "exec 'echo $RUBYOPT'" 89 | out.should have_rubyopts(rubyopt) 90 | 91 | bundle "exec 'echo $RUBYOPT'", :env => {"RUBYOPT" => rubyopt} 92 | out.should have_rubyopts(rubyopt) 93 | end 94 | 95 | it "errors nicely when the argument doesn't exist" do 96 | install_gemfile <<-G 97 | gem "rack" 98 | G 99 | 100 | bundle "exec foobarbaz" 101 | out.should include("bundler: command not found: foobarbaz") 102 | out.should include("Install missing gem binaries with `bundle install`") 103 | end 104 | 105 | it "errors nicely when the argument is not executable" do 106 | install_gemfile <<-G 107 | gem "rack" 108 | G 109 | 110 | bundle "exec touch foo" 111 | bundle "exec ./foo" 112 | out.should include("bundler: not executable: ./foo") 113 | end 114 | 115 | describe "with gem binaries" do 116 | describe "run from a random directory" do 117 | before(:each) do 118 | install_gemfile <<-G 119 | gem "rack" 120 | G 121 | end 122 | 123 | it "works when unlocked" do 124 | bundle "exec 'cd #{tmp('gems')} && rackup'" 125 | out.should == "1.0.0" 126 | end 127 | 128 | it "works when locked" do 129 | bundle "lock" 130 | should_be_locked 131 | bundle "exec 'cd #{tmp('gems')} && rackup'" 132 | out.should == "1.0.0" 133 | end 134 | end 135 | 136 | describe "from gems bundled via :path" do 137 | before(:each) do 138 | build_lib "fizz", :path => home("fizz") do |s| 139 | s.executables = "fizz" 140 | end 141 | 142 | install_gemfile <<-G 143 | gem "fizz", :path => "#{File.expand_path(home("fizz"))}" 144 | G 145 | end 146 | 147 | it "works when unlocked" do 148 | bundle "exec fizz" 149 | out.should == "1.0" 150 | end 151 | 152 | it "works when locked" do 153 | bundle "lock" 154 | should_be_locked 155 | 156 | bundle "exec fizz" 157 | out.should == "1.0" 158 | end 159 | end 160 | 161 | describe "from gems bundled via :git" do 162 | before(:each) do 163 | build_git "fizz_git" do |s| 164 | s.executables = "fizz_git" 165 | end 166 | 167 | install_gemfile <<-G 168 | gem "fizz_git", :git => "#{lib_path('fizz_git-1.0')}" 169 | G 170 | end 171 | 172 | it "works when unlocked" do 173 | bundle "exec fizz_git" 174 | out.should == "1.0" 175 | end 176 | 177 | it "works when locked" do 178 | bundle "lock" 179 | should_be_locked 180 | bundle "exec fizz_git" 181 | out.should == "1.0" 182 | end 183 | end 184 | 185 | describe "from gems bundled via :git with no gemspec" do 186 | before(:each) do 187 | build_git "fizz_no_gemspec", :gemspec => false do |s| 188 | s.executables = "fizz_no_gemspec" 189 | end 190 | 191 | install_gemfile <<-G 192 | gem "fizz_no_gemspec", "1.0", :git => "#{lib_path('fizz_no_gemspec-1.0')}" 193 | G 194 | end 195 | 196 | it "works when unlocked" do 197 | bundle "exec fizz_no_gemspec" 198 | out.should == "1.0" 199 | end 200 | 201 | it "works when locked" do 202 | bundle "lock" 203 | should_be_locked 204 | bundle "exec fizz_no_gemspec" 205 | out.should == "1.0" 206 | end 207 | end 208 | 209 | end 210 | end -------------------------------------------------------------------------------- /spec/runtime/setup_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Bundler.setup" do 4 | it "uses BUNDLE_GEMFILE to locate the gemfile if present" do 5 | gemfile <<-G 6 | source "file://#{gem_repo1}" 7 | gem "rack" 8 | G 9 | 10 | gemfile bundled_app('4realz'), <<-G 11 | source "file://#{gem_repo1}" 12 | gem "activesupport", "2.3.5" 13 | G 14 | 15 | ENV['BUNDLE_GEMFILE'] = bundled_app('4realz').to_s 16 | bundle :install 17 | 18 | should_be_installed "activesupport 2.3.5" 19 | end 20 | 21 | it "prioritizes gems in BUNDLE_PATH over gems in GEM_HOME" do 22 | ENV['BUNDLE_PATH'] = bundled_app('.bundle').to_s 23 | install_gemfile <<-G 24 | source "file://#{gem_repo1}" 25 | gem "rack", "1.0.0" 26 | G 27 | 28 | build_gem "rack", "1.0", :to_system => true do |s| 29 | s.write "lib/rack.rb", "RACK = 'FAIL'" 30 | end 31 | 32 | should_be_installed "rack 1.0.0" 33 | end 34 | 35 | describe "cripping rubygems" do 36 | describe "by replacing #gem" do 37 | before :each do 38 | install_gemfile <<-G 39 | source "file://#{gem_repo1}" 40 | gem "rack", "0.9.1" 41 | G 42 | end 43 | 44 | it "replaces #gem but raises when the gem is missing" do 45 | run <<-R 46 | begin 47 | gem "activesupport" 48 | puts "FAIL" 49 | rescue LoadError 50 | puts "WIN" 51 | end 52 | R 53 | 54 | out.should == "WIN" 55 | end 56 | 57 | it "replaces #gem but raises when the version is wrong" do 58 | run <<-R 59 | begin 60 | gem "rack", "1.0.0" 61 | puts "FAIL" 62 | rescue LoadError 63 | puts "WIN" 64 | end 65 | R 66 | 67 | out.should == "WIN" 68 | end 69 | end 70 | 71 | describe "by hiding system gems" do 72 | before :each do 73 | system_gems "activesupport-2.3.5" 74 | install_gemfile <<-G 75 | source "file://#{gem_repo1}" 76 | gem "yard" 77 | G 78 | end 79 | 80 | it "removes system gems from Gem.source_index" do 81 | run "require 'yard'" 82 | out.should == "yard-1.0" 83 | end 84 | end 85 | end 86 | 87 | describe "with paths" do 88 | it "activates the gems in the path source" do 89 | system_gems "rack-1.0.0" 90 | 91 | build_lib "rack", "1.0.0" do |s| 92 | s.write "lib/rack.rb", "puts 'WIN'" 93 | end 94 | 95 | gemfile <<-G 96 | path "#{lib_path('rack-1.0.0')}" 97 | source "file://#{gem_repo1}" 98 | gem "rack" 99 | G 100 | 101 | run "require 'rack'" 102 | out.should == "WIN" 103 | end 104 | end 105 | 106 | describe "with git" do 107 | it "provides a useful exception when the git repo is not checked out yet" do 108 | build_git "rack", "1.0.0" 109 | 110 | gemfile <<-G 111 | gem "foo", :git => "#{lib_path('rack-1.0.0')}" 112 | G 113 | 114 | run "1", :expect_err => true 115 | err.should include("#{lib_path('rack-1.0.0')} (at master) is not checked out. Please run `bundle install`") 116 | end 117 | end 118 | 119 | describe "when excluding groups" do 120 | it "doesn't change the resolve if --without is used" do 121 | install_gemfile <<-G, :without => :rails 122 | source "file://#{gem_repo1}" 123 | gem "activesupport" 124 | 125 | group :rails do 126 | gem "rails", "2.3.2" 127 | end 128 | G 129 | 130 | install_gems "activesupport-2.3.5" 131 | 132 | should_be_installed "activesupport 2.3.2", :groups => :default 133 | end 134 | 135 | it "remembers --without and does not bail on bare Bundler.setup" do 136 | install_gemfile <<-G, :without => :rails 137 | source "file://#{gem_repo1}" 138 | gem "activesupport" 139 | 140 | group :rails do 141 | gem "rails", "2.3.2" 142 | end 143 | G 144 | 145 | install_gems "activesupport-2.3.5" 146 | 147 | should_be_installed "activesupport 2.3.2" 148 | end 149 | 150 | it "remembers --without and does not include groups passed to Bundler.setup" do 151 | install_gemfile <<-G, :without => :rails 152 | source "file://#{gem_repo1}" 153 | gem "activesupport" 154 | 155 | group :rack do 156 | gem "rack" 157 | end 158 | 159 | group :rails do 160 | gem "rails", "2.3.2" 161 | end 162 | G 163 | 164 | should_not_be_installed "activesupport 2.3.2", :groups => :rack 165 | should_be_installed "rack 1.0.0", :groups => :rack 166 | end 167 | end 168 | 169 | # Rubygems returns loaded_from as a string 170 | it "has loaded_from as a string on all specs" do 171 | build_git "foo" 172 | build_git "no-gemspec", :gemspec => false 173 | 174 | install_gemfile <<-G 175 | source "file://#{gem_repo1}" 176 | gem "rack" 177 | gem "foo", :git => "#{lib_path('foo-1.0')}" 178 | gem "no-gemspec", "1.0", :git => "#{lib_path('no-gemspec-1.0')}" 179 | G 180 | 181 | run <<-R 182 | Gem.loaded_specs.each do |n, s| 183 | puts "FAIL" unless String === s.loaded_from 184 | end 185 | R 186 | 187 | out.should be_empty 188 | end 189 | 190 | it "ignores empty gem paths" do 191 | install_gemfile <<-G 192 | source "file://#{gem_repo1}" 193 | gem "rack" 194 | G 195 | 196 | ENV["GEM_HOME"] = "" 197 | bundle %{exec ruby -e "require 'set'"} 198 | 199 | err.should be_empty 200 | end 201 | 202 | end 203 | -------------------------------------------------------------------------------- /spec/support/helpers.rb: -------------------------------------------------------------------------------- 1 | module Spec 2 | module Helpers 3 | def reset! 4 | Dir["#{tmp}/{gems/*,*}"].each do |dir| 5 | next if %(base remote1 gems rubygems_1_3_5 rubygems_1_3_6 rubygems_master).include?(File.basename(dir)) 6 | if File.owned?(dir) 7 | FileUtils.rm_rf(dir) 8 | else 9 | `sudo rm -rf #{dir}` 10 | end 11 | end 12 | FileUtils.mkdir_p(tmp) 13 | FileUtils.mkdir_p(home) 14 | Gem.sources = ["file://#{gem_repo1}/"] 15 | Gem.configuration.write 16 | end 17 | 18 | attr_reader :out, :err 19 | 20 | def in_app_root(&blk) 21 | Dir.chdir(bundled_app, &blk) 22 | end 23 | 24 | def in_app_root2(&blk) 25 | Dir.chdir(bundled_app2, &blk) 26 | end 27 | 28 | def run(cmd, *args) 29 | opts = args.last.is_a?(Hash) ? args.pop : {} 30 | expect_err = opts.delete(:expect_err) 31 | groups = args.map {|a| a.inspect }.join(", ") 32 | platform = opts[:platform] 33 | 34 | if opts[:lite_runtime] 35 | setup = "require 'rubygems' ; require 'bundler/setup' ; Bundler.setup(#{groups})\n" 36 | else 37 | setup = "require 'rubygems' ; " 38 | setup << "Gem.platforms = [Gem::Platform::RUBY, Gem::Platform.new('#{platform}')] ; " if platform 39 | setup = "require 'bundler' ; Bundler.setup(#{groups})\n" 40 | end 41 | 42 | @out = ruby(setup + cmd, :expect_err => expect_err) 43 | end 44 | 45 | def lib 46 | File.expand_path('../../../lib', __FILE__) 47 | end 48 | 49 | def bundle(cmd, options = {}) 50 | expect_err = options.delete(:expect_err) 51 | exit_status = options.delete(:exit_status) 52 | 53 | env = (options.delete(:env) || {}).map{|k,v| "#{k}='#{v}' "}.join 54 | args = options.map do |k,v| 55 | v == true ? " --#{k}" : " --#{k} #{v}" 56 | end.join 57 | gemfile = File.expand_path('../../../bin/bundle', __FILE__) 58 | cmd = "#{env}#{Gem.ruby} -I#{lib} #{gemfile} #{cmd}#{args}" 59 | 60 | if exit_status 61 | sys_status(cmd) 62 | else 63 | sys_exec(cmd, expect_err){|i| yield i if block_given? } 64 | end 65 | end 66 | 67 | def ruby(ruby, options = {}) 68 | expect_err = options.delete(:expect_err) 69 | ruby.gsub!(/(?=")/, "\\") 70 | ruby.gsub!('$', '\\$') 71 | sys_exec(%'#{Gem.ruby} -I#{lib} -e "#{ruby}"', expect_err) 72 | end 73 | 74 | def gembin(cmd) 75 | lib = File.expand_path("../../../lib", __FILE__) 76 | old, ENV['RUBYOPT'] = ENV['RUBYOPT'], "#{ENV['RUBYOPT']} -I#{lib}" 77 | sys_exec(cmd) 78 | ensure 79 | ENV['RUBYOPT'] = old 80 | end 81 | 82 | def sys_exec(cmd, expect_err = false) 83 | require "open3" 84 | @in, @out, @err = Open3.popen3(cmd) 85 | 86 | yield @in if block_given? 87 | 88 | @err = err.read_available_bytes.strip 89 | @out = out.read_available_bytes.strip 90 | 91 | puts @err unless expect_err || @err.empty? || !$show_err 92 | @out 93 | end 94 | 95 | def sys_status(cmd) 96 | @err = nil 97 | @out = %x{#{cmd}}.strip 98 | @exitstatus = $?.exitstatus 99 | end 100 | 101 | def config(config = nil) 102 | path = bundled_app('.bundle/config') 103 | return YAML.load_file(path) unless config 104 | FileUtils.mkdir_p(File.dirname(path)) 105 | File.open(path, 'w') do |f| 106 | f.puts config.to_yaml 107 | end 108 | config 109 | end 110 | 111 | def gemfile(*args) 112 | path = bundled_app("Gemfile") 113 | path = args.shift if Pathname === args.first 114 | str = args.shift || "" 115 | path.dirname.mkpath 116 | File.open(path.to_s, 'w') do |f| 117 | f.puts str 118 | end 119 | end 120 | 121 | def lockfile(*args) 122 | path = bundled_app("Gemfile.lock") 123 | path = args.shift if Pathname === args.first 124 | str = args.shift || "" 125 | 126 | # Trim the leading spaces 127 | spaces = str[/\A\s+/, 0] || "" 128 | str.gsub!(/^#{spaces}/, '') 129 | 130 | File.open(path.to_s, 'w') do |f| 131 | f.puts str 132 | end 133 | end 134 | 135 | def install_gemfile(*args) 136 | gemfile(*args) 137 | opts = args.last.is_a?(Hash) ? args.last : {} 138 | bundle :install, opts 139 | end 140 | 141 | def install_gems(*gems) 142 | gems.each do |g| 143 | path = "#{gem_repo1}/gems/#{g}.gem" 144 | 145 | raise "OMG `#{path}` does not exist!" unless File.exist?(path) 146 | 147 | gem_command :install, "--no-rdoc --no-ri --ignore-dependencies #{path}" 148 | end 149 | end 150 | 151 | alias install_gem install_gems 152 | 153 | def with_gem_path_as(path) 154 | gem_home, gem_path = ENV['GEM_HOME'], ENV['GEM_PATH'] 155 | ENV['GEM_HOME'], ENV['GEM_PATH'] = path.to_s, path.to_s 156 | yield 157 | ensure 158 | ENV['GEM_HOME'], ENV['GEM_PATH'] = gem_home, gem_path 159 | end 160 | 161 | def system_gems(*gems) 162 | gems = gems.flatten 163 | 164 | FileUtils.rm_rf(system_gem_path) 165 | FileUtils.mkdir_p(system_gem_path) 166 | 167 | Gem.clear_paths 168 | 169 | gem_home, gem_path, path = ENV['GEM_HOME'], ENV['GEM_PATH'], ENV['PATH'] 170 | ENV['GEM_HOME'], ENV['GEM_PATH'] = system_gem_path.to_s, system_gem_path.to_s 171 | 172 | install_gems(*gems) 173 | if block_given? 174 | begin 175 | yield 176 | ensure 177 | ENV['GEM_HOME'], ENV['GEM_PATH'] = gem_home, gem_path 178 | ENV['PATH'] = path 179 | end 180 | end 181 | end 182 | 183 | def simulate_new_machine 184 | system_gems [] 185 | FileUtils.rm_rf default_bundle_path 186 | FileUtils.rm_rf bundled_app('.bundle') 187 | end 188 | 189 | def revision_for(path) 190 | Dir.chdir(path) { `git rev-parse HEAD`.strip } 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /lib/bundler/dsl.rb: -------------------------------------------------------------------------------- 1 | module Bundler 2 | class DslError < StandardError; end 3 | 4 | class Dsl 5 | def self.evaluate(gemfile) 6 | builder = new 7 | builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1) 8 | builder.to_definition 9 | end 10 | 11 | VALID_PLATFORMS = [:ruby_18, :ruby_19, :ruby, :jruby, :mswin] 12 | 13 | def initialize 14 | @rubygems_source = Source::Rubygems.new 15 | @source = nil 16 | @sources = [] 17 | @dependencies = [] 18 | @groups = [] 19 | @platforms = [] 20 | @env = nil 21 | end 22 | 23 | def gem(name, *args) 24 | options = Hash === args.last ? args.pop : {} 25 | version = args.last || ">= 0" 26 | if group = options[:groups] || options[:group] 27 | options[:group] = group 28 | end 29 | 30 | _deprecated_options(options) 31 | _normalize_options(name, version, options) 32 | 33 | @dependencies << Dependency.new(name, version, options) 34 | end 35 | 36 | def source(source, options = {}) 37 | case source 38 | when :gemcutter, :rubygems, :rubyforge then 39 | rubygems_source "http://rubygems.org" 40 | return 41 | when String 42 | rubygems_source source 43 | return 44 | end 45 | 46 | @source = source 47 | options[:prepend] ? @sources.unshift(@source) : @sources << @source 48 | 49 | yield if block_given? 50 | @source 51 | ensure 52 | @source = nil 53 | end 54 | 55 | def path(path, options = {}, source_options = {}, &blk) 56 | source Source::Path.new(_normalize_hash(options).merge("path" => Pathname.new(path))), source_options, &blk 57 | end 58 | 59 | def git(uri, options = {}, source_options = {}, &blk) 60 | source Source::Git.new(_normalize_hash(options).merge("uri" => uri)), source_options, &blk 61 | end 62 | 63 | def to_definition(lockfile, unlock) 64 | @sources << @rubygems_source 65 | @sources.uniq! 66 | Definition.new(lockfile, @dependencies, @sources, unlock) 67 | end 68 | 69 | def group(*args, &blk) 70 | @groups.concat args 71 | yield 72 | ensure 73 | args.each { @groups.pop } 74 | end 75 | 76 | def platforms(*platforms) 77 | @platforms.concat platforms 78 | yield 79 | ensure 80 | platforms.each { @platforms.pop } 81 | end 82 | 83 | def env(name) 84 | @env, old = name, @env 85 | yield 86 | ensure 87 | @env = old 88 | end 89 | 90 | # Deprecated methods 91 | 92 | def self.deprecate(name, replacement = nil) 93 | define_method(name) do |*| 94 | message = "'#{name}' has been removed from the Gemfile DSL, " 95 | if replacement 96 | message << "and has been replaced with '#{replacement}'." 97 | else 98 | message << "and is no longer supported." 99 | end 100 | message << "\nSee the README for more information on upgrading from Bundler 0.8." 101 | raise DeprecatedMethod, message 102 | end 103 | end 104 | 105 | deprecate :only, :group 106 | deprecate :except 107 | deprecate :disable_system_gems 108 | deprecate :disable_rubygems 109 | deprecate :clear_sources 110 | deprecate :bundle_path 111 | deprecate :bin_path 112 | 113 | private 114 | 115 | def rubygems_source(source) 116 | @rubygems_source.add_remote source 117 | @sources << @rubygems_source 118 | end 119 | 120 | def _version?(version) 121 | version && Gem::Version.new(version) rescue false 122 | end 123 | 124 | def _normalize_hash(opts) 125 | # Cannot modify a hash during an iteration in 1.9 126 | opts.keys.each do |k| 127 | next if String === k 128 | v = opts[k] 129 | opts.delete(k) 130 | opts[k.to_s] = v 131 | end 132 | opts 133 | end 134 | 135 | def _normalize_options(name, version, opts) 136 | _normalize_hash(opts) 137 | 138 | invalid_keys = opts.keys - %w(group git path name branch ref tag require) 139 | if invalid_keys.any? 140 | plural = invalid_keys.size > 1 141 | message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} " 142 | if plural 143 | message << "as options for gem '#{name}', but they are invalid." 144 | else 145 | message << "as an option for gem '#{name}', but it is invalid." 146 | end 147 | raise InvalidOption, message 148 | end 149 | 150 | groups = @groups.dup 151 | groups.concat Array(opts.delete("group")) 152 | groups = [:default] if groups.empty? 153 | 154 | platforms = @platforms.dup 155 | platforms.concat Array(opts.delete("platforms")) 156 | platforms.map! { |p| p.to_sym } 157 | platforms.each do |p| 158 | next if VALID_PLATFORMS.include?(p) 159 | raise DslError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}" 160 | end 161 | 162 | # Normalize git and path options 163 | ["git", "path"].each do |type| 164 | if param = opts[type] 165 | options = _version?(version) ? opts.merge("name" => name, "version" => version) : opts.dup 166 | source = send(type, param, options, :prepend => true) 167 | opts["source"] = source 168 | end 169 | end 170 | 171 | opts["source"] ||= @source 172 | opts["env"] ||= @env 173 | opts["platforms"] = @platforms.dup 174 | opts["group"] = groups 175 | end 176 | 177 | def _deprecated_options(options) 178 | if options.include?(:require_as) 179 | raise DeprecatedOption, "Please replace :require_as with :require" 180 | elsif options.include?(:vendored_at) 181 | raise DeprecatedOption, "Please replace :vendored_at with :path" 182 | elsif options.include?(:only) 183 | raise DeprecatedOption, "Please replace :only with :group" 184 | elsif options.include?(:except) 185 | raise DeprecatedOption, "The :except option is no longer supported" 186 | end 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /spec/install/gems/groups_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "bundle install with gem sources" do 4 | describe "with groups" do 5 | describe "installing with no options" do 6 | before :each do 7 | install_gemfile <<-G 8 | source "file://#{gem_repo1}" 9 | gem "rack" 10 | group :emo do 11 | gem "activesupport", "2.3.5" 12 | end 13 | G 14 | end 15 | 16 | it "installs gems in the default group" do 17 | should_be_installed "rack 1.0.0" 18 | end 19 | 20 | it "installs gems in other groups" do 21 | should_be_installed "activesupport 2.3.5" 22 | end 23 | 24 | it "sets up everything if Bundler.setup is used with no groups" do 25 | out = run("require 'rack'; puts RACK") 26 | check out.should == '1.0.0' 27 | 28 | out = run("require 'activesupport'; puts ACTIVESUPPORT") 29 | out.should == '2.3.5' 30 | end 31 | end 32 | 33 | describe "installing --without" do 34 | describe "with gems assigned to a single group" do 35 | before :each do 36 | gemfile <<-G 37 | source "file://#{gem_repo1}" 38 | gem "rack" 39 | group :emo do 40 | gem "activesupport", "2.3.5" 41 | end 42 | G 43 | end 44 | 45 | it "installs gems in the default group" do 46 | bundle :install, :without => "emo" 47 | should_be_installed "rack 1.0.0", :groups => [:default] 48 | end 49 | 50 | it "does not install gems from the excluded group" do 51 | bundle :install, :without => "emo" 52 | should_not_be_installed "activesupport 2.3.5", :groups => [:default] 53 | end 54 | 55 | it "does not say it installed gems from the excluded group" do 56 | bundle :install, :without => "emo" 57 | out.should_not include("activesupport") 58 | end 59 | 60 | it "allows Bundler.setup for specific groups" do 61 | bundle :install, :without => "emo" 62 | run("require 'rack'; puts RACK", :default) 63 | out.should == '1.0.0' 64 | end 65 | 66 | it "does not effect the resolve" do 67 | gemfile <<-G 68 | source "file://#{gem_repo1}" 69 | gem "activesupport" 70 | group :emo do 71 | gem "rails", "2.3.2" 72 | end 73 | G 74 | 75 | bundle :install, :without => "emo" 76 | should_be_installed "activesupport 2.3.2", :groups => [:default] 77 | end 78 | 79 | it "still works when locked" do 80 | bundle :install, :without => "emo" 81 | bundle :lock 82 | 83 | simulate_new_machine 84 | bundle :install, :without => "emo" 85 | 86 | should_be_installed "rack 1.0.0", :groups => [:default] 87 | should_not_be_installed "activesupport 2.3.5", :groups => [:default] 88 | end 89 | end 90 | 91 | describe "with gems assigned to multiple groups" do 92 | before :each do 93 | gemfile <<-G 94 | source "file://#{gem_repo1}" 95 | gem "rack" 96 | group :emo, :lolercoaster do 97 | gem "activesupport", "2.3.5" 98 | end 99 | G 100 | end 101 | 102 | it "installs gems in the default group" do 103 | bundle :install, :without => "emo lolercoaster" 104 | should_be_installed "rack 1.0.0" 105 | end 106 | 107 | it "installs the gem if any of its groups are installed" do 108 | bundle "install --without emo" 109 | should_be_installed "rack 1.0.0", "activesupport 2.3.5" 110 | end 111 | 112 | it "works when locked as well" do 113 | bundle "install --without emo" 114 | bundle "lock" 115 | 116 | simulate_new_machine 117 | 118 | bundle "install --without lolercoaster" 119 | should_be_installed "rack 1.0.0", "activesupport 2.3.5" 120 | end 121 | 122 | describe "with a gem defined multiple times in different groups" do 123 | before :each do 124 | gemfile <<-G 125 | source "file://#{gem_repo1}" 126 | gem "rack" 127 | 128 | group :emo do 129 | gem "activesupport", "2.3.5" 130 | end 131 | 132 | group :lolercoaster do 133 | gem "activesupport", "2.3.5" 134 | end 135 | G 136 | end 137 | 138 | it "installs the gem w/ option --without emo" do 139 | bundle "install --without emo" 140 | should_be_installed "activesupport 2.3.5" 141 | end 142 | 143 | it "installs the gem w/ option --without lolercoaster" do 144 | bundle "install --without lolercoaster" 145 | should_be_installed "activesupport 2.3.5" 146 | end 147 | 148 | it "does not install the gem w/ option --without emo lolercoaster" do 149 | bundle "install --without emo lolercoaster" 150 | should_not_be_installed "activesupport 2.3.5" 151 | end 152 | end 153 | end 154 | 155 | describe "nesting groups" do 156 | before :each do 157 | gemfile <<-G 158 | source "file://#{gem_repo1}" 159 | gem "rack" 160 | group :emo do 161 | group :lolercoaster do 162 | gem "activesupport", "2.3.5" 163 | end 164 | end 165 | G 166 | end 167 | 168 | it "installs gems in the default group" do 169 | bundle :install, :without => "emo lolercoaster" 170 | should_be_installed "rack 1.0.0" 171 | end 172 | 173 | it "installs the gem if any of its groups are installed" do 174 | bundle "install --without emo" 175 | should_be_installed "rack 1.0.0", "activesupport 2.3.5" 176 | end 177 | 178 | it "works when locked as well" do 179 | bundle "install --without emo" 180 | bundle "lock" 181 | 182 | simulate_new_machine 183 | 184 | bundle "install --without lolercoaster" 185 | should_be_installed "rack 1.0.0", "activesupport 2.3.5" 186 | end 187 | end 188 | end 189 | end 190 | end -------------------------------------------------------------------------------- /lib/bundler/vendor/thor/invocation.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module Invocation 3 | def self.included(base) #:nodoc: 4 | base.extend ClassMethods 5 | end 6 | 7 | module ClassMethods 8 | # This method is responsible for receiving a name and find the proper 9 | # class and task for it. The key is an optional parameter which is 10 | # available only in class methods invocations (i.e. in Thor::Group). 11 | def prepare_for_invocation(key, name) #:nodoc: 12 | case name 13 | when Symbol, String 14 | Thor::Util.find_class_and_task_by_namespace(name.to_s, !key) 15 | else 16 | name 17 | end 18 | end 19 | end 20 | 21 | # Make initializer aware of invocations and the initialization args. 22 | def initialize(args=[], options={}, config={}, &block) #:nodoc: 23 | @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] } 24 | @_initializer = [ args, options, config ] 25 | super 26 | end 27 | 28 | # Receives a name and invokes it. The name can be a string (either "task" or 29 | # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task 30 | # cannot be guessed by name, it can also be supplied as second argument. 31 | # 32 | # You can also supply the arguments, options and configuration values for 33 | # the task to be invoked, if none is given, the same values used to 34 | # initialize the invoker are used to initialize the invoked. 35 | # 36 | # When no name is given, it will invoke the default task of the current class. 37 | # 38 | # ==== Examples 39 | # 40 | # class A < Thor 41 | # def foo 42 | # invoke :bar 43 | # invoke "b:hello", ["José"] 44 | # end 45 | # 46 | # def bar 47 | # invoke "b:hello", ["José"] 48 | # end 49 | # end 50 | # 51 | # class B < Thor 52 | # def hello(name) 53 | # puts "hello #{name}" 54 | # end 55 | # end 56 | # 57 | # You can notice that the method "foo" above invokes two tasks: "bar", 58 | # which belongs to the same class and "hello" which belongs to the class B. 59 | # 60 | # By using an invocation system you ensure that a task is invoked only once. 61 | # In the example above, invoking "foo" will invoke "b:hello" just once, even 62 | # if it's invoked later by "bar" method. 63 | # 64 | # When class A invokes class B, all arguments used on A initialization are 65 | # supplied to B. This allows lazy parse of options. Let's suppose you have 66 | # some rspec tasks: 67 | # 68 | # class Rspec < Thor::Group 69 | # class_option :mock_framework, :type => :string, :default => :rr 70 | # 71 | # def invoke_mock_framework 72 | # invoke "rspec:#{options[:mock_framework]}" 73 | # end 74 | # end 75 | # 76 | # As you noticed, it invokes the given mock framework, which might have its 77 | # own options: 78 | # 79 | # class Rspec::RR < Thor::Group 80 | # class_option :style, :type => :string, :default => :mock 81 | # end 82 | # 83 | # Since it's not rspec concern to parse mock framework options, when RR 84 | # is invoked all options are parsed again, so RR can extract only the options 85 | # that it's going to use. 86 | # 87 | # If you want Rspec::RR to be initialized with its own set of options, you 88 | # have to do that explicitely: 89 | # 90 | # invoke "rspec:rr", [], :style => :foo 91 | # 92 | # Besides giving an instance, you can also give a class to invoke: 93 | # 94 | # invoke Rspec::RR, [], :style => :foo 95 | # 96 | def invoke(name=nil, *args) 97 | args.unshift(nil) if Array === args.first || NilClass === args.first 98 | task, args, opts, config = args 99 | 100 | object, task = _prepare_for_invocation(name, task) 101 | klass, instance = _initialize_klass_with_initializer(object, args, opts, config) 102 | 103 | method_args = [] 104 | current = @_invocations[klass] 105 | 106 | iterator = proc do |_, task| 107 | unless current.include?(task.name) 108 | current << task.name 109 | task.run(instance, method_args) 110 | end 111 | end 112 | 113 | if task 114 | args ||= [] 115 | method_args = args[Range.new(klass.arguments.size, -1)] || [] 116 | iterator.call(nil, task) 117 | else 118 | klass.all_tasks.map(&iterator) 119 | end 120 | end 121 | 122 | # Invokes using shell padding. 123 | def invoke_with_padding(*args) 124 | with_padding { invoke(*args) } 125 | end 126 | 127 | protected 128 | 129 | # Configuration values that are shared between invocations. 130 | def _shared_configuration #:nodoc: 131 | { :invocations => @_invocations } 132 | end 133 | 134 | # This method can receive several different types of arguments and it's then 135 | # responsible to normalize them by returning the object where the task should 136 | # be invoked and a Thor::Task object. 137 | def _prepare_for_invocation(name, sent_task=nil) #:nodoc: 138 | if name.is_a?(Thor::Task) 139 | task = name 140 | elsif task = self.class.all_tasks[name.to_s] 141 | object = self 142 | else 143 | object, task = self.class.prepare_for_invocation(nil, name) 144 | task ||= sent_task 145 | end 146 | 147 | # If the object was not set, use self and use the name as task. 148 | object, task = self, name unless object 149 | return object, _validate_task(object, task) 150 | end 151 | 152 | # Check if the object given is a Thor class object and get a task object 153 | # for it. 154 | def _validate_task(object, task) #:nodoc: 155 | klass = object.is_a?(Class) ? object : object.class 156 | raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base 157 | 158 | task ||= klass.default_task if klass.respond_to?(:default_task) 159 | task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task) 160 | task 161 | end 162 | 163 | # Initialize klass using values stored in the @_initializer. 164 | def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc: 165 | if object.is_a?(Class) 166 | klass = object 167 | 168 | stored_args, stored_opts, stored_config = @_initializer 169 | args ||= stored_args.dup 170 | opts ||= stored_opts.dup 171 | 172 | config ||= {} 173 | config = stored_config.merge(_shared_configuration).merge!(config) 174 | [ klass, klass.new(args, opts, config) ] 175 | else 176 | [ object.class, object ] 177 | end 178 | end 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /lib/bundler.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'pathname' 3 | require 'yaml' 4 | require 'bundler/rubygems_ext' 5 | require 'bundler/version' 6 | 7 | module Bundler 8 | ORIGINAL_ENV = ENV.to_hash 9 | 10 | autoload :Definition, 'bundler/definition' 11 | autoload :Dependency, 'bundler/dependency' 12 | autoload :Dsl, 'bundler/dsl' 13 | autoload :Environment, 'bundler/environment' 14 | autoload :Graph, 'bundler/graph' 15 | autoload :Index, 'bundler/index' 16 | autoload :Installer, 'bundler/installer' 17 | autoload :LazySpecification, 'bundler/lazy_specification' 18 | autoload :LockfileParser, 'bundler/lockfile_parser' 19 | autoload :RemoteSpecification, 'bundler/remote_specification' 20 | autoload :Resolver, 'bundler/resolver' 21 | autoload :Runtime, 'bundler/runtime' 22 | autoload :Settings, 'bundler/settings' 23 | autoload :SharedHelpers, 'bundler/shared_helpers' 24 | autoload :SpecSet, 'bundler/spec_set' 25 | autoload :Source, 'bundler/source' 26 | autoload :Specification, 'bundler/shared_helpers' 27 | autoload :UI, 'bundler/ui' 28 | 29 | class BundlerError < StandardError 30 | def self.status_code(code = nil) 31 | return @code unless code 32 | @code = code 33 | end 34 | 35 | def status_code 36 | self.class.status_code 37 | end 38 | end 39 | 40 | class GemfileNotFound < BundlerError; status_code(10) ; end 41 | class GemNotFound < BundlerError; status_code(7) ; end 42 | class GemfileError < BundlerError; status_code(4) ; end 43 | class GemfileChanged < GemfileError; status_code(4) ; end 44 | class PathError < BundlerError; status_code(13) ; end 45 | class GitError < BundlerError; status_code(11) ; end 46 | class GemspecError < BundlerError; status_code(14) ; end 47 | class DeprecatedMethod < BundlerError; status_code(12) ; end 48 | class DeprecatedOption < BundlerError; status_code(12) ; end 49 | class GemspecError < BundlerError; status_code(14) ; end 50 | class InvalidOption < BundlerError; status_code(15) ; end 51 | 52 | class VersionConflict < BundlerError 53 | attr_reader :conflicts 54 | 55 | def initialize(conflicts, msg = nil) 56 | super(msg) 57 | @conflicts = conflicts 58 | end 59 | 60 | status_code(6) 61 | end 62 | 63 | # Internal errors, should be rescued 64 | class InvalidSpecSet < StandardError; end 65 | 66 | class << self 67 | attr_writer :ui, :bundle_path 68 | 69 | def configure 70 | @configured ||= begin 71 | configure_gem_home_and_path 72 | true 73 | end 74 | end 75 | 76 | def ui 77 | @ui ||= UI.new 78 | end 79 | 80 | def bundle_path 81 | @bundle_path ||= begin 82 | path = settings[:path] || Gem.dir 83 | Pathname.new(path).expand_path(root) 84 | end 85 | end 86 | 87 | def bin_path 88 | @bin_path ||= begin 89 | path = settings[:bin] || "#{Gem.user_home}/.bundle/bin" 90 | FileUtils.mkdir_p(path) 91 | Pathname.new(path).expand_path 92 | end 93 | end 94 | 95 | def setup(*groups) 96 | return @setup if defined?(@setup) && @setup 97 | 98 | if groups.empty? 99 | # Load all groups, but only once 100 | @setup = load.setup 101 | else 102 | # Figure out which groups haven't been loaded yet 103 | unloaded = groups - (@completed_groups || []) 104 | # Record groups that are now loaded 105 | @completed_groups = groups | (@completed_groups || []) 106 | # Load any groups that are not yet loaded 107 | unloaded.any? ? load.setup(*unloaded) : load 108 | end 109 | end 110 | 111 | def require(*groups) 112 | setup(*groups).require(*groups) 113 | end 114 | 115 | def load 116 | @load ||= Runtime.new(root, definition) 117 | end 118 | 119 | def environment 120 | Bundler::Environment.new(root, definition) 121 | end 122 | 123 | def definition(unlock = nil) 124 | @definition = nil if unlock 125 | @definition ||= begin 126 | configure 127 | upgrade_lockfile 128 | lockfile = root.join("Gemfile.lock") 129 | Definition.build(default_gemfile, lockfile, unlock) 130 | end 131 | end 132 | 133 | def home 134 | bundle_path.join("bundler") 135 | end 136 | 137 | def install_path 138 | home.join("gems") 139 | end 140 | 141 | def specs_path 142 | bundle_path.join("specifications") 143 | end 144 | 145 | def cache 146 | bundle_path.join("cache/bundler") 147 | end 148 | 149 | def root 150 | default_gemfile.dirname.expand_path 151 | end 152 | 153 | def app_cache 154 | root.join("vendor/cache") 155 | end 156 | 157 | def tmp 158 | "#{Gem.user_home}/.bundler/tmp" 159 | end 160 | 161 | def settings 162 | @settings ||= Settings.new(root) 163 | end 164 | 165 | def with_clean_env 166 | bundled_env = ENV.to_hash 167 | ENV.replace(ORIGINAL_ENV) 168 | yield 169 | ensure 170 | ENV.replace(bundled_env.to_hash) 171 | end 172 | 173 | def default_gemfile 174 | SharedHelpers.default_gemfile 175 | end 176 | 177 | WINDOWS = Config::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)! 178 | NULL = WINDOWS ? "NUL" : "/dev/null" 179 | 180 | def requires_sudo? 181 | case 182 | when File.writable?(bundle_path) || 183 | `which sudo 2>#{NULL}`.empty? || 184 | File.owned?(bundle_path) 185 | false 186 | else 187 | true 188 | end 189 | rescue Errno::ENOENT 190 | false 191 | end 192 | 193 | private 194 | 195 | def configure_gem_home_and_path 196 | if settings[:disable_shared_gems] 197 | ENV['GEM_PATH'] = '' 198 | ENV['GEM_HOME'] = File.expand_path(bundle_path, root) 199 | else 200 | paths = [Gem.dir, Gem.path].flatten.compact.uniq.reject{|p| p.empty? } 201 | ENV["GEM_PATH"] = paths.join(File::PATH_SEPARATOR) 202 | ENV["GEM_HOME"] = bundle_path.to_s 203 | end 204 | 205 | Gem.clear_paths 206 | end 207 | 208 | def upgrade_lockfile 209 | lockfile = root.join("Gemfile.lock") 210 | if lockfile.exist? && lockfile.read(3) == "---" 211 | Bundler.ui.warn "Detected Gemfile.lock generated by 0.9, deleting..." 212 | lockfile.rmtree 213 | # lock = YAML.load_file(lockfile) 214 | # 215 | # source_uris = lock["sources"].map{|s| s["Rubygems"]["uri"] } 216 | # sources = [Bundler::Source::Rubygems.new({"remotes" => source_uris})] 217 | # 218 | # deps = lock["dependencies"].map do |name, opts| 219 | # version = opts.delete("version") 220 | # Bundler::Dependency.new(name, version, opts) 221 | # end 222 | # 223 | # definition = Bundler::Definition.new(nil, deps, sources, {}) 224 | # 225 | # File.open(lockfile, 'w') do |f| 226 | # f.write definition.to_lock 227 | # end 228 | end 229 | end 230 | 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Note: the master branch is currently unstable while 1.0 is in beta.
The current stable version of bundler is in the branch named `v0.9`. 2 | 3 | ## Bundler : A gem to bundle gems 4 | 5 | Bundler is a tool that manages gem dependencies for your ruby application. It 6 | takes a gem manifest file and is able to fetch, download, and install the gems 7 | and all child dependencies specified in this manifest. It can manage any update 8 | to the gem manifest file and update the bundle's gems accordingly. It also lets 9 | you run any ruby code in context of the bundle's gem environment. 10 | 11 | ## Installation and usage 12 | 13 | See [gembundler.com](http://gembundler.com) for up-to-date installation and usage instructions 14 | 15 | ## Gem dependency resolution 16 | 17 | One of the most important things that the bundler does is do a 18 | dependency resolution on the full list of gems that you specify, all 19 | at once. This differs from the one-at-a-time dependency resolution that 20 | Rubygems does, which can result in the following problem: 21 | 22 | # On my system: 23 | # activesupport 3.0.pre 24 | # activesupport 2.3.4 25 | # activemerchant 1.4.2 26 | # rails 2.3.4 27 | # 28 | # activemerchant 1.4.2 depends on activesupport >= 2.3.2 29 | 30 | gem "activemerchant", "1.4.2" 31 | # results in activating activemerchant, as well as 32 | # activesupport 3.0.pre, since it is >= 2.3.2 33 | 34 | gem "rails", "2.3.4" 35 | # results in: 36 | # can't activate activesupport (= 2.3.4, runtime) 37 | # for ["rails-2.3.4"], already activated 38 | # activesupport-3.0.pre for ["activemerchant-1.4.2"] 39 | 40 | This is because activemerchant has a broader dependency, which results 41 | in the activation of a version of activesupport that does not satisfy 42 | a more narrow dependency. 43 | 44 | Bundler solves this problem by evaluating all dependencies at once, 45 | so it can detect that all gems *together* require activesupport "2.3.4". 46 | 47 | ## Upgrading from Bundler 0.8 to 0.9 and above 48 | 49 | Upgrading to Bundler 0.9 from Bundler 0.8 requires upgrading several 50 | API calls in your Gemfile, and some workarounds if you are using Rails 2.3. 51 | 52 | ### Gemfile Removals 53 | 54 | Bundler 0.9 removes the following Bundler 0.8 Gemfile APIs: 55 | 56 | 1. `disable_system_gems`: This is now the default (and only) option 57 | for bundler. Bundler uses the system gems you have specified 58 | in the Gemfile, and only the system gems you have specified 59 | (and their dependencies) 60 | 2. `disable_rubygems`: This is no longer supported. We are looking 61 | into ways to get the fastest performance out of each supported 62 | scenario, and we will make speed the default where possible. 63 | 3. `clear_sources`: Bundler now defaults to an empty source 64 | list. If you want to include Rubygems, you can add the source 65 | via source "http://gemcutter.org". If you use bundle init, this 66 | source will be automatically added for you in the generated 67 | Gemfile 68 | 4. `bundle_path`: You can specify this setting when installing 69 | via `bundle install /path/to/bundle`. Bundler will remember 70 | where you installed the dependencies to on a particular 71 | machine for future installs, loads, setups, etc. 72 | 5. `bin_path`: Bundler no longer generates binaries in the root 73 | of your app. You should use `bundle exec` to execute binaries 74 | in the current context. 75 | 76 | ### Gemfile Changes 77 | 78 | Bundler 0.9 changes the following Bundler 0.8 Gemfile APIs: 79 | 80 | 1. Bundler 0.8 supported :only and :except as APIs for describing 81 | groups of gems. Bundler 0.9 supports a single `group` method, 82 | which you can use to group gems together. See the above "Group" 83 | section for more information. 84 | 85 | This means that `gem "foo", :only => :production` becomes 86 | `gem "foo", :group => :production`, and 87 | `only :production { gem "foo" }` becomes 88 | `group :production { gem "foo" }` 89 | 90 | The short version is: group your gems together logically, and 91 | use the available commands to make use of the groups you've 92 | created. 93 | 94 | 2. `:require_as` becomes `:require` 95 | 96 | 3. `:vendored_at` is fully removed; you should use `:path` 97 | 98 | ### API Changes 99 | 100 | 1. `Bundler.require_env(:environment)` becomes 101 | `Bundler.require(:multiple, :groups)`. You must 102 | now specify the default group (the default group is the 103 | group made up of the gems not assigned to any group) 104 | explicitly. So `Bundler.require_env(:test)` becomes 105 | `Bundler.require(:default, :test)` 106 | 107 | 2. `require 'vendor/gems/environment'`: In unlocked 108 | mode, where using system gems, this becomes 109 | `Bundler.setup(:multiple, :groups)`. If you don't 110 | specify any groups, this puts all groups on the load 111 | path. In locked, mode, it becomes `require '.bundle/environment'` 112 | 113 | ## More information 114 | 115 | ### Development 116 | 117 | For information about future plans and changes that will happen between now and bundler 1.0, see the [ROADMAP](http://github.com/carlhuda/bundler/blob/master/ROADMAP.md). To see what has changed in each version of bundler, starting with 0.9.5, see the [CHANGELOG](http://github.com/carlhuda/bundler/blob/master/CHANGELOG.md). 118 | 119 | ### Deploying to memory-constrained servers 120 | 121 | When deploying to a server that is memory-constrained, like Dreamhost, you should run `bundle package` on your local development machine, and then check in the resulting `Gemfile.lock` file and `vendor/cache` directory. The lockfile and cached gems will mean bundler can just install the gems immediately, without contacting any gem servers or using a lot of memory to resolve the dependency tree. On the server, you only need to run `bundle install` after you update your deployed code. 122 | 123 | ### Other questions 124 | 125 | Any remaining questions may be asked via IRC in [#bundler](irc://irc.freenode.net/bundler) on Freenode, or via email on the [Bundler mailing list](http://groups.google.com/group/ruby-bundler). 126 | 127 | ## Reporting bugs 128 | 129 | Before reporting a bug, try these troubleshooting steps: 130 | 131 | rm -rf ~/.bundle/ ~/.gem/ .bundle/ Gemfile.lock 132 | bundle install 133 | 134 | If you are still having problems, please report bugs to the github issue tracker for the project, located at [http://github.com/carlhuda/bundler/issues/](http://github.com/carlhuda/bundler/issues/). 135 | 136 | The best possible scenario is a ticket with a fix for the bug and a test for the fix. If that's not possible, instructions to reproduce the issue are vitally important. If you're not sure exactly how to reproduce the issue that you are seeing, create a gist of the following information and include it in your ticket: 137 | 138 | - Whether you have locked or not 139 | - What version of bundler you are using 140 | - What version of Ruby you are using 141 | - Whether you are using RVM, and if so what version 142 | - Your Gemfile 143 | - The command you ran to generate exception(s) 144 | - The exception backtrace(s) 145 | 146 | If you are using Rails 2.3, please also include: 147 | 148 | - Your boot.rb file 149 | - Your preinitializer.rb file 150 | - Your environment.rb file 151 | -------------------------------------------------------------------------------- /lib/bundler/definition.rb: -------------------------------------------------------------------------------- 1 | require "digest/sha1" 2 | 3 | module Bundler 4 | class Definition 5 | attr_reader :dependencies, :platforms 6 | 7 | def self.build(gemfile, lockfile, unlock) 8 | unlock ||= {} 9 | gemfile = Pathname.new(gemfile).expand_path 10 | 11 | unless gemfile.file? 12 | raise GemfileNotFound, "#{gemfile} not found" 13 | end 14 | 15 | # TODO: move this back into DSL 16 | builder = Dsl.new 17 | builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1) 18 | builder.to_definition(lockfile, unlock) 19 | end 20 | 21 | =begin 22 | How does the new system work? 23 | === 24 | * Load information from Gemfile and Lockfile 25 | * Invalidate stale locked specs 26 | * All specs from stale source are stale 27 | * All specs that are reachable only through a stale 28 | dependency are stale. 29 | * If all fresh dependencies are satisfied by the locked 30 | specs, then we can try to resolve locally. 31 | =end 32 | 33 | def initialize(lockfile, dependencies, sources, unlock) 34 | @dependencies, @sources, @unlock = dependencies, sources, unlock 35 | @specs = nil 36 | @unlock[:gems] ||= [] 37 | @unlock[:sources] ||= [] 38 | 39 | if lockfile && File.exists?(lockfile) 40 | locked = LockfileParser.new(File.read(lockfile)) 41 | @platforms = locked.platforms 42 | @locked_deps = locked.dependencies 43 | @last_resolve = SpecSet.new(locked.specs) 44 | @locked_sources = locked.sources 45 | else 46 | @platforms = [] 47 | @locked_deps = [] 48 | @last_resolve = SpecSet.new([]) 49 | @locked_sources = [] 50 | end 51 | 52 | current_platform = Gem.platforms.map { |p| p.to_generic }.compact.last 53 | @platforms |= [current_platform] 54 | 55 | converge 56 | end 57 | 58 | def resolve_remotely! 59 | raise "Specs already loaded" if @specs 60 | @sources.each { |s| s.remote! } 61 | specs 62 | end 63 | 64 | def specs 65 | @specs ||= resolve.materialize(requested_dependencies) 66 | end 67 | 68 | def missing_specs 69 | missing = [] 70 | resolve.materialize(requested_dependencies, missing) 71 | missing 72 | end 73 | 74 | def requested_specs 75 | @requested_specs ||= begin 76 | groups = self.groups - Bundler.settings.without 77 | groups.map! { |g| g.to_sym } 78 | specs_for(groups) 79 | end 80 | end 81 | 82 | def current_dependencies 83 | dependencies.reject { |d| !d.should_include? } 84 | end 85 | 86 | def specs_for(groups) 87 | deps = dependencies.select { |d| (d.groups & groups).any? } 88 | deps.delete_if { |d| !d.should_include? } 89 | specs.for(expand_dependencies(deps)) 90 | end 91 | 92 | def resolve 93 | @resolve ||= begin 94 | if @last_resolve.valid_for?(expanded_dependencies) 95 | @last_resolve 96 | else 97 | source_requirements = {} 98 | dependencies.each do |dep| 99 | next unless dep.source 100 | source_requirements[dep.name] = dep.source.specs 101 | end 102 | 103 | # Run a resolve against the locally available gems 104 | Resolver.resolve(expanded_dependencies, index, source_requirements, @last_resolve) 105 | end 106 | end 107 | end 108 | 109 | def index 110 | @index ||= Index.build do |idx| 111 | @sources.each do |s| 112 | idx.use s.specs 113 | end 114 | end 115 | end 116 | 117 | def no_sources? 118 | @sources.length == 1 && @sources.first.remotes.empty? 119 | end 120 | 121 | def groups 122 | dependencies.map { |d| d.groups }.flatten.uniq 123 | end 124 | 125 | def to_lock 126 | out = "" 127 | 128 | sorted_sources.each do |source| 129 | # Add the source header 130 | out << source.to_lock 131 | # Find all specs for this source 132 | resolve. 133 | select { |s| s.source == source }. 134 | sort_by { |s| [s.name, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }. 135 | each do |spec| 136 | out << spec.to_lock 137 | end 138 | out << "\n" 139 | end 140 | 141 | out << "PLATFORMS\n" 142 | 143 | platforms.map { |p| p.to_s }.sort.each do |p| 144 | out << " #{p}\n" 145 | end 146 | 147 | out << "\n" 148 | out << "DEPENDENCIES\n" 149 | 150 | dependencies. 151 | sort_by { |d| d.name }. 152 | each do |dep| 153 | out << dep.to_lock 154 | end 155 | 156 | out 157 | end 158 | 159 | private 160 | 161 | def converge 162 | converge_sources 163 | converge_dependencies 164 | converge_locked_specs 165 | end 166 | 167 | def converge_sources 168 | @sources = (@locked_sources & @sources) | @sources 169 | @sources.each do |source| 170 | source.unlock! if source.respond_to?(:unlock!) && @unlock[:sources].include?(source.name) 171 | end 172 | end 173 | 174 | def converge_dependencies 175 | (@dependencies + @locked_deps).each do |dep| 176 | if dep.source 177 | source = @sources.find { |s| dep.source == s } 178 | raise "Something went wrong, there is no matching source" unless source 179 | dep.source = source 180 | end 181 | end 182 | end 183 | 184 | def converge_locked_specs 185 | deps = [] 186 | 187 | @dependencies.each do |dep| 188 | if in_locked_deps?(dep) || satisfies_locked_spec?(dep) 189 | deps << dep 190 | end 191 | end 192 | 193 | converged = [] 194 | @last_resolve.each do |s| 195 | s.source = @sources.find { |src| s.source == src } 196 | 197 | next if s.source.nil? || @unlock[:sources].include?(s.name) 198 | 199 | converged << s 200 | end 201 | 202 | resolve = SpecSet.new(converged) 203 | resolve = resolve.for(expand_dependencies(deps), @unlock[:gems]) 204 | @last_resolve.select!(resolve.names) 205 | end 206 | 207 | def in_locked_deps?(dep) 208 | @locked_deps.any? do |d| 209 | dep == d && dep.source == d.source 210 | end 211 | end 212 | 213 | def satisfies_locked_spec?(dep) 214 | @last_resolve.any? { |s| s.satisfies?(dep) } 215 | end 216 | 217 | def expanded_dependencies 218 | @expanded_dependencies ||= expand_dependencies(dependencies) 219 | end 220 | 221 | def expand_dependencies(dependencies) 222 | deps = [] 223 | dependencies.each do |dep| 224 | dep.gem_platforms(@platforms).each do |p| 225 | deps << DepProxy.new(dep, p) 226 | end 227 | end 228 | deps 229 | end 230 | 231 | def sorted_sources 232 | @sources.sort_by do |s| 233 | # Place GEM at the top 234 | [ s.is_a?(Source::Rubygems) ? 1 : 0, s.to_s ] 235 | end 236 | end 237 | 238 | def requested_dependencies 239 | groups = self.groups - Bundler.settings.without 240 | groups.map! { |g| g.to_sym } 241 | dependencies.reject { |d| !d.should_include? || (d.groups & groups).empty? } 242 | end 243 | end 244 | end 245 | --------------------------------------------------------------------------------