bundler 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

@@ -12,12 +12,6 @@ and all child dependencies specified in this manifest. It can manage any update
12
12
  to the gem manifest file and update the bundled gems accordingly. It also lets
13
13
  you run any ruby code in context of the bundled gem environment.
14
14
 
15
- ## Disclaimer
16
-
17
- This project is under rapid development. It is usable today, but there will be
18
- many changes in the near future, including to the Gemfile DSL. We will bump up
19
- versions with changes though. We greatly appreciate feedback.
20
-
21
15
  ## Installation
22
16
 
23
17
  Bundler has no dependencies. Just clone the git repository and install the gem
@@ -25,6 +19,10 @@ with the following rake task:
25
19
 
26
20
  rake install
27
21
 
22
+ You can also install the gem with
23
+
24
+ gem install bundler
25
+
28
26
  ## Usage
29
27
 
30
28
  Bundler requires a gem manifest file to be created. This should be a file named
@@ -50,11 +48,30 @@ information, please refer to Bundler::ManifestBuilder.
50
48
  # it can be specified the same way as with rubygems' #gem method.
51
49
  gem "rack", "1.0.0"
52
50
 
53
- # Specify a dependency rspec, but only activate that gem in the "testing"
54
- # environment (read more about environments later). :except is also a valid
55
- # option to specify environment restrictions.
51
+ # Specify a dependency rspec, but only require that gem in the "testing"
52
+ # environment. :except is also a valid option to specify environment
53
+ # restrictions.
56
54
  gem "rspec", :only => :testing
57
55
 
56
+ # Specify a dependency, but specify that it is already present and expanded
57
+ # at vendor/rspec. Bundler will treat rspec as though it was the rspec gem
58
+ # for the purpose of gem resolution: if another gem depends on a version
59
+ # of rspec satisfied by "1.1.6", it will be used.
60
+ #
61
+ # If a gemspec is found in the directory, it will be used to specify load
62
+ # paths and supply additional dependencies.
63
+ #
64
+ # Bundler will also recursively search for *.gemspec, and assume that
65
+ # gemspecs it finds represent gems that are rooted in the same directory
66
+ # the gemspec is found in.
67
+ gem "rspec", "1.1.6", :vendored_at => "vendor/rspec"
68
+
69
+ # Works exactly like :vendored_at, but first downloads the repo from
70
+ # git and handles stashing the files for you. As with :vendored_at,
71
+ # Bundler will automatically use *.gemspec files in the root or anywhere
72
+ # in the repository.
73
+ gem "rails", "3.0.pre", :git => "git://github.com/rails/rails.git"
74
+
58
75
  # Add http://gems.github.com as a source that the bundler will use
59
76
  # to find gems listed in the manifest. By default,
60
77
  # http://gems.rubyforge.org is already added to the list.
@@ -84,11 +101,43 @@ information, please refer to Bundler::ManifestBuilder.
84
101
  # the ones that have been bundled.
85
102
  disable_rubygems
86
103
 
104
+ ### Gem Resolution
105
+
106
+ One of the most important things that the bundler does is do a
107
+ dependency resolution on the full list of gems that you specify, all
108
+ at once. This differs from the one-at-a-time dependency resolution that
109
+ Rubygems does, which can result in the following problem:
110
+
111
+ # On my system:
112
+ # activesupport 3.0.pre
113
+ # activesupport 2.3.4
114
+ # activemerchant 1.4.2
115
+ # rails 2.3.4
116
+ #
117
+ # activemerchant 1.4.2 depends on activesupport >= 2.3.2
118
+
119
+ gem "activemerchant", "1.4.2"
120
+ # results in activating activemerchant, as well as
121
+ # activesupport 3.0.pre, since it is >= 2.3.2
122
+
123
+ gem "rails", "2.3.4"
124
+ # results in:
125
+ # can't activate activesupport (= 2.3.4, runtime)
126
+ # for ["rails-2.3.4"], already activated
127
+ # activesupport-3.0.pre for ["activemerchant-1.4.2"]
128
+
129
+ This is because activemerchant has a broader dependency, which results
130
+ in the activation of a version of activesupport that does not satisfy
131
+ a more narrow dependency.
132
+
133
+ Bundler solves this problem by evaluating all dependencies at once,
134
+ so it can detect that all gems *together* require activesupport "2.3.4".
135
+
87
136
  ### Running Bundler
88
137
 
89
138
  Once a manifest file has been created, the only thing that needs to be done
90
139
  is to run the `gem bundle` command anywhere in your application. The script
91
- will load the manifest file, resole all the dependencies, download all
140
+ will load the manifest file, resolve all the dependencies, download all
92
141
  needed gems, and install them into the specified directory.
93
142
 
94
143
  Every time an update is made to the manifest file, run `gem bundle` again to
@@ -96,6 +145,41 @@ get the changes installed. This will only check the remote sources if your
96
145
  currently installed gems do not satisfy the `Gemfile`. If you want to force
97
146
  checking for updates on the remote sources, use the `--update` option.
98
147
 
148
+ ### Remote deploys
149
+
150
+ When you run `gem bundle`, the following steps occur:
151
+
152
+ 1. Gemfile is read in
153
+ 2. The gems specified in the Gemfile are resolved against the gems
154
+ already in your bundle. If the dependencies resolve, skip to step 5.
155
+ 3. If the dependencies in your Gemfile cannot be fully resolved
156
+ against the gems already in the bundle, the metadata for each
157
+ source is fetched.
158
+ 4. The gems in the Gemfile are resolved against the full list of
159
+ available gems in all sources, and the resulting gems are downloaded
160
+ 5. Each gem that has been downloaded but not yet expanded is expanded
161
+ into the local directory. This expansion process also installs
162
+ native gems.
163
+
164
+ As you can see, if you run gem bundle twice in a row, it will do nothing the
165
+ second time, since the gems obviously resolve against the installed gems,
166
+ and they are all expanded.
167
+
168
+ This also means that if you run `gem bundle`, and .gitignore the expanded
169
+ copies, leaving only the cached `.gem` files, you can run `gem bundle` again
170
+ on the remote system, and it will only expand out the gems (but not
171
+ resolve or download `.gem` files). This also means that native gems
172
+ will be compiled for the target platform without requiring that the
173
+ `.gem` file itself be downloaded from a remote gem server.
174
+
175
+ Assuming a Rails app with Bundler's standard setup, add something like
176
+ this to your top-level `.gitignore` to only keep the cache:
177
+
178
+ vendor/gems/
179
+ !vendor/gems/cache/
180
+
181
+ Make sure that you explicitly `git add vendor/gems/cache` before you commit.
182
+
99
183
  ### Running your application
100
184
 
101
185
  The easiest way to run your application is to start it with an executable
@@ -108,6 +192,9 @@ run it with the `gem exec` command. For example:
108
192
 
109
193
  gem exec ruby my_ruby_script.rb
110
194
 
195
+ You can use `gem exec bash` to enter a shell that will run all binaries in
196
+ the current context.
197
+
111
198
  Yet another way is to manually require the environment file first. This is
112
199
  located in `[bundle_path]/environments/default.rb`. For example:
113
200
 
@@ -133,9 +220,9 @@ to follow.
133
220
  * You can now use rails if you prepend `gem exec` to every call to `script/*`
134
221
  but that isn't fun.
135
222
 
136
- * At the top of `config/boot.rb`, add the following line:
223
+ * At the top of `config/preinitializer.rb`, add the following line:
137
224
 
138
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment'))
225
+ require "#{RAILS_ROOT}/vendor/gems/environment"
139
226
 
140
227
  In theory, this should be enough to get going.
141
228
 
@@ -143,12 +230,17 @@ In theory, this should be enough to get going.
143
230
 
144
231
  Ideally, no gem would assume the presence of rubygems at runtime. Rubygems provides
145
232
  enough features so that this isn't necessary. However, there are a number of gems
146
- that require specific rubygem features.
233
+ that require specific rubygems features.
147
234
 
148
235
  If the `disable_rubygems` option is used, Bundler will stub out the most common
149
236
  of these features, but it is possible that things will not go as intended quite
150
237
  yet. So, if you are brave, try your code without rubygems at runtime.
151
238
 
239
+ This is different from the `disable_system_gems` option, which uses the rubygems
240
+ library, but prevents system gems from being loaded; only gems that are bundled
241
+ will be available to your application. This option guarantees that dependencies
242
+ of your application will be available to a remote system.
243
+
152
244
  ## Known Issues
153
245
 
154
246
  * When a gem points to a git repository, the git repository will be cloned
@@ -159,4 +251,4 @@ yet. So, if you are brave, try your code without rubygems at runtime.
159
251
  Please report all bugs on the github issue tracker for the project located
160
252
  at:
161
253
 
162
- http://github.com/wycats/bundler/issues/
254
+ http://github.com/wycats/bundler/issues/
data/Rakefile CHANGED
@@ -1,12 +1,14 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
1
2
  require 'rubygems' unless ENV['NO_RUBYGEMS']
2
3
  require 'rubygems/specification'
4
+ require 'bundler'
3
5
  require 'date'
4
6
 
5
7
  spec = Gem::Specification.new do |s|
6
8
  s.name = "bundler"
7
- s.version = "0.5.0"
8
- s.author = "Yehuda Katz"
9
- s.email = "wycats@gmail.com"
9
+ s.version = Bundler::VERSION
10
+ s.authors = ["Yehuda Katz", "Carl Lerche"]
11
+ s.email = ["wycats@gmail.com", "clerche@engineyard.com"]
10
12
  s.homepage = "http://github.com/wycats/bundler"
11
13
  s.description = s.summary = "An easy way to vendor gem dependencies"
12
14
 
@@ -31,6 +33,7 @@ else
31
33
  Spec::Rake::SpecTask.new do |t|
32
34
  t.spec_files = FileList['spec/**/*_spec.rb'] - FileList['spec/fixtures/**/*_spec.rb']
33
35
  t.spec_opts = %w(-fs --color)
36
+ t.warning = true
34
37
  end
35
38
  end
36
39
 
@@ -16,9 +16,10 @@ require "bundler/dsl"
16
16
  require "bundler/cli"
17
17
  require "bundler/repository"
18
18
  require "bundler/dependency"
19
+ require "bundler/remote_specification"
19
20
 
20
21
  module Bundler
21
- VERSION = "0.5.0"
22
+ VERSION = "0.6.0"
22
23
 
23
24
  class << self
24
25
  attr_writer :logger
@@ -6,19 +6,25 @@ module Bundler
6
6
  new(options).run(command)
7
7
  rescue DefaultManifestNotFound => e
8
8
  Bundler.logger.error "Could not find a Gemfile to use"
9
- exit 2
9
+ exit 3
10
10
  rescue InvalidEnvironmentName => e
11
11
  Bundler.logger.error "Gemfile error: #{e.message}"
12
- exit
12
+ exit 4
13
13
  rescue InvalidRepository => e
14
14
  Bundler.logger.error e.message
15
- exit
15
+ exit 5
16
16
  rescue VersionConflict => e
17
17
  Bundler.logger.error e.message
18
- exit
18
+ exit 6
19
19
  rescue GemNotFound => e
20
20
  Bundler.logger.error e.message
21
- exit
21
+ exit 7
22
+ rescue InvalidCacheArgument => e
23
+ Bundler.logger.error e.message
24
+ exit 8
25
+ rescue SourceNotCached => e
26
+ Bundler.logger.error e.message
27
+ exit 9
22
28
  end
23
29
 
24
30
  def initialize(options)
@@ -27,7 +33,19 @@ module Bundler
27
33
  end
28
34
 
29
35
  def bundle
30
- @manifest.install(@options[:update])
36
+ @manifest.install(@options)
37
+ end
38
+
39
+ def cache
40
+ @manifest.cache(@options)
41
+ end
42
+
43
+ def prune
44
+ @manifest.prune(@options)
45
+ end
46
+
47
+ def list
48
+ @manifest.list(@options)
31
49
  end
32
50
 
33
51
  def exec
@@ -10,6 +10,22 @@ class Gem::Commands::BundleCommand < Gem::Command
10
10
  add_option('-u', '--update', "Force a remote check for newer gems") do
11
11
  options[:update] = true
12
12
  end
13
+
14
+ add_option('--cached', "Only use cached gems when expanding the bundle") do
15
+ options[:cached] = true
16
+ end
17
+
18
+ add_option('--cache GEM', "Specify a path to a .gem file to add to the bundled gem cache") do |gem, options|
19
+ options[:cache] = gem
20
+ end
21
+
22
+ add_option('--prune-cache', "Removes all .gem files from the bundle's cache") do
23
+ options[:prune] = true
24
+ end
25
+
26
+ add_option('--list', "List all gems that are part of the active bundle") do
27
+ options[:list] = true
28
+ end
13
29
  end
14
30
 
15
31
  def usage
@@ -25,7 +41,15 @@ Bundle stuff
25
41
  def execute
26
42
  # Prevent the bundler from getting required unless it is actually being used
27
43
  require 'bundler'
28
- Bundler::CLI.run(:bundle, options)
44
+ if options[:cache]
45
+ Bundler::CLI.run(:cache, options)
46
+ elsif options[:prune]
47
+ Bundler::CLI.run(:prune, options)
48
+ elsif options[:list]
49
+ Bundler::CLI.run(:list, options)
50
+ else
51
+ Bundler::CLI.run(:bundle, options)
52
+ end
29
53
  end
30
54
 
31
55
  end
@@ -5,6 +5,7 @@ module Bundler
5
5
  def initialize(environment)
6
6
  @environment = environment
7
7
  @sources = Hash.new { |h,k| h[k] = {} }
8
+ @only, @except = nil, nil
8
9
  end
9
10
 
10
11
  def bundle_path(path)
@@ -2,13 +2,16 @@ require "rubygems/source_index"
2
2
 
3
3
  module Bundler
4
4
  class DefaultManifestNotFound < StandardError; end
5
+ class InvalidCacheArgument < StandardError; end
6
+ class SourceNotCached < StandardError; end
5
7
 
6
8
  class Environment
7
9
  attr_reader :filename, :dependencies
8
- attr_accessor :rubygems, :system_gems, :gem_path, :bindir
10
+ attr_accessor :rubygems, :system_gems
11
+ attr_writer :gem_path, :bindir
9
12
 
10
13
  def self.load(gemfile = nil)
11
- gemfile = gemfile ? Pathname.new(gemfile) : default_manifest_file
14
+ gemfile = gemfile ? Pathname.new(gemfile).expand_path : default_manifest_file
12
15
 
13
16
  unless gemfile.file?
14
17
  raise ManifestFileNotFound, "#{filename.inspect} does not exist"
@@ -31,7 +34,7 @@ module Bundler
31
34
 
32
35
  def initialize(filename) #, sources, dependencies, bindir, path, rubygems, system_gems)
33
36
  @filename = filename
34
- @default_sources = [GemSource.new(:uri => "http://gems.rubyforge.org")]
37
+ @default_sources = [GemSource.new(:uri => "http://gems.rubyforge.org"), SystemGemSource.new({})]
35
38
  @sources = []
36
39
  @priority_sources = []
37
40
  @dependencies = []
@@ -43,23 +46,61 @@ module Bundler
43
46
  builder.instance_eval(File.read(filename))
44
47
  end
45
48
 
46
- def install(update = false)
47
- begin
48
- tmp_path = filename.dirname.join(".tmp")
49
- FileUtils.mkdir_p(tmp_path)
50
- sources.each { |s| s.tmp_path = tmp_path }
51
- repository.install(gem_dependencies, sources,
52
- :rubygems => rubygems,
53
- :system_gems => system_gems,
54
- :manifest => filename,
55
- :update => update
56
- )
57
- ensure
58
- FileUtils.rm_rf(tmp_path)
59
- end
49
+ def install(options = {})
50
+ update = options[:update]
51
+ cached = options[:cached]
52
+
53
+ repository.install(gem_dependencies, sources,
54
+ :rubygems => rubygems,
55
+ :system_gems => system_gems,
56
+ :manifest => filename,
57
+ :update => update,
58
+ :cached => cached
59
+ )
60
60
  Bundler.logger.info "Done."
61
61
  end
62
62
 
63
+ def cache(options = {})
64
+ gemfile = options[:cache]
65
+
66
+ if File.extname(gemfile) == ".gem"
67
+ if !File.exist?(gemfile)
68
+ raise InvalidCacheArgument, "'#{gemfile}' does not exist."
69
+ end
70
+ repository.cache(gemfile)
71
+ elsif File.directory?(gemfile) || gemfile.include?('/')
72
+ if !File.directory?(gemfile)
73
+ raise InvalidCacheArgument, "'#{gemfile}' does not exist."
74
+ end
75
+ gemfiles = Dir["#{gemfile}/*.gem"]
76
+ if gemfiles.empty?
77
+ raise InvalidCacheArgument, "'#{gemfile}' contains no gemfiles"
78
+ end
79
+ repository.cache(*gemfiles)
80
+ else
81
+ local = Gem::SourceIndex.from_installed_gems.find_name(gemfile).last
82
+
83
+ if !local
84
+ raise InvalidCacheArgument, "w0t? '#{gemfile}' means nothing to me."
85
+ end
86
+
87
+ gemfile = Pathname.new(local.loaded_from)
88
+ gemfile = gemfile.dirname.join('..', 'cache', "#{local.full_name}.gem").expand_path
89
+ repository.cache(gemfile)
90
+ end
91
+ end
92
+
93
+ def prune(options = {})
94
+ repository.prune(gem_dependencies, sources)
95
+ end
96
+
97
+ def list(options = {})
98
+ Bundler.logger.info "Currently bundled gems:"
99
+ repository.gems.each do |spec|
100
+ Bundler.logger.info " * #{spec.name} (#{spec.version})"
101
+ end
102
+ end
103
+
63
104
  def setup_environment
64
105
  unless system_gems
65
106
  ENV["GEM_HOME"] = gem_path
@@ -1,8 +1,8 @@
1
1
  module Bundler
2
2
  class GemBundle < Array
3
- def download(repository)
3
+ def download
4
4
  sort_by {|s| s.full_name.downcase }.each do |spec|
5
- spec.source.download(spec, repository)
5
+ spec.source.download(spec)
6
6
  end
7
7
 
8
8
  self
@@ -1,5 +1,7 @@
1
1
  module Gem
2
2
  class Installer
3
+ remove_method(:app_script_text) if method_defined?(:app_script_text)
4
+
3
5
  def app_script_text(bin_file_name)
4
6
  path = @gem_home
5
7
  template = File.read(File.join(File.dirname(__FILE__), "templates", "app_script.erb"))
@@ -12,6 +14,8 @@ module Gem
12
14
  attr_accessor :source
13
15
  attr_accessor :location
14
16
 
17
+ remove_method(:specification_version) if method_defined?(:specification_version)
18
+
15
19
  # Hack to fix github's strange marshal file
16
20
  def specification_version
17
21
  @specification_version && @specification_version.to_i
@@ -19,7 +23,11 @@ module Gem
19
23
 
20
24
  alias full_gem_path_without_location full_gem_path
21
25
  def full_gem_path
22
- @location ? @location : full_gem_path_without_location
26
+ if defined?(@location) && @location
27
+ @location
28
+ else
29
+ full_gem_path_without_location
30
+ end
23
31
  end
24
32
  end
25
33
  end
@@ -0,0 +1,50 @@
1
+ module Bundler
2
+ # Represents a lazily loaded gem specification, where the full specification
3
+ # is on the source server in rubygems' "quick" index. The proxy object is to
4
+ # be seeded with what we're given from the source's abbreviated index - the
5
+ # full specification will only be fetched when necesary.
6
+ class RemoteSpecification
7
+ attr_reader :name, :version, :platform
8
+ attr_accessor :source
9
+
10
+ def initialize(name, version, platform, source_uri)
11
+ @name = name
12
+ @version = version
13
+ @platform = platform
14
+ @source_uri = source_uri
15
+ end
16
+
17
+ def full_name
18
+ if platform == Gem::Platform::RUBY or platform.nil? then
19
+ "#{@name}-#{@version}"
20
+ else
21
+ "#{@name}-#{@version}-#{platform}"
22
+ end
23
+ end
24
+
25
+ # Because Rubyforge cannot be trusted to provide valid specifications
26
+ # once the remote gem is donwloaded, the backend specification will
27
+ # be swapped out.
28
+ def __swap__(spec)
29
+ @specification = spec
30
+ end
31
+
32
+ private
33
+
34
+ def _remote_uri
35
+ "#{@source_uri}/quick/Marshal.4.8/#{@name}-#{@version}.gemspec.rz"
36
+ end
37
+
38
+ def _remote_specification
39
+ @specification ||= begin
40
+ deflated = Gem::RemoteFetcher.fetcher.fetch_path(_remote_uri)
41
+ inflated = Gem.inflate(deflated)
42
+ Marshal.load(inflated)
43
+ end
44
+ end
45
+
46
+ def method_missing(method, *args, &blk)
47
+ _remote_specification.send(method, *args, &blk)
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,3 @@
1
- require "bundler/repository/gem_repository"
2
- require "bundler/repository/directory_repository"
3
-
4
1
  module Bundler
5
2
  class InvalidRepository < StandardError ; end
6
3
 
@@ -13,51 +10,67 @@ module Bundler
13
10
  @path = Pathname.new(path)
14
11
  @bindir = Pathname.new(bindir)
15
12
 
16
- @repos = {
17
- :gem => Gems.new(@path, @bindir),
18
- :directory => Directory.new(@path.join("dirs"), @bindir)
19
- }
13
+ @cache = GemDirectorySource.new(:location => @path.join("cache"))
20
14
  end
21
15
 
22
16
  def install(dependencies, sources, options = {})
23
- if options[:update] || !satisfies?(dependencies)
24
- fetch(dependencies, sources)
25
- expand(options)
26
- else
27
- # Remove any gems that are still around if the Gemfile changed without
28
- # requiring new gems to be download (e.g. a line in the Gemfile was
29
- # removed)
30
- cleanup(Resolver.resolve(dependencies, [source_index]))
17
+ # TODO: clean this up
18
+ sources.each do |s|
19
+ s.repository = self
20
+ s.local = options[:cached]
31
21
  end
32
- configure(options)
33
- sync
34
- end
35
22
 
36
- def gems
37
- gems = []
38
- each_repo do |repo|
39
- gems.concat repo.gems
23
+ begin
24
+ valid = Resolver.resolve(dependencies, [source_index])
25
+ rescue Bundler::GemNotFound
40
26
  end
41
- gems
27
+
28
+ if options[:cached]
29
+ sources = sources.select { |s| s.can_be_local? }
30
+ end
31
+
32
+ if options[:update] || !valid
33
+ Bundler.logger.info "Calculating dependencies..."
34
+ bundle = Resolver.resolve(dependencies, [@cache] + sources)
35
+ do_install(bundle, options)
36
+ valid = bundle
37
+ end
38
+ cleanup(valid)
39
+ configure(valid, options)
42
40
  end
43
41
 
44
- def satisfies?(dependencies)
45
- index = source_index
46
- dependencies.all? { |dep| index.search(dep).size > 0 }
42
+ def cache(*gemfiles)
43
+ FileUtils.mkdir_p(@path.join("cache"))
44
+ gemfiles.each do |gemfile|
45
+ Bundler.logger.info "Caching: #{File.basename(gemfile)}"
46
+ FileUtils.cp(gemfile, @path.join("cache"))
47
+ end
47
48
  end
48
49
 
49
- def source_index
50
- index = Gem::SourceIndex.new
50
+ def prune(dependencies, sources)
51
+ sources.each do |s|
52
+ s.repository = self
53
+ s.local = true
54
+ end
51
55
 
52
- each_repo do |repo|
53
- index.gems.merge!(repo.source_index.gems)
56
+ sources = sources.select { |s| s.can_be_local? }
57
+ bundle = Resolver.resolve(dependencies, [@cache] + sources)
58
+ @cache.gems.each do |name, spec|
59
+ unless bundle.any? { |s| s.name == spec.name && s.version == spec.version }
60
+ Bundler.logger.info "Pruning #{spec.name} (#{spec.version}) from the cache"
61
+ FileUtils.rm @path.join("cache", "#{spec.full_name}.gem")
62
+ end
54
63
  end
64
+ end
55
65
 
56
- index
66
+ def gems
67
+ source_index.gems.values
57
68
  end
58
69
 
59
- def add_spec(type, spec)
60
- @repos[type].add_spec(spec)
70
+ def source_index
71
+ index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
72
+ index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
73
+ index
61
74
  end
62
75
 
63
76
  def download_path_for(type)
@@ -66,34 +79,84 @@ module Bundler
66
79
 
67
80
  private
68
81
 
69
- def cleanup(bundle)
70
- each_repo do |repo|
71
- repo.cleanup(bundle)
82
+ def do_install(bundle, options)
83
+ bundle.download
84
+
85
+ bundle.each do |spec|
86
+ spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec")
87
+ # Do nothing if the gem is already expanded
88
+ next if @path.join("gems", spec.full_name).directory?
89
+
90
+ case spec.source
91
+ when GemSource, GemDirectorySource, SystemGemSource
92
+ expand_gemfile(spec, options)
93
+ else
94
+ expand_vendored_gem(spec, options)
95
+ end
72
96
  end
73
97
  end
74
98
 
75
- def each_repo
76
- @repos.each do |k, repo|
77
- yield repo
99
+ def expand_gemfile(spec, options)
100
+ Bundler.logger.info "Installing #{spec.name} (#{spec.version})"
101
+
102
+ gemfile = @path.join("cache", "#{spec.full_name}.gem").to_s
103
+
104
+ installer = Gem::Installer.new(gemfile, options.merge(
105
+ :install_dir => @path,
106
+ :ignore_dependencies => true,
107
+ :env_shebang => true,
108
+ :wrappers => true,
109
+ :bin_dir => @bindir
110
+ ))
111
+ installer.install
112
+ end
113
+
114
+ def expand_vendored_gem(spec, options)
115
+ add_spec(spec)
116
+ FileUtils.mkdir_p(@path.join("gems"))
117
+ File.symlink(spec.location, @path.join("gems", spec.full_name))
118
+
119
+ # HAX -- Generate the bin
120
+ bin_dir = @bindir
121
+ path = @path
122
+ installer = Gem::Installer.allocate
123
+ installer.instance_eval do
124
+ @spec = spec
125
+ @bin_dir = bin_dir
126
+ @gem_dir = path.join("gems", "#{spec.full_name}")
127
+ @gem_home = path
128
+ @wrappers = true
78
129
  end
130
+ installer.generate_bin
79
131
  end
80
132
 
81
- def fetch(dependencies, sources)
82
- bundle = Resolver.resolve(dependencies, sources)
83
- # Cleanup here to remove any gems that could cause problem in the expansion
84
- # phase
85
- #
86
- # TODO: Try to avoid double cleanup
87
- cleanup(bundle)
88
- bundle.download(self)
133
+ def add_spec(spec)
134
+ destination = path.join('specifications')
135
+ destination.mkdir unless destination.exist?
136
+
137
+ File.open(destination.join("#{spec.full_name}.gemspec"), 'w') do |f|
138
+ f.puts spec.to_ruby
139
+ end
89
140
  end
90
141
 
91
- def sync
92
- glob = gems.map { |g| g.executables }.flatten.join(',')
142
+ def cleanup(valid)
143
+ to_delete = gems
144
+ to_delete.delete_if do |spec|
145
+ valid.any? { |other| spec.name == other.name && spec.version == other.version }
146
+ end
93
147
 
94
- (Dir[@bindir.join("*")] - Dir[@bindir.join("{#{glob}}")]).each do |file|
95
- Bundler.logger.info "Deleting bin file: #{File.basename(file)}"
96
- FileUtils.rm_rf(file)
148
+ valid_executables = valid.map { |s| s.executables }.flatten.compact
149
+
150
+ to_delete.each do |spec|
151
+ Bundler.logger.info "Deleting gem: #{spec.name} (#{spec.version})"
152
+ FileUtils.rm_rf(@path.join("specifications", "#{spec.full_name}.gemspec"))
153
+ FileUtils.rm_rf(@path.join("gems", spec.full_name))
154
+ # Cleanup the bin directory
155
+ spec.executables.each do |bin|
156
+ next if valid_executables.include?(bin)
157
+ Bundler.logger.info "Deleting bin file: #{bin}"
158
+ FileUtils.rm_rf(@bindir.join(bin))
159
+ end
97
160
  end
98
161
  end
99
162
 
@@ -103,14 +166,13 @@ module Bundler
103
166
  end
104
167
  end
105
168
 
106
- def configure(options)
107
- generate_environment(options)
169
+ def configure(specs, options)
170
+ generate_environment(specs, options)
108
171
  end
109
172
 
110
- def generate_environment(options)
173
+ def generate_environment(specs, options)
111
174
  FileUtils.mkdir_p(path)
112
175
 
113
- specs = gems
114
176
  load_paths = load_paths_for_specs(specs)
115
177
  bindir = @bindir.relative_path_from(path).to_s
116
178
  filename = options[:manifest].relative_path_from(path).to_s
@@ -148,4 +210,4 @@ module Bundler
148
210
  "require #{file.inspect}#{constraint}"
149
211
  end
150
212
  end
151
- end
213
+ end
@@ -37,8 +37,6 @@ module Bundler
37
37
  # <GemBundle>,nil:: If the list of dependencies can be resolved, a
38
38
  # collection of gemspecs is returned. Otherwise, nil is returned.
39
39
  def self.resolve(requirements, sources)
40
- Bundler.logger.info "Calculating dependencies..."
41
-
42
40
  resolver = new(sources)
43
41
  result = catch(:success) do
44
42
  resolver.resolve(requirements, {})
@@ -181,7 +179,9 @@ module Bundler
181
179
  def search(dependency)
182
180
  @cache[dependency.hash] ||= begin
183
181
  @specs[dependency.name].values.select do |spec|
184
- dependency =~ spec
182
+ match = dependency =~ spec
183
+ match &= dependency.version_requirements.prerelease? if spec.version.prerelease?
184
+ match
185
185
  end.sort_by {|s| s.version }
186
186
  end
187
187
  end
@@ -2,7 +2,7 @@ module Bundler
2
2
  # Represents a source of rubygems. Initially, this is only gem repositories, but
3
3
  # eventually, this will be git, svn, HTTP
4
4
  class Source
5
- attr_accessor :tmp_path
5
+ attr_accessor :repository, :local
6
6
  end
7
7
 
8
8
  class GemSource < Source
@@ -14,6 +14,10 @@ module Bundler
14
14
  raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
15
15
  end
16
16
 
17
+ def can_be_local?
18
+ false
19
+ end
20
+
17
21
  def gems
18
22
  @specs ||= fetch_specs
19
23
  end
@@ -28,16 +32,22 @@ module Bundler
28
32
 
29
33
  class RubygemsRetardation < StandardError; end
30
34
 
31
- def download(spec, repository)
35
+ def download(spec)
32
36
  Bundler.logger.info "Downloading #{spec.full_name}.gem"
33
37
 
34
- destination = repository.download_path_for(:gem)
38
+ destination = repository.path
35
39
 
36
40
  unless destination.writable?
37
41
  raise RubygemsRetardation
38
42
  end
39
43
 
40
- Gem::RemoteFetcher.fetcher.download(spec, uri, repository.download_path_for(:gem))
44
+ # Download the gem
45
+ Gem::RemoteFetcher.fetcher.download(spec, uri, destination)
46
+
47
+ # Re-read the gemspec from the downloaded gem to correct
48
+ # any errors that were present in the Rubyforge specification.
49
+ new_spec = Gem::Format.from_file_by_path(destination.join('cache', "#{spec.full_name}.gem")).spec
50
+ spec.__swap__(new_spec)
41
51
  end
42
52
 
43
53
  private
@@ -45,17 +55,109 @@ module Bundler
45
55
  def fetch_specs
46
56
  Bundler.logger.info "Updating source: #{to_s}"
47
57
 
48
- deflated = Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/Marshal.4.8.Z")
49
- inflated = Gem.inflate deflated
58
+ fetcher = Gem::RemoteFetcher.fetcher
59
+ main_index = fetcher.fetch_path("#{uri}/specs.4.8.gz")
60
+ begin
61
+ prerelease_index = fetcher.fetch_path("#{uri}/prerelease_specs.4.8.gz")
62
+ index = Marshal.load(main_index) + Marshal.load(prerelease_index)
63
+ rescue Gem::RemoteFetcher::FetchError
64
+ Bundler.logger.warn "Source '#{uri}' does not support prerelease gems"
65
+ index = Marshal.load(main_index)
66
+ end
50
67
 
51
- index = Marshal.load(inflated)
52
- index.gems
68
+ gems = {}
69
+ index.each do |name, version, platform|
70
+ spec = RemoteSpecification.new(name, version, platform, @uri)
71
+ gems[spec.full_name] = spec
72
+ end
73
+ gems
53
74
  rescue Gem::RemoteFetcher::FetchError => e
54
75
  raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
55
76
  end
56
77
  end
57
78
 
79
+ class SystemGemSource < Source
80
+ def initialize(options)
81
+ # Nothing to do
82
+ end
83
+
84
+ def can_be_local?
85
+ false
86
+ end
87
+
88
+ def gems
89
+ @specs ||= Gem::SourceIndex.from_installed_gems.gems
90
+ end
91
+
92
+ def ==(other)
93
+ other.is_a?(SystemGemSource)
94
+ end
95
+
96
+ def to_s
97
+ "system"
98
+ end
99
+
100
+ def download(spec)
101
+ # gemfile = Pathname.new(local.loaded_from)
102
+ # gemfile = gemfile.dirname.join('..', 'cache', "#{local.full_name}.gem").expand_path
103
+ # repository.cache(File.join(Gem.dir, "cache", "#{local.full_name}.gem"))
104
+ gemfile = Pathname.new(spec.loaded_from)
105
+ gemfile = gemfile.dirname.join('..', 'cache', "#{spec.full_name}.gem")
106
+ repository.cache(gemfile)
107
+ end
108
+
109
+ private
110
+
111
+ def fetch_specs
112
+
113
+ end
114
+
115
+ end
116
+
117
+ class GemDirectorySource < Source
118
+ attr_reader :location
119
+
120
+ def initialize(options)
121
+ @location = options[:location]
122
+ end
123
+
124
+ def can_be_local?
125
+ true
126
+ end
127
+
128
+ def gems
129
+ @specs ||= fetch_specs
130
+ end
131
+
132
+ def ==(other)
133
+ location == other.location
134
+ end
135
+
136
+ def to_s
137
+ location.to_s
138
+ end
139
+
140
+ def download(spec)
141
+ # raise NotImplementedError
142
+ end
143
+
144
+ private
145
+
146
+ def fetch_specs
147
+ specs = {}
148
+
149
+ Dir["#{@location}/*.gem"].each do |gemfile|
150
+ spec = Gem::Format.from_file_by_path(gemfile).spec
151
+ specs[spec.full_name] = spec
152
+ end
153
+
154
+ specs
155
+ end
156
+ end
157
+
58
158
  class DirectorySource < Source
159
+ attr_reader :location
160
+
59
161
  def initialize(options)
60
162
  @name = options[:name]
61
163
  @version = options[:version]
@@ -63,32 +165,38 @@ module Bundler
63
165
  @require_paths = options[:require_paths] || %w(lib)
64
166
  end
65
167
 
168
+ def can_be_local?
169
+ true
170
+ end
171
+
66
172
  def gems
67
173
  @gems ||= begin
68
174
  specs = {}
69
175
 
70
176
  # Find any gemspec files in the directory and load those specs
71
- Dir[@location.join('**', '*.gemspec')].each do |file|
72
- path = Pathname.new(file).relative_path_from(@location).dirname
73
- spec = eval(File.read(file))
74
- spec.require_paths.map! { |p| path.join(p) }
75
- specs[spec.full_name] = spec
177
+ Dir["#{location}/**/*.gemspec"].each do |file|
178
+ file = Pathname.new(file)
179
+ if spec = eval(File.read(file)) and validate_gemspec(file, spec)
180
+ spec.location = file.dirname.expand_path
181
+ specs[spec.full_name] = spec
182
+ end
76
183
  end
77
184
 
78
185
  # If a gemspec for the dependency was not found, add it to the list
79
186
  if specs.keys.grep(/^#{Regexp.escape(@name)}/).empty?
80
187
  case
81
188
  when @version.nil?
82
- raise ArgumentError, "If you use :at, you must specify the gem" \
189
+ raise ArgumentError, "If you use :at, you must specify the gem " \
83
190
  "and version you wish to stand in for"
84
191
  when !Gem::Version.correct?(@version)
85
- raise ArgumentError, "If you use :at, you must specify a gem and" \
192
+ raise ArgumentError, "If you use :at, you must specify a gem and " \
86
193
  "version. You specified #{@version} for the version"
87
194
  end
88
195
 
89
196
  default = Gem::Specification.new do |s|
90
- s.name = @name
91
- s.version = Gem::Version.new(@version) if @version
197
+ s.name = @name
198
+ s.version = Gem::Version.new(@version) if @version
199
+ s.location = location
92
200
  end
93
201
  specs[default.full_name] = default
94
202
  end
@@ -97,18 +205,52 @@ module Bundler
97
205
  end
98
206
  end
99
207
 
208
+ # Too aggressive apparently.
209
+ # ===
210
+ # def validate_gemspec(file, spec)
211
+ # file = Pathname.new(file)
212
+ # Dir.chdir(file.dirname) do
213
+ # spec.validate
214
+ # end
215
+ # rescue Gem::InvalidSpecificationException => e
216
+ # file = file.relative_path_from(repository.path)
217
+ # Bundler.logger.warn e.message
218
+ # Bundler.logger.warn "Gemspec #{spec.name} (#{spec.version}) found at '#{file}' is not valid"
219
+ # false
220
+ # end
221
+ def validate_gemspec(file, spec)
222
+ base = file.dirname
223
+ msg = "Gemspec for #{spec.name} (#{spec.version}) is invalid:"
224
+ # Check the require_paths
225
+ (spec.require_paths || []).each do |path|
226
+ unless base.join(path).directory?
227
+ Bundler.logger.warn "#{msg} Missing require path: '#{path}'"
228
+ return false
229
+ end
230
+ end
231
+
232
+ # Check the executables
233
+ (spec.executables || []).each do |exec|
234
+ unless base.join(spec.bindir, exec).file?
235
+ Bundler.logger.warn "#{msg} Missing executable: '#{File.join(spec.bindir, exec)}'"
236
+ return false
237
+ end
238
+ end
239
+
240
+ true
241
+ end
242
+
100
243
  def ==(other)
101
244
  # TMP HAX
102
245
  other.is_a?(DirectorySource)
103
246
  end
104
247
 
105
248
  def to_s
106
- "#{@name} (#{@version}) Located at: '#{@location}'"
249
+ "#{@name} (#{@version}) Located at: '#{location}'"
107
250
  end
108
251
 
109
- def download(spec, repository)
110
- spec.require_paths.map! { |p| File.join(@location, p) }
111
- repository.add_spec(:directory, spec)
252
+ def download(spec)
253
+ # Nothing needed here
112
254
  end
113
255
  end
114
256
 
@@ -120,31 +262,35 @@ module Bundler
120
262
  @branch = options[:branch]
121
263
  end
122
264
 
265
+ def location
266
+ # TMP HAX to get the *.gemspec reading to work
267
+ repository.path.join('dirs', File.basename(@uri, '.git'))
268
+ end
269
+
123
270
  def gems
124
- FileUtils.mkdir_p(tmp_path.join("gitz"))
271
+ unless location.directory?
272
+ # Raise an error if the source should run in local mode,
273
+ # but it has not been cached yet.
274
+ if local
275
+ raise SourceNotCached, "Git repository '#{@uri}' has not been cloned yet"
276
+ end
125
277
 
126
- # TMP HAX to get the *.gemspec reading to work
127
- @location = tmp_path.join("gitz", @name)
278
+ FileUtils.mkdir_p(location.dirname)
128
279
 
129
- Bundler.logger.info "Cloning git repository at: #{@uri}"
130
- `git clone #{@uri} #{@location} --no-hardlinks`
280
+ Bundler.logger.info "Cloning git repository at: #{@uri}"
281
+ `git clone #{@uri} #{location} --no-hardlinks`
131
282
 
132
- if @ref
133
- Dir.chdir(@location) { `git checkout #{@ref}` }
134
- elsif @branch && @branch != "master"
135
- Dir.chdir(@location) { `git checkout origin/#{@branch}` }
283
+ if @ref
284
+ Dir.chdir(location) { `git checkout #{@ref}` }
285
+ elsif @branch && @branch != "master"
286
+ Dir.chdir(location) { `git checkout origin/#{@branch}` }
287
+ end
136
288
  end
137
289
  super
138
290
  end
139
291
 
140
- def download(spec, repository)
141
- dest = repository.download_path_for(:directory).join(@name)
142
- spec.require_paths.map! { |p| File.join(dest, p) }
143
- repository.add_spec(:directory, spec)
144
- if spec.name == @name
145
- FileUtils.mkdir_p(dest.dirname)
146
- FileUtils.mv(tmp_path.join("gitz", spec.name), dest)
147
- end
292
+ def download(spec)
293
+ # Nothing needed here
148
294
  end
149
295
  end
150
- end
296
+ end
@@ -1,13 +1,14 @@
1
1
  # DO NOT MODIFY THIS FILE
2
2
  module Bundler
3
- dir = File.dirname(__FILE__)
3
+ file = File.expand_path(__FILE__)
4
+ dir = File.dirname(file)
4
5
 
5
6
  <% unless options[:system_gems] -%>
6
7
  ENV["GEM_HOME"] = dir
7
8
  ENV["GEM_PATH"] = dir
8
9
  <% end -%>
9
10
  ENV["PATH"] = "#{dir}/<%= bindir %>:#{ENV["PATH"]}"
10
- ENV["RUBYOPT"] = "-r#{__FILE__} #{ENV["RUBYOPT"]}"
11
+ ENV["RUBYOPT"] = "-r#{file} #{ENV["RUBYOPT"]}"
11
12
 
12
13
  <% load_paths.each do |load_path| -%>
13
14
  $LOAD_PATH.unshift File.expand_path("#{dir}/<%= load_path %>")
@@ -57,6 +58,7 @@ module Bundler
57
58
  only = _combine_onlys(opt[:only] || opt["only"])
58
59
  except = _combine_excepts(opt[:except] || opt["except"])
59
60
  files = opt[:require_as] || opt["require_as"] || name
61
+ files = [files] unless files.respond_to?(:each)
60
62
 
61
63
  return unless !only || only.any? {|e| e == @env }
62
64
  return if except && except.any? {|e| e == @env }
@@ -124,4 +126,4 @@ module Gem
124
126
  class VerificationError < Exception; end
125
127
  class SystemExitException < SystemExit; end
126
128
  end
127
- <% end -%>
129
+ <% end -%>
metadata CHANGED
@@ -1,20 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
8
+ - Carl Lerche
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
12
 
12
- date: 2009-09-03 00:00:00 -07:00
13
+ date: 2009-09-23 00:00:00 -07:00
13
14
  default_executable:
14
15
  dependencies: []
15
16
 
16
17
  description: An easy way to vendor gem dependencies
17
- email: wycats@gmail.com
18
+ email:
19
+ - wycats@gmail.com
20
+ - clerche@engineyard.com
18
21
  executables: []
19
22
 
20
23
  extensions: []
@@ -35,8 +38,7 @@ files:
35
38
  - lib/bundler/finder.rb
36
39
  - lib/bundler/gem_bundle.rb
37
40
  - lib/bundler/gem_ext.rb
38
- - lib/bundler/repository/directory_repository.rb
39
- - lib/bundler/repository/gem_repository.rb
41
+ - lib/bundler/remote_specification.rb
40
42
  - lib/bundler/repository.rb
41
43
  - lib/bundler/resolver.rb
42
44
  - lib/bundler/runtime.rb
@@ -1,46 +0,0 @@
1
- module Bundler
2
- class Repository
3
- class Directory
4
- attr_reader :path, :bindir
5
-
6
- def initialize(path, bindir)
7
- @path = path
8
- @bindir = bindir
9
-
10
- FileUtils.mkdir_p(path.to_s)
11
- end
12
-
13
- def source_index
14
- index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
15
- index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
16
- index
17
- end
18
-
19
- def gems
20
- source_index.gems.values
21
- end
22
-
23
- def add_spec(spec)
24
- destination = path.join('specifications')
25
- destination.mkdir unless destination.exist?
26
-
27
- File.open(destination.join("#{spec.full_name}.gemspec"), 'w') do |f|
28
- f.puts spec.to_ruby
29
- end
30
- end
31
-
32
- def download_path_for
33
- @path.join("dirs")
34
- end
35
-
36
- # Checks whether a gem is installed
37
- def expand(options)
38
- # raise NotImplementedError
39
- end
40
-
41
- def cleanup(gems)
42
- # raise NotImplementedError
43
- end
44
- end
45
- end
46
- end
@@ -1,108 +0,0 @@
1
- module Bundler
2
- class Repository
3
- class Gems
4
- attr_reader :path, :bindir
5
-
6
- def initialize(path, bindir)
7
- @path = path
8
- @bindir = bindir
9
- end
10
-
11
- # Returns the source index for all gems installed in the
12
- # repository
13
- def source_index
14
- index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
15
- index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
16
- index
17
- end
18
-
19
- def gems
20
- source_index.gems.values
21
- end
22
-
23
- # Checks whether a gem is installed
24
- def expand(options)
25
- cached_gems.each do |name, version|
26
- unless installed?(name, version)
27
- install_cached_gem(name, version, options)
28
- end
29
- end
30
- end
31
-
32
- def cleanup(gems)
33
- glob = gems.map { |g| g.full_name }.join(',')
34
- base = path.join("{cache,specifications,gems}")
35
-
36
- (Dir[base.join("*")] - Dir[base.join("{#{glob}}{.gemspec,.gem,}")]).each do |file|
37
- if File.basename(file) =~ /\.gem$/
38
- name = File.basename(file, '.gem')
39
- Bundler.logger.info "Deleting gem: #{name}"
40
- end
41
- FileUtils.rm_rf(file)
42
- end
43
- end
44
-
45
- def add_spec(spec)
46
- raise NotImplementedError
47
- end
48
-
49
- def download_path_for
50
- path
51
- end
52
-
53
- private
54
-
55
- def cache_path
56
- @path.join("cache")
57
- end
58
-
59
- def cache_files
60
- Dir[cache_path.join("*.gem")]
61
- end
62
-
63
- def cached_gems
64
- cache_files.map do |f|
65
- full_name = File.basename(f).gsub(/\.gem$/, '')
66
- full_name.split(/-(?=[^-]+$)/)
67
- end
68
- end
69
-
70
- def spec_path
71
- @path.join("specifications")
72
- end
73
-
74
- def spec_files
75
- Dir[spec_path.join("*.gemspec")]
76
- end
77
-
78
- def gem_path
79
- @path.join("gems")
80
- end
81
-
82
- def gem_paths
83
- Dir[gem_path.join("*")]
84
- end
85
-
86
- def installed?(name, version)
87
- spec_files.any? { |g| File.basename(g) == "#{name}-#{version}.gemspec" } &&
88
- gem_paths.any? { |g| File.basename(g) == "#{name}-#{version}" }
89
- end
90
-
91
- def install_cached_gem(name, version, options = {})
92
- cached_gem = cache_path.join("#{name}-#{version}.gem")
93
- # TODO: Add a warning if cached_gem is not a file
94
- if cached_gem.file?
95
- Bundler.logger.info "Installing #{name}-#{version}.gem"
96
- installer = Gem::Installer.new(cached_gem.to_s, options.merge(
97
- :install_dir => @path,
98
- :ignore_dependencies => true,
99
- :env_shebang => true,
100
- :wrappers => true,
101
- :bin_dir => @bindir
102
- ))
103
- installer.install
104
- end
105
- end
106
- end
107
- end
108
- end