Vendor the latest version of the bundler

This commit is contained in:
Carl Lerche 2009-09-03 09:31:04 -07:00
parent e3f5fd536e
commit f416f9f0ae
30 changed files with 1392 additions and 814 deletions

@ -0,0 +1,162 @@
## Bundler : A gem to bundle gems
Github: http://github.com/wycats/bundler
Mailing list: http://groups.google.com/group/ruby-bundler
IRC: #carlhuda on freenode
## Intro
Bundler is a tool that manages gem dependencies for your ruby application. It
takes a gem manifest file and is able to fetch, download, and install the gems
and all child dependencies specified in this manifest. It can manage any update
to the gem manifest file and update the bundled gems accordingly. It also lets
you run any ruby code in context of the bundled gem environment.
## Disclaimer
This project is under rapid development. It is usable today, but there will be
many changes in the near future, including to the Gemfile DSL. We will bump up
versions with changes though. We greatly appreciate feedback.
## Installation
Bundler has no dependencies. Just clone the git repository and install the gem
with the following rake task:
rake install
## Usage
Bundler requires a gem manifest file to be created. This should be a file named
`Gemfile` located in the root directory of your application. After the manifest
has been created, in your shell, cd into your application's directory and run
`gem bundle`. This will start the bundling process.
### Manifest file
This is where you specify all of your application's dependencies. By default
this should be in a file named `Gemfile` located in your application's root
directory. The following is an example of a potential `Gemfile`. For more
information, please refer to Bundler::ManifestBuilder.
# Specify a dependency on rails. When the bundler downloads gems,
# it will download rails as well as all of rails' dependencies (such as
# activerecord, actionpack, etc...)
#
# At least one dependency must be specified
gem "rails"
# Specify a dependency on rack v.1.0.0. The version is optional. If present,
# it can be specified the same way as with rubygems' #gem method.
gem "rack", "1.0.0"
# Specify a dependency rspec, but only activate that gem in the "testing"
# environment (read more about environments later). :except is also a valid
# option to specify environment restrictions.
gem "rspec", :only => :testing
# Add http://gems.github.com as a source that the bundler will use
# to find gems listed in the manifest. By default,
# http://gems.rubyforge.org is already added to the list.
#
# This is an optional setting.
source "http://gems.github.com"
# Specify where the bundled gems should be stashed. This directory will
# be a gem repository where all gems are downloaded to and installed to.
#
# This is an optional setting.
# The default is: vendor/gems
bundle_path "my/bundled/gems"
# Specify where gem executables should be copied to.
#
# This is an optional setting.
# The default is: bin
bin_path "my/executables"
# Specify that rubygems should be completely disabled. This means that it
# will be impossible to require it and that available gems will be
# limited exclusively to gems that have been bundled.
#
# The default is to automatically require rubygems. There is also a
# `disable_system_gems` option that will limit available rubygems to
# the ones that have been bundled.
disable_rubygems
### Running Bundler
Once a manifest file has been created, the only thing that needs to be done
is to run the `gem bundle` command anywhere in your application. The script
will load the manifest file, resole all the dependencies, download all
needed gems, and install them into the specified directory.
Every time an update is made to the manifest file, run `gem bundle` again to
get the changes installed. This will only check the remote sources if your
currently installed gems do not satisfy the `Gemfile`. If you want to force
checking for updates on the remote sources, use the `--update` option.
### Running your application
The easiest way to run your application is to start it with an executable
copied to the specified bin directory (by default, simply bin). For example,
if the application in question is a rack app, start it with `bin/rackup`.
This will automatically set the gem environment correctly.
Another way to run arbitrary ruby code in context of the bundled gems is to
run it with the `gem exec` command. For example:
gem exec ruby my_ruby_script.rb
Yet another way is to manually require the environment file first. This is
located in `[bundle_path]/environments/default.rb`. For example:
ruby -r vendor/gems/environment.rb my_ruby_script.rb
### Using Bundler with Rails today
It should be possible to use Bundler with Rails today. Here are the steps
to follow.
* In your rails app, create a Gemfile and specify the gems that your
application depends on. Make sure to specify rails as well:
gem "rails", "2.1.2"
gem "will_paginate"
# Optionally, you can disable system gems all together and only
# use bundled gems.
disable_system_gems
* Run `gem bundle`
* You can now use rails if you prepend `gem exec` to every call to `script/*`
but that isn't fun.
* At the top of `config/boot.rb`, add the following line:
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment'))
In theory, this should be enough to get going.
## To require rubygems or not
Ideally, no gem would assume the presence of rubygems at runtime. Rubygems provides
enough features so that this isn't necessary. However, there are a number of gems
that require specific rubygem features.
If the `disable_rubygems` option is used, Bundler will stub out the most common
of these features, but it is possible that things will not go as intended quite
yet. So, if you are brave, try your code without rubygems at runtime.
## Known Issues
* When a gem points to a git repository, the git repository will be cloned
every time Bundler does a gem dependency resolve.
## Reporting bugs
Please report all bugs on the github issue tracker for the project located
at:
http://github.com/wycats/bundler/issues/

@ -1,52 +1,57 @@
require 'rubygems' unless ENV['NO_RUBYGEMS']
require 'rake/gempackagetask'
require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask'
spec = Gem::Specification.new do |s|
s.name = "bundler"
s.version = "0.0.1"
s.author = "Your Name"
s.email = "Your Email"
s.homepage = "http://example.com"
s.description = s.summary = "A gem that provides..."
s.version = "0.5.0.pre"
s.author = "Yehuda Katz"
s.email = "wycats@gmail.com"
s.homepage = "http://github.com/wycats/bundler"
s.description = s.summary = "An easy way to vendor gem dependencies"
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["README", "LICENSE"]
s.summary = ""
s.extra_rdoc_files = ["README.markdown", "LICENSE"]
# Uncomment this to add a dependency
# s.add_dependency "foo"
s.required_rubygems_version = ">= 1.3.5"
s.bindir = "bin"
s.executables = %w( gem_bundler )
s.require_path = 'lib'
s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("lib/**/*")
end
task :default => :spec
begin
require 'spec/rake/spectask'
rescue LoadError
task(:spec) { $stderr.puts '`gem install rspec` to run specs' }
else
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_files = FileList['spec/**/*_spec.rb'] - FileList['spec/fixtures/**/*_spec.rb']
t.spec_opts = %w(-fs --color)
end
end
begin
require 'rake/gempackagetask'
rescue LoadError
task(:gem) { $stderr.puts '`gem install rake` to package gems' }
else
Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end
end
desc "install the gem locally"
task :install => [:package] do
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
sh %{gem install pkg/#{spec.name}-#{spec.version}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM}.gemspec", "w") do |file|
File.open("#{spec.name}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end

@ -1,40 +0,0 @@
#!/usr/bin/env ruby
require "optparse"
require "bundler"
options = {}
parser = OptionParser.new do |op|
op.banner = "Usage: gem_bundler [OPTIONS] [PATH]"
op.on("-m", "--manifest MANIFEST") do |manifest|
options[:manifest] = manifest
end
op.on_tail("-h", "--help", "Show this message") do
puts op
exit
end
end
parser.parse!
options[:path] = ARGV.shift
unless options[:path]
puts parser
puts %(
[PATH] must be specified
)
exit
end
unless options[:manifest] && File.exist?(options[:manifest])
puts parser
puts %(
MANIFEST must be a valid manifest file
)
exit
end
Bundler.run(options)

@ -1,24 +1,34 @@
require 'pathname'
require 'logger'
require 'set'
require 'erb'
# Required elements of rubygems
require "rubygems/remote_fetcher"
require "rubygems/installer"
require "bundler/gem_bundle"
require "bundler/installer"
require "bundler/source"
require "bundler/finder"
require "bundler/gem_specification"
require "bundler/gem_ext"
require "bundler/resolver"
require "bundler/manifest"
require "bundler/dependency"
require "bundler/runtime"
require "bundler/environment"
require "bundler/dsl"
require "bundler/cli"
require "bundler/repository"
require "bundler/dependency"
module Bundler
VERSION = "0.5.0"
def self.run(options = {})
manifest = ManifestBuilder.load(options[:path], options[:manifest])
manifest.install
class << self
attr_writer :logger
def logger
@logger ||= begin
logger = Logger.new(STDOUT, Logger::INFO)
logger.formatter = proc {|_,_,_,msg| "#{msg}\n" }
logger
end
end
end
end

@ -1,24 +1,44 @@
require "optparse"
module Bundler
module CLI
def default_manifest
current = Pathname.new(Dir.pwd)
begin
manifest = current.join("Gemfile")
return manifest.to_s if File.exist?(manifest)
current = current.parent
end until current.root?
nil
class CLI
def self.run(command, options = {})
new(options).run(command)
rescue DefaultManifestNotFound => e
Bundler.logger.error "Could not find a Gemfile to use"
exit 2
rescue InvalidEnvironmentName => e
Bundler.logger.error "Gemfile error: #{e.message}"
exit
rescue InvalidRepository => e
Bundler.logger.error e.message
exit
rescue VersionConflict => e
Bundler.logger.error e.message
exit
rescue GemNotFound => e
Bundler.logger.error e.message
exit
end
module_function :default_manifest
def default_path
Pathname.new(File.dirname(default_manifest)).join("vendor").join("gems").to_s
def initialize(options)
@options = options
@manifest = Bundler::Environment.load(@options[:manifest])
end
module_function :default_path
def bundle
@manifest.install(@options[:update])
end
def exec
@manifest.setup_environment
# w0t?
super(*@options[:args])
end
def run(command)
send(command)
end
end
end

@ -0,0 +1,31 @@
class Gem::Commands::BundleCommand < Gem::Command
def initialize
super('bundle', 'Create a gem bundle based on your Gemfile', {:manifest => nil, :update => false})
add_option('-m', '--manifest MANIFEST', "Specify the path to the manifest file") do |manifest, options|
options[:manifest] = manifest
end
add_option('-u', '--update', "Force a remote check for newer gems") do
options[:update] = true
end
end
def usage
"#{program_name}"
end
def description # :nodoc:
<<-EOF
Bundle stuff
EOF
end
def execute
# Prevent the bundler from getting required unless it is actually being used
require 'bundler'
Bundler::CLI.run(:bundle, options)
end
end

@ -0,0 +1,31 @@
class Gem::Commands::ExecCommand < Gem::Command
def initialize
super('exec', 'Run a command in context of a gem bundle', {:manifest => nil})
add_option('-m', '--manifest MANIFEST', "Specify the path to the manifest file") do |manifest, options|
options[:manifest] = manifest
end
end
def usage
"#{program_name} COMMAND"
end
def arguments # :nodoc:
"COMMAND command to run in context of the gem bundle"
end
def description # :nodoc:
<<-EOF.gsub(' ', '')
Run in context of a bundle
EOF
end
def execute
# Prevent the bundler from getting required unless it is actually being used
require 'bundler'
Bundler::CLI.run(:exec, options)
end
end

@ -1,9 +1,10 @@
module Bundler
class Dependency
class InvalidEnvironmentName < StandardError; end
class Dependency
attr_reader :name, :version, :require_as, :only, :except
def initialize(name, options = {})
def initialize(name, options = {}, &block)
options.each do |k, v|
options[k.to_s] = v
end
@ -11,8 +12,13 @@ def initialize(name, options = {})
@name = name
@version = options["version"] || ">= 0"
@require_as = Array(options["require_as"] || name)
@only = Array(options["only"]).map {|e| e.to_s } if options["only"]
@except = Array(options["except"]).map {|e| e.to_s } if options["except"]
@only = options["only"]
@except = options["except"]
@block = block
if (@only && @only.include?("rubygems")) || (@except && @except.include?("rubygems"))
raise InvalidEnvironmentName, "'rubygems' is not a valid environment name"
end
end
def in?(environment)
@ -27,9 +33,24 @@ def to_s
to_gem_dependency.to_s
end
def require(environment)
return unless in?(environment)
@require_as.each do |file|
super(file)
end
@block.call if @block
end
def to_gem_dependency
@gem_dep ||= Gem::Dependency.new(name, version)
end
def ==(o)
[name, version, require_as, only, except] ==
[o.name, o.version, o.require_as, o.only, o.except]
end
end
end

@ -0,0 +1,109 @@
module Bundler
class ManifestFileNotFound < StandardError; end
class Dsl
def initialize(environment)
@environment = environment
@sources = Hash.new { |h,k| h[k] = {} }
end
def bundle_path(path)
path = Pathname.new(path)
@environment.gem_path = (path.relative? ?
@environment.root.join(path) : path).expand_path
end
def bin_path(path)
path = Pathname.new(path)
@environment.bindir = (path.relative? ?
@environment.root.join(path) : path).expand_path
end
def disable_rubygems
@environment.rubygems = false
end
def disable_system_gems
@environment.system_gems = false
end
def source(source)
source = GemSource.new(:uri => source)
unless @environment.sources.include?(source)
@environment.add_source(source)
end
end
def only(env)
old, @only = @only, _combine_onlys(env)
yield
@only = old
end
def except(env)
old, @except = @except, _combine_excepts(env)
yield
@except = old
end
def clear_sources
@environment.clear_sources
end
def gem(name, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
version = args.last
options[:only] = _combine_onlys(options[:only] || options["only"])
options[:except] = _combine_excepts(options[:except] || options["except"])
dep = Dependency.new(name, options.merge(:version => version))
# OMG REFACTORZ. KTHX
if vendored_at = options[:vendored_at]
vendored_at = Pathname.new(vendored_at)
vendored_at = @environment.filename.dirname.join(vendored_at) if vendored_at.relative?
@sources[:directory][vendored_at.to_s] ||= begin
source = DirectorySource.new(
:name => name,
:version => version,
:location => vendored_at
)
@environment.add_priority_source(source)
true
end
elsif git = options[:git]
@sources[:git][git] ||= begin
source = GitSource.new(
:name => name,
:version => version,
:uri => git,
:ref => options[:commit] || options[:tag],
:branch => options[:branch]
)
@environment.add_priority_source(source)
true
end
end
@environment.dependencies << dep
end
private
def _combine_onlys(only)
return @only unless only
only = [only].flatten.compact.uniq.map { |o| o.to_s }
only &= @only if @only
only
end
def _combine_excepts(except)
return @except unless except
except = [except].flatten.compact.uniq.map { |o| o.to_s }
except |= @except if @except
except
end
end
end

@ -0,0 +1,111 @@
require "rubygems/source_index"
module Bundler
class DefaultManifestNotFound < StandardError; end
class Environment
attr_reader :filename, :dependencies
attr_accessor :rubygems, :system_gems, :gem_path, :bindir
def self.load(gemfile = nil)
gemfile = gemfile ? Pathname.new(gemfile) : default_manifest_file
unless gemfile.file?
raise ManifestFileNotFound, "#{filename.inspect} does not exist"
end
new(gemfile)
end
def self.default_manifest_file
current = Pathname.new(Dir.pwd)
until current.root?
filename = current.join("Gemfile")
return filename if filename.exist?
current = current.parent
end
raise DefaultManifestNotFound
end
def initialize(filename) #, sources, dependencies, bindir, path, rubygems, system_gems)
@filename = filename
@default_sources = [GemSource.new(:uri => "http://gems.rubyforge.org")]
@sources = []
@priority_sources = []
@dependencies = []
@rubygems = true
@system_gems = true
# Evaluate the Gemfile
builder = Dsl.new(self)
builder.instance_eval(File.read(filename))
end
def install(update = false)
begin
tmp_path = filename.dirname.join(".tmp")
FileUtils.mkdir_p(tmp_path)
sources.each { |s| s.tmp_path = tmp_path }
repository.install(gem_dependencies, sources,
:rubygems => rubygems,
:system_gems => system_gems,
:manifest => filename,
:update => update
)
ensure
FileUtils.rm_rf(tmp_path)
end
Bundler.logger.info "Done."
end
def setup_environment
unless system_gems
ENV["GEM_HOME"] = gem_path
ENV["GEM_PATH"] = gem_path
end
ENV["PATH"] = "#{bindir}:#{ENV["PATH"]}"
ENV["RUBYOPT"] = "-r#{gem_path}/environment #{ENV["RUBYOPT"]}"
end
def root
filename.parent
end
def gem_path
@gem_path ||= root.join("vendor", "gems")
end
def bindir
@bindir ||= root.join("bin")
end
def sources
@priority_sources + @sources + @default_sources
end
def add_source(source)
@sources << source
end
def add_priority_source(source)
@priority_sources << source
end
def clear_sources
@sources.clear
@default_sources.clear
end
private
def repository
@repository ||= Repository.new(gem_path, bindir)
end
def gem_dependencies
@gem_dependencies ||= dependencies.map { |d| d.to_gem_dependency }
end
end
end

@ -1,42 +1,51 @@
module Bundler
# Finder behaves like a rubygems source index in that it responds
# to #search. It also resolves a list of dependencies finding the
# best possible configuration of gems that satisifes all requirements
# without causing any gem activation errors.
class Finder
# Takes an array of gem sources and fetches the full index of
# gems from each one. It then combines the indexes together keeping
# track of the original source so that any resolved gem can be
# fetched from the correct source.
#
# ==== Parameters
# *sources<String>:: URI pointing to the gem repository
def initialize(*sources)
@results = {}
@index = Hash.new { |h,k| h[k] = {} }
sources.each { |source| fetch(source) }
end
def resolve(*dependencies)
resolved = Resolver.resolve(dependencies, self)
resolved && GemBundle.new(resolved.all_specs)
end
def fetch(source)
deflated = Gem::RemoteFetcher.fetcher.fetch_path("#{source}/Marshal.4.8.Z")
inflated = Gem.inflate deflated
append(Marshal.load(inflated), source)
rescue Gem::RemoteFetcher::FetchError => e
raise ArgumentError, "#{source} is not a valid source: #{e.message}"
end
def append(index, source)
index.gems.values.each do |spec|
next unless Gem::Platform.match(spec.platform)
spec.source = source
@index[spec.name][spec.version] ||= spec
end
self
@cache = {}
@index = {}
@sources = sources
end
# Searches for a gem that matches the dependency
#
# ==== Parameters
# dependency<Gem::Dependency>:: The gem dependency to search for
#
# ==== Returns
# [Gem::Specification]:: A collection of gem specifications
# matching the search
def search(dependency)
@results[dependency.hash] ||= begin
possibilities = @index[dependency.name].values
possibilities.select do |spec|
@cache[dependency.hash] ||= begin
find_by_name(dependency.name).select do |spec|
dependency =~ spec
end.sort_by {|s| s.version }
end
end
private
def find_by_name(name)
matches = @index[name] ||= begin
versions = {}
@sources.reverse_each do |source|
versions.merge! source.specs[name] || {}
end
versions
end
matches.values
end
end
end

@ -1,22 +1,10 @@
module Bundler
class GemBundle < Array
def download(directory)
FileUtils.mkdir_p(directory)
current = Dir[File.join(directory, "cache", "*.gem*")]
each do |spec|
cached = File.join(directory, "cache", "#{spec.full_name}.gem")
unless File.file?(cached)
Gem::RemoteFetcher.fetcher.download(spec, spec.source, directory)
def download(repository)
sort_by {|s| s.full_name.downcase }.each do |spec|
spec.source.download(spec, repository)
end
current.delete(cached)
end
current.each { |file| File.delete(file) }
self
end
end

@ -0,0 +1,25 @@
module Gem
class Installer
def app_script_text(bin_file_name)
path = @gem_home
template = File.read(File.join(File.dirname(__FILE__), "templates", "app_script.erb"))
erb = ERB.new(template, nil, '-')
erb.result(binding)
end
end
class Specification
attr_accessor :source
attr_accessor :location
# Hack to fix github's strange marshal file
def specification_version
@specification_version && @specification_version.to_i
end
alias full_gem_path_without_location full_gem_path
def full_gem_path
@location ? @location : full_gem_path_without_location
end
end
end

@ -1,10 +0,0 @@
module Gem
class Specification
attribute :source
def source=(source)
@source = source.is_a?(URI) ? source : URI.parse(source)
raise ArgumentError, "The source must be an absolute URI" unless @source.absolute?
end
end
end

@ -1,44 +0,0 @@
module Bundler
class Installer
def initialize(path)
if !File.directory?(path)
raise ArgumentError, "#{path} is not a directory"
elsif !File.directory?(File.join(path, "cache"))
raise ArgumentError, "#{path} is not a valid environment (it does not contain a cache directory)"
end
@path = path
@gems = Dir[(File.join(path, "cache", "*.gem"))]
end
def install(options = {})
bin_dir = options[:bin_dir] ||= File.join(@path, "bin")
specs = Dir[File.join(@path, "specifications", "*.gemspec")]
gems = Dir[File.join(@path, "gems", "*")]
@gems.each do |gem|
name = File.basename(gem).gsub(/\.gem$/, '')
installed = specs.any? { |g| File.basename(g) == "#{name}.gemspec" } &&
gems.any? { |g| File.basename(g) == name }
unless installed
installer = Gem::Installer.new(gem, :install_dir => @path,
:ignore_dependencies => true,
:env_shebang => true,
:wrappers => true,
:bin_dir => bin_dir)
installer.install
end
# remove this spec
specs.delete_if { |g| File.basename(g) == "#{name}.gemspec"}
gems.delete_if { |g| File.basename(g) == name }
end
(specs + gems).each do |path|
FileUtils.rm_rf(path)
end
end
end
end

@ -1,130 +0,0 @@
require "rubygems/source_index"
require "pathname"
module Bundler
class VersionConflict < StandardError; end
class Manifest
attr_reader :sources, :dependencies, :path
def initialize(sources, dependencies, path)
sources.map! {|s| s.is_a?(URI) ? s : URI.parse(s) }
@sources, @dependencies, @path = sources, dependencies, Pathname.new(path)
end
def fetch
return if all_gems_installed?
finder = Finder.new(*sources)
unless bundle = finder.resolve(*gem_dependencies)
gems = @dependencies.map {|d| " #{d.to_s}" }.join("\n")
raise VersionConflict, "No compatible versions could be found for:\n#{gems}"
end
bundle.download(@path)
end
def install(options = {})
fetch
installer = Installer.new(@path)
installer.install # options come here
create_load_paths_files(File.join(@path, "environments"))
create_fake_rubygems(File.join(@path, "environments"))
end
def activate(environment = "default")
require File.join(@path, "environments", "#{environment}.rb")
end
def require_all
dependencies.each do |dep|
dep.require_as.each {|file| require file }
end
end
def gems_for(environment)
deps = dependencies.select { |d| d.in?(environment) }
deps.map! { |d| d.to_gem_dependency }
index = Gem::SourceIndex.from_gems_in(File.join(@path, "specifications"))
Resolver.resolve(deps, index).all_specs
end
def environments
envs = dependencies.map {|dep| Array(dep.only) + Array(dep.except) }.flatten
envs << "default"
end
private
def gem_dependencies
@gem_dependencies ||= dependencies.map { |d| d.to_gem_dependency }
end
def all_gems_installed?
gem_versions = {}
Dir[File.join(@path, "cache", "*.gem")].each do |file|
file =~ /\/([^\/]+)-([\d\.]+)\.gem$/
name, version = $1, $2
gem_versions[name] = Gem::Version.new(version)
end
gem_dependencies.all? do |dep|
gem_versions[dep.name] &&
dep.version_requirements.satisfied_by?(gem_versions[dep.name])
end
end
def create_load_paths_files(path)
FileUtils.mkdir_p(path)
environments.each do |environment|
gem_specs = gems_for(environment)
File.open(File.join(path, "#{environment}.rb"), "w") do |file|
file.puts <<-RUBY_EVAL
module Bundler
def self.rubygems_required
#{create_gem_stubs(path, gem_specs)}
end
end
RUBY_EVAL
file.puts "$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))"
load_paths_for_specs(gem_specs).each do |load_path|
file.puts "$LOAD_PATH.unshift #{load_path.inspect}"
end
end
end
end
def create_gem_stubs(path, gem_specs)
gem_specs.map do |spec|
path = File.expand_path(File.join(path, '..', 'specifications', "#{spec.full_name}.gemspec"))
%{
Gem.loaded_specs["#{spec.name}"] = eval(File.read("#{path}"))
}
end.join("\n")
end
def create_fake_rubygems(path)
File.open(File.join(path, "rubygems.rb"), "w") do |file|
file.puts <<-RUBY_EVAL
$:.delete File.expand_path(File.dirname(__FILE__))
load "rubygems.rb"
if defined?(Bundler) && Bundler.respond_to?(:rubygems_required)
Bundler.rubygems_required
end
RUBY_EVAL
end
end
def load_paths_for_specs(specs)
load_paths = []
specs.each do |spec|
load_paths << File.join(spec.full_gem_path, spec.bindir) if spec.bindir
spec.require_paths.each do |path|
load_paths << File.join(spec.full_gem_path, path)
end
end
load_paths
end
end
end

@ -0,0 +1,151 @@
require "bundler/repository/gem_repository"
require "bundler/repository/directory_repository"
module Bundler
class InvalidRepository < StandardError ; end
class Repository
attr_reader :path
def initialize(path, bindir)
FileUtils.mkdir_p(path)
@path = Pathname.new(path)
@bindir = Pathname.new(bindir)
@repos = {
:gem => Gems.new(@path, @bindir),
:directory => Directory.new(@path.join("dirs"), @bindir)
}
end
def install(dependencies, sources, options = {})
if options[:update] || !satisfies?(dependencies)
fetch(dependencies, sources)
expand(options)
else
# Remove any gems that are still around if the Gemfile changed without
# requiring new gems to be download (e.g. a line in the Gemfile was
# removed)
cleanup(Resolver.resolve(dependencies, [source_index]))
end
configure(options)
sync
end
def gems
gems = []
each_repo do |repo|
gems.concat repo.gems
end
gems
end
def satisfies?(dependencies)
index = source_index
dependencies.all? { |dep| index.search(dep).size > 0 }
end
def source_index
index = Gem::SourceIndex.new
each_repo do |repo|
index.gems.merge!(repo.source_index.gems)
end
index
end
def add_spec(type, spec)
@repos[type].add_spec(spec)
end
def download_path_for(type)
@repos[type].download_path_for
end
private
def cleanup(bundle)
each_repo do |repo|
repo.cleanup(bundle)
end
end
def each_repo
@repos.each do |k, repo|
yield repo
end
end
def fetch(dependencies, sources)
bundle = Resolver.resolve(dependencies, sources)
# Cleanup here to remove any gems that could cause problem in the expansion
# phase
#
# TODO: Try to avoid double cleanup
cleanup(bundle)
bundle.download(self)
end
def sync
glob = gems.map { |g| g.executables }.flatten.join(',')
(Dir[@bindir.join("*")] - Dir[@bindir.join("{#{glob}}")]).each do |file|
Bundler.logger.info "Deleting bin file: #{File.basename(file)}"
FileUtils.rm_rf(file)
end
end
def expand(options)
each_repo do |repo|
repo.expand(options)
end
end
def configure(options)
generate_environment(options)
end
def generate_environment(options)
FileUtils.mkdir_p(path)
specs = gems
load_paths = load_paths_for_specs(specs)
bindir = @bindir.relative_path_from(path).to_s
filename = options[:manifest].relative_path_from(path).to_s
spec_files = specs.inject({}) do |hash, spec|
relative = spec.loaded_from.relative_path_from(@path).to_s
hash.merge!(spec.name => relative)
end
File.open(path.join("environment.rb"), "w") do |file|
template = File.read(File.join(File.dirname(__FILE__), "templates", "environment.erb"))
erb = ERB.new(template, nil, '-')
file.puts erb.result(binding)
end
end
def load_paths_for_specs(specs)
load_paths = []
specs.each do |spec|
gem_path = Pathname.new(spec.full_gem_path)
if spec.bindir
load_paths << gem_path.join(spec.bindir).relative_path_from(@path).to_s
end
spec.require_paths.each do |path|
load_paths << gem_path.join(path).relative_path_from(@path).to_s
end
end
load_paths
end
def require_code(file, dep)
constraint = case
when dep.only then %{ if #{dep.only.inspect}.include?(env)}
when dep.except then %{ unless #{dep.except.inspect}.include?(env)}
end
"require #{file.inspect}#{constraint}"
end
end
end

@ -0,0 +1,46 @@
module Bundler
class Repository
class Directory
attr_reader :path, :bindir
def initialize(path, bindir)
@path = path
@bindir = bindir
FileUtils.mkdir_p(path.to_s)
end
def source_index
index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
index
end
def gems
source_index.gems.values
end
def add_spec(spec)
destination = path.join('specifications')
destination.mkdir unless destination.exist?
File.open(destination.join("#{spec.full_name}.gemspec"), 'w') do |f|
f.puts spec.to_ruby
end
end
def download_path_for
@path.join("dirs")
end
# Checks whether a gem is installed
def expand(options)
# raise NotImplementedError
end
def cleanup(gems)
# raise NotImplementedError
end
end
end
end

@ -0,0 +1,108 @@
module Bundler
class Repository
class Gems
attr_reader :path, :bindir
def initialize(path, bindir)
@path = path
@bindir = bindir
end
# Returns the source index for all gems installed in the
# repository
def source_index
index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
index
end
def gems
source_index.gems.values
end
# Checks whether a gem is installed
def expand(options)
cached_gems.each do |name, version|
unless installed?(name, version)
install_cached_gem(name, version, options)
end
end
end
def cleanup(gems)
glob = gems.map { |g| g.full_name }.join(',')
base = path.join("{cache,specifications,gems}")
(Dir[base.join("*")] - Dir[base.join("{#{glob}}{.gemspec,.gem,}")]).each do |file|
if File.basename(file) =~ /\.gem$/
name = File.basename(file, '.gem')
Bundler.logger.info "Deleting gem: #{name}"
end
FileUtils.rm_rf(file)
end
end
def add_spec(spec)
raise NotImplementedError
end
def download_path_for
path
end
private
def cache_path
@path.join("cache")
end
def cache_files
Dir[cache_path.join("*.gem")]
end
def cached_gems
cache_files.map do |f|
full_name = File.basename(f).gsub(/\.gem$/, '')
full_name.split(/-(?=[^-]+$)/)
end
end
def spec_path
@path.join("specifications")
end
def spec_files
Dir[spec_path.join("*.gemspec")]
end
def gem_path
@path.join("gems")
end
def gem_paths
Dir[gem_path.join("*")]
end
def installed?(name, version)
spec_files.any? { |g| File.basename(g) == "#{name}-#{version}.gemspec" } &&
gem_paths.any? { |g| File.basename(g) == "#{name}-#{version}" }
end
def install_cached_gem(name, version, options = {})
cached_gem = cache_path.join("#{name}-#{version}.gem")
# TODO: Add a warning if cached_gem is not a file
if cached_gem.file?
Bundler.logger.info "Installing #{name}-#{version}.gem"
installer = Gem::Installer.new(cached_gem.to_s, options.merge(
:install_dir => @path,
:ignore_dependencies => true,
:env_shebang => true,
:wrappers => true,
:bin_dir => @bindir
))
installer.install
end
end
end
end
end

@ -1,19 +1,189 @@
require 'bundler/resolver/inspect'
require 'bundler/resolver/search'
require 'bundler/resolver/engine'
require 'bundler/resolver/stack'
require 'bundler/resolver/state'
# This is the latest iteration of the gem dependency resolving algorithm. As of now,
# it can resolve (as a success of failure) any set of gem dependencies we throw at it
# in a reasonable amount of time. The most iterations I've seen it take is about 150.
# The actual implementation of the algorithm is not as good as it could be yet, but that
# can come later.
# Extending Gem classes to add necessary tracking information
module Gem
class Dependency
def required_by
@required_by ||= []
end
end
class Specification
def required_by
@required_by ||= []
end
end
end
module Bundler
module Resolver
def self.resolve(deps, source_index = Gem.source_index, logger = nil)
unless logger
logger = Logger.new($stderr)
logger.datetime_format = ""
logger.level = ENV["GEM_RESOLVER_DEBUG"] ? Logger::DEBUG : Logger::ERROR
class GemNotFound < StandardError; end
class VersionConflict < StandardError; end
class Resolver
attr_reader :errors
# Figures out the best possible configuration of gems that satisfies
# the list of passed dependencies and any child dependencies without
# causing any gem activation errors.
#
# ==== Parameters
# *dependencies<Gem::Dependency>:: The list of dependencies to resolve
#
# ==== Returns
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
# collection of gemspecs is returned. Otherwise, nil is returned.
def self.resolve(requirements, sources)
Bundler.logger.info "Calculating dependencies..."
resolver = new(sources)
result = catch(:success) do
resolver.resolve(requirements, {})
output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
o << " Conflict on: #{conflict.inspect}:\n"
o << " * #{conflict} (#{origin.version}) activated by #{origin.required_by.first}\n"
o << " * #{requirement} required by #{requirement.required_by.first}\n"
o << " All possible versions of origin requirements conflict."
end
raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
nil
end
result && GemBundle.new(result.values)
end
Engine.resolve(deps, source_index, logger)
def initialize(sources)
@errors = {}
@stack = []
@specs = Hash.new { |h,k| h[k] = {} }
@cache = {}
@index = {}
sources.reverse_each do |source|
source.gems.values.each do |spec|
# TMP HAX FOR OPTZ
spec.source = source
next unless Gem::Platform.match(spec.platform)
@specs[spec.name][spec.version] = spec
end
end
end
def resolve(reqs, activated)
# If the requirements are empty, then we are in a success state. Aka, all
# gem dependencies have been resolved.
throw :success, activated if reqs.empty?
# Sort requirements so that the ones that are easiest to resolve are first.
# Easiest to resolve is defined by: Is this gem already activated? Otherwise,
# check the number of child dependencies this requirement has.
reqs = reqs.sort_by do |req|
activated[req.name] ? 0 : search(req).size
end
activated = activated.dup
# Pull off the first requirement so that we can resolve it
current = reqs.shift
# Check if the gem has already been activated, if it has, we will make sure
# that the currently activated gem satisfies the requirement.
if existing = activated[current.name]
if current.version_requirements.satisfied_by?(existing.version)
@errors.delete(existing.name)
# Since the current requirement is satisfied, we can continue resolving
# the remaining requirements.
resolve(reqs, activated)
else
@errors[existing.name] = [existing, current]
# Since the current requirement conflicts with an activated gem, we need
# to backtrack to the current requirement's parent and try another version
# of it (maybe the current requirement won't be present anymore). If the
# current requirement is a root level requirement, we need to jump back to
# where the conflicting gem was activated.
parent = current.required_by.last || existing.required_by.last
# We track the spot where the current gem was activated because we need
# to keep a list of every spot a failure happened.
throw parent.name, existing.required_by.last.name
end
else
# There are no activated gems for the current requirement, so we are going
# to find all gems that match the current requirement and try them in decending
# order. We also need to keep a set of all conflicts that happen while trying
# this gem. This is so that if no versions work, we can figure out the best
# place to backtrack to.
conflicts = Set.new
# Fetch all gem versions matching the requirement
#
# TODO: Warn / error when no matching versions are found.
matching_versions = search(current)
if matching_versions.empty?
if current.required_by.empty?
raise GemNotFound, "Could not find gem '#{current}' in any of the sources"
end
Bundler.logger.warn "Could not find gem '#{current}' (required by '#{current.required_by.last}') in any of the sources"
end
matching_versions.reverse_each do |spec|
conflict = resolve_requirement(spec, current, reqs.dup, activated.dup)
conflicts << conflict if conflict
end
# If the current requirement is a root level gem and we have conflicts, we
# can figure out the best spot to backtrack to.
if current.required_by.empty? && !conflicts.empty?
# Check the current "catch" stack for the first one that is included in the
# conflicts set. That is where the parent of the conflicting gem was required.
# By jumping back to this spot, we can try other version of the parent of
# the conflicting gem, hopefully finding a combination that activates correctly.
@stack.reverse_each do |savepoint|
if conflicts.include?(savepoint)
throw savepoint
end
end
end
end
end
def resolve_requirement(spec, requirement, reqs, activated)
# We are going to try activating the spec. We need to keep track of stack of
# requirements that got us to the point of activating this gem.
spec.required_by.replace requirement.required_by
spec.required_by << requirement
activated[spec.name] = spec
# Now, we have to loop through all child dependencies and add them to our
# array of requirements.
spec.dependencies.each do |dep|
next if dep.type == :development
dep.required_by << requirement
reqs << dep
end
# We create a savepoint and mark it by the name of the requirement that caused
# the gem to be activated. If the activated gem ever conflicts, we are able to
# jump back to this point and try another version of the gem.
length = @stack.length
@stack << requirement.name
retval = catch(requirement.name) do
resolve(reqs, activated)
end
# Since we're doing a lot of throw / catches. A push does not necessarily match
# up to a pop. So, we simply slice the stack back to what it was before the catch
# block.
@stack.slice!(length..-1)
retval
end
def search(dependency)
@cache[dependency.hash] ||= begin
@specs[dependency.name].values.select do |spec|
dependency =~ spec
end.sort_by {|s| s.version }
end
end
end
end

@ -1,61 +0,0 @@
module Bundler
module Resolver
module Builders
def build_index(&block)
index = Gem::SourceIndex.new
IndexBuilder.run(index, &block) if block_given?
index
end
def build_spec(name, version, &block)
spec = Gem::Specification.new
spec.instance_variable_set(:@name, name)
spec.instance_variable_set(:@version, Gem::Version.new(version))
DepBuilder.run(spec, &block) if block_given?
spec
end
def build_dep(name, requirements, type = :runtime)
Gem::Dependency.new(name, requirements, type)
end
class IndexBuilder
include Builders
def self.run(index, &block)
new(index).run(&block)
end
def initialize(index)
@index = index
end
def run(&block)
instance_eval(&block)
end
def add_spec(*args, &block)
@index.add_spec(build_spec(*args, &block))
end
end
class DepBuilder
def self.run(spec, &block)
new(spec).run(&block)
end
def initialize(spec)
@spec = spec
end
def run(&block)
instance_eval(&block)
end
def runtime(name, requirements)
@spec.add_runtime_dependency(name, requirements)
end
end
end
end
end

@ -1,38 +0,0 @@
module Bundler
module Resolver
class ClosedSet < Set
end
class Engine
include Search, Inspect
def self.resolve(deps, source_index, logger)
new(deps, source_index, logger).resolve
end
def initialize(deps, source_index, logger)
@deps, @source_index, @logger = deps, source_index, logger
logger.debug "searching for #{gem_resolver_inspect(@deps)}"
end
attr_reader :deps, :source_index, :logger, :solution
def resolve
state = State.initial(self, [], Stack.new, Stack.new([[[], @deps.dup]]))
if solution = search(state)
logger.info "got the solution with #{solution.all_specs.size} specs"
solution.dump(Logger::INFO)
solution
end
end
def open
@open ||= []
end
def closed
@closed ||= ClosedSet.new
end
end
end
end

@ -1,24 +0,0 @@
module Bundler
module Resolver
module Inspect
def gem_resolver_inspect(o)
case o
when Gem::Specification
"#<Spec: #{o.full_name}>"
when Array
'[' + o.map {|x| gem_resolver_inspect(x)}.join(", ") + ']'
when Set
gem_resolver_inspect(o.to_a)
when Hash
'{' + o.map {|k,v| "#{gem_resolver_inspect(k)} => #{gem_resolver_inspect(v)}"}.join(", ") + '}'
when Stack
o.gem_resolver_inspect
else
o.inspect
end
end
module_function :gem_resolver_inspect
end
end
end

@ -1,71 +0,0 @@
module Bundler
module Resolver
module Search
def search(initial, max_depth = (1.0 / 0.0))
if initial.goal_met?
return initial
end
open << initial
while open.any?
current = open.pop
closed << current
new = []
current.each_possibility do |attempt|
unless closed.include?(attempt)
if attempt.goal_met?
return attempt
elsif attempt.depth < max_depth
new << attempt
end
end
end
new.reverse.each do |state|
open << state
end
end
nil
end
def open
raise "implement #open in #{self.class}"
end
def closed
raise "implement #closed in #{self.class}"
end
module Node
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def initial(*data)
new(0, *data)
end
end
def initialize(depth)
@depth = depth
end
attr_reader :depth
def child(*data)
self.class.new(@depth + 1, *data)
end
def each_possibility
raise "implement #each_possibility on #{self.class}"
end
def goal_met?
raise "implement #goal_met? on #{self.class}"
end
end
end
end
end

@ -1,72 +0,0 @@
module Bundler
module Resolver
class Stack
def initialize(initial = [])
@data = []
initial.each do |(path,value)|
self[path] = value
end
end
def last
@data.last
end
def []=(path, value)
raise ArgumentError, "#{path.inspect} already has a value" if key?(path)
@data << [path.dup, value]
end
def [](path)
if key?(path)
_, value = @data.find do |(k,v)|
k == path
end
value
else
raise "No value for #{path.inspect}"
end
end
def key?(path)
@data.any? do |(k,v)|
k == path
end
end
def each
@data.each do |(k,v)|
yield k, v
end
end
def map
@data.map do |(k,v)|
yield k, v
end
end
def each_value
@data.each do |(k,v)|
yield v
end
end
def dup
self.class.new(@data.dup)
end
def to_s
@data.to_s
end
def inspect
@data.inspect
end
def gem_resolver_inspect
Inspect.gem_resolver_inspect(@data)
end
end
end
end

@ -1,172 +0,0 @@
module Bundler
module Resolver
class State
include Search::Node, Inspect
def initialize(depth, engine, path, spec_stack, dep_stack)
super(depth)
@engine, @path, @spec_stack, @dep_stack = engine, path, spec_stack, dep_stack
end
attr_reader :path
def logger
@engine.logger
end
def goal_met?
logger.info "checking if goal is met"
dump
no_duplicates?
all_deps.all? do |dep|
dependency_satisfied?(dep)
end
end
def no_duplicates?
names = []
all_specs.each do |s|
if names.include?(s.name)
raise "somehow got duplicates for #{s.name}"
end
names << s.name
end
end
def dependency_satisfied?(dep)
all_specs.any? do |spec|
spec.satisfies_requirement?(dep)
end
end
def each_possibility(&block)
index, dep = remaining_deps.first
if dep
logger.warn "working on #{dep} for #{spec_name}"
handle_dep(index, dep, &block)
else
logger.warn "no dependencies left for #{spec_name}"
jump_to_parent(&block)
end
end
def handle_dep(index, dep)
specs = @engine.source_index.search(dep)
specs.reverse.each do |s|
logger.info "attempting with spec: #{s.full_name}"
new_path = @path + [index]
new_spec_stack = @spec_stack.dup
new_dep_stack = @dep_stack.dup
new_spec_stack[new_path] = s
new_dep_stack[new_path] = s.runtime_dependencies.sort_by do |dep|
@engine.source_index.search(dep).size
end
yield child(@engine, new_path, new_spec_stack, new_dep_stack)
end
end
def jump_to_parent
if @path.empty?
dump
logger.warn "at the end"
return
end
logger.info "jumping to parent for #{spec_name}"
new_path = @path[0..-2]
new_spec_stack = @spec_stack.dup
new_dep_stack = @dep_stack.dup
yield child(@engine, new_path, new_spec_stack, new_dep_stack)
end
def remaining_deps
remaining_deps_for(@path)
end
def remaining_deps_for(path)
no_duplicates?
remaining = []
@dep_stack[path].each_with_index do |dep,i|
remaining << [i, dep] unless all_specs.find {|s| s.name == dep.name}
end
remaining
end
def deps
@dep_stack[@path]
end
def spec
@spec_stack[@path]
end
def spec_name
@path.empty? ? "<top>" : spec.full_name
end
def all_deps
all_deps = Set.new
@dep_stack.each_value do |deps|
all_deps.merge(deps)
end
all_deps.to_a
end
def all_specs
@spec_stack.map do |path,spec|
spec
end
end
def dump(level = Logger::DEBUG)
logger.add level, "v" * 80
logger.add level, "path: #{@path.inspect}"
logger.add level, "deps: (#{deps.size})"
deps.map do |dep|
logger.add level, gem_resolver_inspect(dep)
end
logger.add level, "remaining_deps: (#{remaining_deps.size})"
remaining_deps.each do |dep|
logger.add level, gem_resolver_inspect(dep)
end
logger.add level, "dep_stack: "
@dep_stack.each do |path,deps|
logger.add level, "#{path.inspect} (#{deps.size})"
deps.each do |dep|
logger.add level, "-> #{gem_resolver_inspect(dep)}"
end
end
logger.add level, "spec_stack: "
@spec_stack.each do |path,spec|
logger.add level, "#{path.inspect}: #{gem_resolver_inspect(spec)}"
end
logger.add level, "^" * 80
end
def to_dot
io = StringIO.new
io.puts 'digraph deps {'
io.puts ' fontname = "Courier";'
io.puts ' mincross = 4.0;'
io.puts ' ratio = "auto";'
dump_to_dot(io, "<top>", [])
io.puts '}'
io.string
end
def dump_to_dot(io, name, path)
@dep_stack[path].each_with_index do |dep,i|
new_path = path + [i]
spec_name = all_specs.find {|x| x.name == dep.name}.full_name
io.puts ' "%s" -> "%s";' % [name, dep.to_s]
io.puts ' "%s" -> "%s";' % [dep.to_s, spec_name]
if @spec_stack.key?(new_path)
dump_to_dot(io, spec_name, new_path)
end
end
end
end
end
end

@ -1,39 +1,2 @@
module Bundler
class ManifestBuilder
attr_reader :sources
def self.build(path, string)
builder = new(path)
builder.instance_eval(string)
builder.to_manifest
end
def self.load(path, file)
string = File.read(file)
build(path, string)
end
def initialize(path)
@path = path
@sources = %w(http://gems.rubyforge.org)
@dependencies = []
end
def to_manifest
Manifest.new(@sources, @dependencies, @path)
end
def source(source)
@sources << source
end
def gem(name, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
version = args.last
@dependencies << Dependency.new(name, options.merge(:version => version))
end
end
end
require File.join(File.dirname(__FILE__), "runtime", "dsl")
require File.join(File.dirname(__FILE__), "runtime", "dependency")

@ -0,0 +1,150 @@
module Bundler
# Represents a source of rubygems. Initially, this is only gem repositories, but
# eventually, this will be git, svn, HTTP
class Source
attr_accessor :tmp_path
end
class GemSource < Source
attr_reader :uri
def initialize(options)
@uri = options[:uri]
@uri = URI.parse(@uri) unless @uri.is_a?(URI)
raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
end
def gems
@specs ||= fetch_specs
end
def ==(other)
uri == other.uri
end
def to_s
@uri.to_s
end
class RubygemsRetardation < StandardError; end
def download(spec, repository)
Bundler.logger.info "Downloading #{spec.full_name}.gem"
destination = repository.download_path_for(:gem)
unless destination.writable?
raise RubygemsRetardation
end
Gem::RemoteFetcher.fetcher.download(spec, uri, repository.download_path_for(:gem))
end
private
def fetch_specs
Bundler.logger.info "Updating source: #{to_s}"
deflated = Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/Marshal.4.8.Z")
inflated = Gem.inflate deflated
index = Marshal.load(inflated)
index.gems
rescue Gem::RemoteFetcher::FetchError => e
raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
end
end
class DirectorySource < Source
def initialize(options)
@name = options[:name]
@version = options[:version]
@location = options[:location]
@require_paths = options[:require_paths] || %w(lib)
end
def gems
@gems ||= begin
specs = {}
# Find any gemspec files in the directory and load those specs
Dir[@location.join('**', '*.gemspec')].each do |file|
path = Pathname.new(file).relative_path_from(@location).dirname
spec = eval(File.read(file))
spec.require_paths.map! { |p| path.join(p) }
specs[spec.full_name] = spec
end
# If a gemspec for the dependency was not found, add it to the list
if specs.keys.grep(/^#{Regexp.escape(@name)}/).empty?
case
when @version.nil?
raise ArgumentError, "If you use :at, you must specify the gem" \
"and version you wish to stand in for"
when !Gem::Version.correct?(@version)
raise ArgumentError, "If you use :at, you must specify a gem and" \
"version. You specified #{@version} for the version"
end
default = Gem::Specification.new do |s|
s.name = @name
s.version = Gem::Version.new(@version) if @version
end
specs[default.full_name] = default
end
specs
end
end
def ==(other)
# TMP HAX
other.is_a?(DirectorySource)
end
def to_s
"#{@name} (#{@version}) Located at: '#{@location}'"
end
def download(spec, repository)
spec.require_paths.map! { |p| File.join(@location, p) }
repository.add_spec(:directory, spec)
end
end
class GitSource < DirectorySource
def initialize(options)
super
@uri = options[:uri]
@ref = options[:ref]
@branch = options[:branch]
end
def gems
FileUtils.mkdir_p(tmp_path.join("gitz"))
# TMP HAX to get the *.gemspec reading to work
@location = tmp_path.join("gitz", @name)
Bundler.logger.info "Cloning git repository at: #{@uri}"
`git clone #{@uri} #{@location} --no-hardlinks`
if @ref
Dir.chdir(@location) { `git checkout #{@ref}` }
elsif @branch && @branch != "master"
Dir.chdir(@location) { `git checkout origin/#{@branch}` }
end
super
end
def download(spec, repository)
dest = repository.download_path_for(:directory).join(@name)
spec.require_paths.map! { |p| File.join(dest, p) }
repository.add_spec(:directory, spec)
if spec.name == @name
FileUtils.mkdir_p(dest.dirname)
FileUtils.mv(tmp_path.join("gitz", spec.name), dest)
end
end
end
end

@ -0,0 +1,3 @@
<%= shebang bin_file_name %>
require File.join(File.dirname(__FILE__), "<%= path.join("environment").relative_path_from(Pathname.new(bin_dir)) %>")
load File.join(File.dirname(__FILE__), "<%= path.join("gems", @spec.full_name, @spec.bindir, bin_file_name).relative_path_from(Pathname.new(bin_dir)) %>")

@ -0,0 +1,127 @@
# DO NOT MODIFY THIS FILE
module Bundler
dir = File.dirname(__FILE__)
<% unless options[:system_gems] -%>
ENV["GEM_HOME"] = dir
ENV["GEM_PATH"] = dir
<% end -%>
ENV["PATH"] = "#{dir}/<%= bindir %>:#{ENV["PATH"]}"
ENV["RUBYOPT"] = "-r#{__FILE__} #{ENV["RUBYOPT"]}"
<% load_paths.each do |load_path| -%>
$LOAD_PATH.unshift File.expand_path("#{dir}/<%= load_path %>")
<% end -%>
@gemfile = "#{dir}/<%= filename %>"
<% if options[:rubygems] -%>
require "rubygems"
@bundled_specs = {}
<% spec_files.each do |name, path| -%>
@bundled_specs["<%= name %>"] = eval(File.read("#{dir}/<%= path %>"))
@bundled_specs["<%= name %>"].loaded_from = "#{dir}/<%= path %>"
<% end -%>
def self.add_specs_to_loaded_specs
Gem.loaded_specs.merge! @bundled_specs
end
def self.add_specs_to_index
@bundled_specs.each do |name, spec|
Gem.source_index.add_spec spec
end
end
add_specs_to_loaded_specs
add_specs_to_index
<% end -%>
def self.require_env(env = nil)
context = Class.new do
def initialize(env) @env = env && env.to_s ; end
def method_missing(*) ; end
def only(env)
old, @only = @only, _combine_onlys(env)
yield
@only = old
end
def except(env)
old, @except = @except, _combine_excepts(env)
yield
@except = old
end
def gem(name, *args)
opt = args.last || {}
only = _combine_onlys(opt[:only] || opt["only"])
except = _combine_excepts(opt[:except] || opt["except"])
files = opt[:require_as] || opt["require_as"] || name
return unless !only || only.any? {|e| e == @env }
return if except && except.any? {|e| e == @env }
files.each { |f| require f }
yield if block_given?
true
end
private
def _combine_onlys(only)
return @only unless only
only = [only].flatten.compact.uniq.map { |o| o.to_s }
only &= @only if @only
only
end
def _combine_excepts(except)
return @except unless except
except = [except].flatten.compact.uniq.map { |o| o.to_s }
except |= @except if @except
except
end
end
context.new(env && env.to_s).instance_eval(File.read(@gemfile))
end
end
<% if options[:rubygems] -%>
module Gem
def source_index.refresh!
super
Bundler.add_specs_to_index
end
end
<% else -%>
$" << "rubygems.rb"
module Kernel
def gem(*)
# Silently ignore calls to gem, since, in theory, everything
# is activated correctly already.
end
end
# Define all the Gem errors for gems that reference them.
module Gem
def self.ruby ; <%= Gem.ruby.inspect %> ; end
class LoadError < ::LoadError; end
class Exception < RuntimeError; end
class CommandLineError < Exception; end
class DependencyError < Exception; end
class DependencyRemovalException < Exception; end
class GemNotInHomeException < Exception ; end
class DocumentError < Exception; end
class EndOfYAMLException < Exception; end
class FilePermissionError < Exception; end
class FormatException < Exception; end
class GemNotFoundException < Exception; end
class InstallError < Exception; end
class InvalidSpecificationException < Exception; end
class OperationNotSupportedError < Exception; end
class RemoteError < Exception; end
class RemoteInstallationCancelled < Exception; end
class RemoteInstallationSkipped < Exception; end
class RemoteSourceException < Exception; end
class VerificationError < Exception; end
class SystemExitException < SystemExit; end
end
<% end -%>