bundler 0.9.26 → 1.0.0.beta.1

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.

@@ -1,13 +1,79 @@
1
- unless defined? Gem
2
- require 'rubygems'
3
- require 'rubygems/specification'
1
+ require 'pathname'
2
+
3
+ if defined?(Gem::QuickLoader)
4
+ # Gem Prelude makes me a sad panda :'(
5
+ Gem::QuickLoader.load_full_rubygems_library
6
+ end
7
+
8
+ require 'rubygems'
9
+ require 'rubygems/specification'
10
+
11
+ module Bundler
12
+ class DepProxy
13
+
14
+ attr_reader :required_by, :__platform, :dep
15
+
16
+ def initialize(dep, platform)
17
+ @dep, @__platform, @required_by = dep, platform, []
18
+ end
19
+
20
+ def hash
21
+ @hash ||= dep.hash
22
+ end
23
+
24
+ def ==(o)
25
+ dep == o.dep && __platform == o.__platform
26
+ end
27
+
28
+ alias eql? ==
29
+
30
+ def type
31
+ @dep.type
32
+ end
33
+
34
+ def to_s
35
+ @dep.to_s
36
+ end
37
+
38
+ private
39
+
40
+ def method_missing(*args)
41
+ @dep.send(*args)
42
+ end
43
+
44
+ end
4
45
  end
5
46
 
6
47
  module Gem
7
48
  @loaded_stacks = Hash.new { |h,k| h[k] = [] }
8
49
 
50
+ module MatchPlatform
51
+ def match_platform(p)
52
+ Gem::Platform::RUBY == platform or
53
+ platform.nil? or p == platform or
54
+ Gem::Platform.new(platform).to_generic == p
55
+ end
56
+ end
57
+
9
58
  class Specification
10
- attr_accessor :source, :location
59
+ attr_accessor :source, :location, :relative_loaded_from
60
+
61
+ alias_method :rg_full_gem_path, :full_gem_path
62
+ alias_method :rg_loaded_from, :loaded_from
63
+
64
+ include MatchPlatform
65
+
66
+ def full_gem_path
67
+ source.respond_to?(:path) ?
68
+ Pathname.new(loaded_from).dirname.expand_path.to_s :
69
+ rg_full_gem_path
70
+ end
71
+
72
+ def loaded_from
73
+ relative_loaded_from ?
74
+ source.path.join(relative_loaded_from).to_s :
75
+ rg_loaded_from
76
+ end
11
77
 
12
78
  def load_paths
13
79
  require_paths.map do |require_path|
@@ -26,8 +92,7 @@ module Gem
26
92
  def git_version
27
93
  if @loaded_from && File.exist?(File.join(full_gem_path, ".git"))
28
94
  sha = Dir.chdir(full_gem_path){ `git rev-parse HEAD`.strip }
29
- branch = full_gem_path.split("-")[3]
30
- (branch && branch != sha) ? " #{branch}-#{sha[0...6]}" : " #{sha[0...6]}"
95
+ " #{sha[0..6]}"
31
96
  end
32
97
  end
33
98
 
@@ -79,8 +144,42 @@ module Gem
79
144
  class Dependency
80
145
  attr_accessor :source, :groups
81
146
 
147
+ alias eql? ==
148
+
82
149
  def to_yaml_properties
83
150
  instance_variables.reject { |p| ["@source", "@groups"].include?(p.to_s) }
84
151
  end
152
+
153
+ def to_lock
154
+ out = " #{name}"
155
+ unless requirement == Gem::Requirement.default
156
+ out << " (#{requirement.to_s})"
157
+ end
158
+ out
159
+ end
160
+ end
161
+
162
+ class Platform
163
+ JAVA = Gem::Platform.new('java')
164
+ MSWIN = Gem::Platform.new('mswin32')
165
+ MING = Gem::Platform.new('x86-mingw32')
166
+
167
+ GENERIC_CACHE = {}
168
+
169
+ class << RUBY
170
+ def to_generic ; self ; end
171
+ end
172
+
173
+ GENERICS = [JAVA, MSWIN, MING, RUBY]
174
+
175
+ def hash
176
+ @cpu.hash + @os.hash + @version.hash
177
+ end
178
+
179
+ alias eql? ==
180
+
181
+ def to_generic
182
+ GENERIC_CACHE[self] ||= GENERICS.find { |p| self =~ p } || RUBY
183
+ end
85
184
  end
86
185
  end
@@ -6,17 +6,16 @@ module Bundler
6
6
 
7
7
  def initialize(*)
8
8
  super
9
- write_rb_lock if locked? && !defined?(Bundler::ENV_LOADED)
9
+ lock
10
10
  end
11
11
 
12
12
  def setup(*groups)
13
13
  # Has to happen first
14
14
  clean_load_path
15
15
 
16
- specs = groups.any? ? specs_for(groups) : requested_specs
16
+ specs = groups.any? ? @definition.specs_for(groups) : requested_specs
17
17
 
18
18
  cripple_rubygems(specs)
19
- replace_rubygems_paths
20
19
 
21
20
  # Activate the specs
22
21
  specs.each do |spec|
@@ -35,37 +34,27 @@ module Bundler
35
34
  def require(*groups)
36
35
  groups.map! { |g| g.to_sym }
37
36
  groups = [:default] if groups.empty?
38
- autorequires = autorequires_for_groups(*groups)
39
-
40
- groups.each do |group|
41
- (autorequires[group] || [[]]).each do |path, explicit|
42
- if explicit
43
- Kernel.require(path)
44
- else
45
- begin
46
- Kernel.require(path)
47
- rescue LoadError
48
- end
37
+
38
+ @definition.dependencies.each do |dep|
39
+ # Skip the dependency if it is not in any of the requested
40
+ # groups
41
+ next unless (dep.groups & groups).any?
42
+
43
+ begin
44
+ # Loop through all the specified autorequires for the
45
+ # dependency. If there are none, use the dependency's name
46
+ # as the autorequire.
47
+ Array(dep.autorequire || dep.name).each do |file|
48
+ Kernel.require file
49
49
  end
50
+ rescue LoadError
51
+ # Only let a LoadError through if the autorequire was explicitly
52
+ # specified by the user.
53
+ raise if dep.autorequire
50
54
  end
51
55
  end
52
56
  end
53
57
 
54
- def dependencies
55
- @definition.dependencies
56
- end
57
-
58
- def actual_dependencies
59
- @definition.actual_dependencies
60
- end
61
-
62
- def lock
63
- sources.each { |s| s.lock if s.respond_to?(:lock) }
64
- FileUtils.mkdir_p("#{root}/.bundle")
65
- write_yml_lock
66
- write_rb_lock
67
- end
68
-
69
58
  def dependencies_for(*groups)
70
59
  if groups.empty?
71
60
  dependencies
@@ -79,78 +68,31 @@ module Bundler
79
68
  def cache
80
69
  FileUtils.mkdir_p(cache_path)
81
70
 
82
- Bundler.ui.info "Copying .gem files into vendor/cache"
71
+ Bundler.ui.info "Updating .gem files in vendor/cache"
83
72
  specs.each do |spec|
84
- next unless spec.source.is_a?(Source::SystemGems) || spec.source.is_a?(Source::Rubygems)
85
- possibilities = Gem.path.map { |p| "#{p.to_s}/cache/#{spec.full_name}.gem" }
86
- cached_path = possibilities.find { |p| File.exist? p }
87
- raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
88
- Bundler.ui.info " * #{File.basename(cached_path)}"
89
- next if File.expand_path(File.dirname(cached_path)) == File.expand_path(cache_path)
90
- FileUtils.cp(cached_path, cache_path)
73
+ spec.source.cache(spec) if spec.source.respond_to?(:cache)
91
74
  end
92
75
  end
93
76
 
94
77
  def prune_cache
95
78
  FileUtils.mkdir_p(cache_path)
79
+
96
80
  Bundler.ui.info "Removing outdated .gem files from vendor/cache"
97
81
  Pathname.glob(cache_path.join("*.gem").to_s).each do |gem_path|
98
- cached_spec = Gem::Format.from_file_by_path(gem_path.to_s).spec
82
+ cached_spec = Gem::Format.from_file_by_path(gem_path).spec
99
83
  next unless Gem::Platform.match(cached_spec.platform)
100
84
  unless specs.any?{|s| s.full_name == cached_spec.full_name }
101
- Bundler.ui.info " * #{File.basename(gem_path.to_s)}"
85
+ Bundler.ui.info " * #{File.basename(gem_path)}"
102
86
  gem_path.rmtree
103
87
  end
104
88
  end
105
89
  end
106
90
 
107
- private
108
-
109
- def load_paths
110
- specs.map { |s| s.load_paths }.flatten
111
- end
91
+ private
112
92
 
113
93
  def cache_path
114
94
  root.join("vendor/cache")
115
95
  end
116
96
 
117
- def write_yml_lock
118
- yml = details.to_yaml
119
- File.open("#{root}/Gemfile.lock", 'w') do |f|
120
- f.puts yml
121
- end
122
- end
123
-
124
- def details
125
- details = {}
126
- details["hash"] = gemfile_fingerprint
127
- details["sources"] = sources.map { |s| { s.class.name.split("::").last => s.options} }
128
-
129
- details["specs"] = specs.map do |s|
130
- options = {"version" => s.version.to_s}
131
- options["source"] = sources.index(s.source) if sources.include?(s.source)
132
- { s.name => options }
133
- end
134
-
135
- details["dependencies"] = @definition.dependencies.inject({}) do |h,d|
136
- info = {"version" => d.requirement.to_s, "group" => d.groups}
137
- info.merge!("require" => d.autorequire) if d.autorequire
138
- h.merge(d.name => info)
139
- end
140
- details
141
- end
142
-
143
- def replace_rubygems_paths
144
- Gem.instance_eval do
145
- def path
146
- [Bundler.bundle_path.to_s]
147
- end
148
-
149
- def source_index
150
- @source_index ||= Gem::SourceIndex.from_installed_gems
151
- end
152
- end
153
- end
154
-
155
97
  end
156
98
  end
@@ -12,16 +12,20 @@ module Bundler
12
12
 
13
13
  def []=(key, value)
14
14
  key = "BUNDLE_#{key.to_s.upcase}"
15
- @config[key] = value
16
- FileUtils.mkdir_p(config_file.dirname)
17
- File.open(config_file, 'w') do |f|
18
- f.puts @config.to_yaml
15
+ unless @config[key] == value
16
+ @config[key] = value
17
+ FileUtils.mkdir_p(config_file.dirname)
18
+ File.open(config_file, 'w') do |f|
19
+ f.puts @config.to_yaml
20
+ end
19
21
  end
20
22
  value
21
23
  end
22
24
 
23
25
  def without=(array)
24
- self[:without] = array.join(":")
26
+ unless array.empty? && without.empty?
27
+ self[:without] = array.join(":")
28
+ end
25
29
  end
26
30
 
27
31
  def without
@@ -1,20 +1,12 @@
1
- # This is not actually required by the actual library
2
- # loads the bundled environment
3
1
  require 'bundler/shared_helpers'
4
2
 
5
3
  if Bundler::SharedHelpers.in_bundle?
6
- env_file = Bundler::SharedHelpers.env_file
7
- if env_file.exist?
8
- require env_file
9
- Bundler.setup if defined?(Bundler::GEM_LOADED)
10
- else
11
- require 'bundler'
12
- begin
13
- Bundler.setup
14
- rescue Bundler::BundlerError => e
15
- puts "\e[31m#{e.message}\e[0m"
16
- exit e.status_code
17
- end
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup
7
+ rescue Bundler::BundlerError => e
8
+ puts "\e[31m#{e.message}\e[0m"
9
+ exit e.status_code
18
10
  end
19
11
 
20
12
  # Add bundler to the load path after disabling system gems
@@ -13,27 +13,6 @@ module Gem
13
13
  end
14
14
 
15
15
  module Bundler
16
- class Specification < Gem::Specification
17
- attr_accessor :relative_loaded_from
18
-
19
- def self.from_gemspec(gemspec)
20
- spec = allocate
21
- gemspec.instance_variables.each do |ivar|
22
- spec.instance_variable_set(ivar, gemspec.instance_variable_get(ivar))
23
- end
24
- spec
25
- end
26
-
27
- def loaded_from
28
- return super unless relative_loaded_from
29
- source.path.join(relative_loaded_from).to_s
30
- end
31
-
32
- def full_gem_path
33
- Pathname.new(loaded_from).dirname.expand_path.to_s
34
- end
35
- end
36
-
37
16
  module SharedHelpers
38
17
  attr_accessor :gem_loaded
39
18
 
@@ -47,10 +26,6 @@ module Bundler
47
26
  find_gemfile
48
27
  end
49
28
 
50
- def env_file
51
- default_gemfile.dirname.join(".bundle/environment.rb")
52
- end
53
-
54
29
  private
55
30
 
56
31
  def find_gemfile
@@ -160,6 +135,8 @@ module Bundler
160
135
  gem_from_path_bin = File.join(File.dirname(spec.loaded_from), spec.bindir, exec_name)
161
136
  File.exist?(gem_bin) ? gem_bin : gem_from_path_bin
162
137
  end
138
+
139
+ Gem.clear_paths
163
140
  end
164
141
 
165
142
  extend self
@@ -1,4 +1,5 @@
1
1
  require "uri"
2
+ require "rubygems/installer"
2
3
  require "rubygems/spec_fetcher"
3
4
  require "rubygems/format"
4
5
  require "digest/sha1"
@@ -6,178 +7,245 @@ require "open3"
6
7
 
7
8
  module Bundler
8
9
  module Source
10
+ # TODO: Refactor this class
9
11
  class Rubygems
10
- attr_reader :uri, :options
12
+ attr_reader :remotes
11
13
 
12
14
  def initialize(options = {})
13
15
  @options = options
14
- @uri = options["uri"].to_s
15
- @uri = "#{uri}/" unless @uri =~ %r'/$'
16
- @uri = URI.parse(@uri) unless @uri.is_a?(URI)
17
- raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
16
+ @remotes = (options["remotes"] || []).map { |r| normalize_uri(r) }
17
+ @allow_remote = false
18
+ # Hardcode the paths for now
19
+ @installed = {}
20
+ @caches = [ Bundler.app_cache ] + Gem.path.map { |p| File.expand_path("#{p}/cache") }
21
+ @spec_fetch_map = {}
18
22
  end
19
23
 
20
- def to_s
21
- "rubygems repository at #{uri}"
24
+ def remote!
25
+ @allow_remote = true
22
26
  end
23
27
 
24
- def specs
25
- @specs ||= fetch_specs
28
+ def [](spec)
29
+ installed_specs[spec].first ||
30
+ @allow_remote && (
31
+ cached_specs[spec].first ||
32
+ remote_specs[spec].first)
26
33
  end
27
34
 
28
- def fetch(spec)
29
- Bundler.ui.debug " * Downloading"
30
- spec.fetch_platform
31
- Gem::RemoteFetcher.fetcher.download(spec, uri, Gem.dir)
35
+ def hash
36
+ Rubygems.hash
32
37
  end
33
38
 
34
- def install(spec)
35
- Bundler.ui.debug " * Installing"
36
- installer = Gem::Installer.new gem_path(spec),
37
- :install_dir => Gem.dir,
38
- :ignore_dependencies => true,
39
- :wrappers => true,
40
- :env_shebang => true,
41
- :bin_dir => "#{Gem.dir}/bin"
39
+ def eql?(o)
40
+ Rubygems === o
41
+ end
42
42
 
43
- installer.install
43
+ alias == eql?
44
44
 
45
- spec.loaded_from = "#{Gem.dir}/specifications/#{spec.full_name}.gemspec"
45
+ # Not really needed, but it seems good to implement this method for interface
46
+ # consistency. Source name is mostly used to identify Path & Git sources
47
+ def name
48
+ ":gems"
46
49
  end
47
50
 
48
- private
51
+ def options
52
+ { "remotes" => @remotes.map { |r| r.to_s } }
53
+ end
49
54
 
50
- def gem_path(spec)
51
- "#{Gem.dir}/cache/#{spec.full_name}.gem"
55
+ def self.from_lock(options)
56
+ s = new(options)
57
+ Array(options["remote"]).each { |r| s.add_remote(r) }
58
+ s
52
59
  end
53
60
 
54
- def fetch_specs
55
- index = Index.new
56
- Bundler.ui.info "Fetching source index from #{uri}"
57
- old, Gem.sources = Gem.sources, ["#{uri}"]
61
+ def to_lock
62
+ out = "GEM\n"
63
+ out << remotes.map {|r| " remote: #{r}\n" }.join
64
+ out << " specs:\n"
65
+ end
58
66
 
59
- fetch_all_specs do |n,v|
60
- v.each do |name, version, platform|
61
- next unless Gem::Platform.match(platform)
62
- spec = RemoteSpecification.new(name, version, platform, @uri)
63
- spec.source = self
64
- index << spec
65
- end
66
- end
67
+ def to_s
68
+ remotes = self.remotes.map { |r| r.to_s }.join(', ')
69
+ "rubygems repository #{remotes}"
70
+ end
67
71
 
68
- index
69
- ensure
70
- Gem.sources = old
72
+ def specs
73
+ @specs ||= @allow_remote ? fetch_specs : installed_specs
71
74
  end
72
75
 
73
- def fetch_all_specs(&blk)
74
- # Fetch all specs, minus prerelease specs
75
- Gem::SpecFetcher.new.list(true, false).each(&blk)
76
- # Then fetch the prerelease specs
77
- begin
78
- Gem::SpecFetcher.new.list(false, true).each(&blk)
79
- rescue Gem::RemoteFetcher::FetchError
80
- Bundler.ui.warn "Could not fetch prerelease specs from #{self}"
81
- end
76
+ def fetch(spec)
77
+ action = @spec_fetch_map[spec.full_name]
78
+ action.call if action
82
79
  end
83
- end
84
80
 
85
- class SystemGems
86
- def specs
87
- @specs ||= begin
88
- index = Index.new
81
+ def install(spec)
82
+ path = cached_gem(spec)
89
83
 
90
- system_paths = Gem::SourceIndex.installed_spec_directories
91
- system_paths.reject!{|d| d == Bundler.specs_path.to_s }
84
+ if @installed[spec.full_name]
85
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) "
86
+ return
87
+ else
88
+ Bundler.ui.info "Installing #{spec.name} (#{spec.version}) "
89
+ end
92
90
 
93
- system_index = Gem::SourceIndex.from_gems_in(*system_paths)
94
- system_index.to_a.reverse.each do |name, spec|
95
- spec.source = self
96
- index << spec
97
- end
91
+ install_path = Bundler.requires_sudo? ? Bundler.tmp : Gem.dir
92
+ installer = Gem::Installer.new path,
93
+ :install_dir => install_path,
94
+ :ignore_dependencies => true,
95
+ :wrappers => true,
96
+ :env_shebang => true,
97
+ :bin_dir => "#{install_path}/bin"
98
+ installer.install
98
99
 
99
- index
100
+ # SUDO HAX
101
+ if Bundler.requires_sudo?
102
+ sudo "mkdir -p #{Gem.dir}/gems #{Gem.dir}/specifications"
103
+ sudo "mv #{Bundler.tmp}/gems/#{spec.full_name} #{Gem.dir}/gems/"
104
+ sudo "mv #{Bundler.tmp}/specifications/#{spec.full_name}.gemspec #{Gem.dir}/specifications/"
100
105
  end
106
+
107
+ spec.loaded_from = "#{Gem.dir}/specifications/#{spec.full_name}.gemspec"
101
108
  end
102
109
 
103
- def to_s
104
- "system gems"
110
+ def sudo(str)
111
+ `sudo -E #{str}`
105
112
  end
106
113
 
107
- def install(spec)
108
- Bundler.ui.debug " * already installed; skipping"
114
+ def cache(spec)
115
+ cached_path = cached_gem(spec)
116
+ raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
117
+ return if File.dirname(cached_path) == Bundler.app_cache.to_s
118
+ Bundler.ui.info " * #{File.basename(cached_path)}"
119
+ FileUtils.cp(cached_path, Bundler.app_cache)
109
120
  end
110
- end
111
121
 
112
- class BundlerGems < SystemGems
113
- def specs
114
- @specs ||= begin
115
- index = Index.new
122
+ def add_remote(source)
123
+ @remotes << normalize_uri(source)
124
+ end
116
125
 
117
- bundle_index = Gem::SourceIndex.from_gems_in(Bundler.specs_path)
118
- bundle_index.to_a.reverse.each do |name, spec|
119
- spec.source = self
120
- index << spec
121
- end
126
+ private
122
127
 
123
- index
124
- end
128
+ def cached_gem(spec)
129
+ possibilities = @caches.map { |p| "#{p}/#{spec.full_name}.gem" }
130
+ possibilities.find { |p| File.exist?(p) }
125
131
  end
126
132
 
127
- def to_s
128
- "bundler gems"
133
+ def normalize_uri(uri)
134
+ uri = uri.to_s
135
+ uri = "#{uri}/" unless uri =~ %r'/$'
136
+ uri = URI(uri)
137
+ raise ArgumentError, "The source must be an absolute URI" unless uri.absolute?
138
+ uri
129
139
  end
130
- end
131
-
132
- class GemCache
133
- attr_reader :options
134
140
 
135
- def initialize(options)
136
- @options = options
137
- @path = options["path"]
141
+ def fetch_specs
142
+ Index.build do |idx|
143
+ idx.use installed_specs
144
+ idx.use cached_specs
145
+ idx.use remote_specs
146
+ end
138
147
  end
139
148
 
140
- def to_s
141
- ".gem files at #{@path}"
149
+ def installed_specs
150
+ @installed_specs ||= begin
151
+ idx = Index.new
152
+ Gem::SourceIndex.from_installed_gems.to_a.reverse.each do |name, spec|
153
+ @installed[spec.full_name] = true
154
+ spec.source = self
155
+ idx << spec
156
+ end
157
+ idx
158
+ end
142
159
  end
143
160
 
144
- def specs
145
- @specs ||= begin
146
- index = Index.new
161
+ def cached_specs
162
+ @cached_specs ||= begin
163
+ idx = Index.new
164
+ @caches.each do |path|
165
+ Dir["#{path}/*.gem"].each do |gemfile|
166
+ s = Gem::Format.from_file_by_path(gemfile).spec
167
+ s.source = self
168
+ idx << s
169
+ end
170
+ end
171
+ idx
172
+ end
173
+ end
147
174
 
148
- Dir["#{@path}/*.gem"].each do |gemfile|
149
- spec = Gem::Format.from_file_by_path(gemfile).spec
150
- next unless Gem::Platform.match(spec.platform)
151
- spec.source = self
152
- index << spec
175
+ def remote_specs
176
+ @remote_specs ||= begin
177
+ idx = Index.new
178
+ remotes = self.remotes.map { |uri| uri.to_s }
179
+ old = Gem.sources
180
+
181
+ remotes.each do |uri|
182
+ Bundler.ui.info "Fetching source index for #{uri}"
183
+ Gem.sources = ["#{uri}"]
184
+ fetch_all_remote_specs do |n,v|
185
+ v.each do |name, version, platform|
186
+ spec = RemoteSpecification.new(name, version, platform, uri)
187
+ spec.source = self
188
+ # Temporary hack until this can be figured out better
189
+ @spec_fetch_map[spec.full_name] = lambda do
190
+ path = download_gem_from_uri(spec, uri)
191
+ s = Gem::Format.from_file_by_path(path).spec
192
+ spec.__swap__(s)
193
+ end
194
+ idx << spec
195
+ end
196
+ end
153
197
  end
198
+ idx
199
+ ensure
200
+ Gem.sources = old
201
+ end
202
+ end
154
203
 
155
- index
204
+ def fetch_all_remote_specs(&blk)
205
+ begin
206
+ # Fetch all specs, minus prerelease specs
207
+ Gem::SpecFetcher.new.list(true, false).each(&blk)
208
+ # Then fetch the prerelease specs
209
+ begin
210
+ Gem::SpecFetcher.new.list(false, true).each(&blk)
211
+ rescue Gem::RemoteFetcher::FetchError
212
+ Bundler.ui.warn "Could not fetch prerelease specs from #{self}"
213
+ end
214
+ rescue Gem::RemoteFetcher::FetchError
215
+ Bundler.ui.warn "Could not reach #{self}"
156
216
  end
157
217
  end
158
218
 
159
- def install(spec)
160
- destination = Gem.dir
219
+ def download_gem_from_uri(spec, uri)
220
+ spec.fetch_platform
161
221
 
162
- Bundler.ui.debug " * Installing from cache"
163
- installer = Gem::Installer.new "#{@path}/#{spec.full_name}.gem",
164
- :install_dir => Gem.dir,
165
- :ignore_dependencies => true,
166
- :wrappers => true,
167
- :env_shebang => true,
168
- :bin_dir => "#{Gem.dir}/bin"
222
+ download_path = Bundler.requires_sudo? ? Bundler.tmp : Gem.dir
223
+ gem_path = "#{Gem.dir}/cache/#{spec.full_name}.gem"
169
224
 
170
- installer.install
171
- spec.loaded_from = "#{Gem.dir}/specifications/#{spec.full_name}.gemspec"
225
+ FileUtils.mkdir_p("#{download_path}/cache")
226
+ Gem::RemoteFetcher.fetcher.download(spec, uri, download_path)
227
+
228
+ if Bundler.requires_sudo?
229
+ sudo "mkdir -p #{Gem.dir}/cache"
230
+ sudo "mv #{Bundler.tmp}/cache/#{spec.full_name}.gem #{gem_path}"
231
+ end
232
+
233
+ gem_path
172
234
  end
173
235
  end
174
236
 
175
237
  class Path
176
238
  attr_reader :path, :options
239
+ # Kind of a hack, but needed for the lock file parser
240
+ attr_accessor :name, :version
241
+
242
+ DEFAULT_GLOB = "{,*/}*.gemspec"
177
243
 
178
244
  def initialize(options)
179
245
  @options = options
180
- @glob = options["glob"] || "{,*/}*.gemspec"
246
+ @glob = options["glob"] || DEFAULT_GLOB
247
+
248
+ @allow_remote = false
181
249
 
182
250
  if options["path"]
183
251
  @path = Pathname.new(options["path"]).expand_path(Bundler.root)
@@ -187,8 +255,40 @@ module Bundler
187
255
  @version = options["version"]
188
256
  end
189
257
 
258
+ def remote!
259
+ @allow_remote = true
260
+ end
261
+
262
+ def self.from_lock(options)
263
+ new(options.merge("path" => options.delete("remote")))
264
+ end
265
+
266
+ def to_lock
267
+ out = "PATH\n"
268
+ out << " remote: #{relative_path}\n"
269
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
270
+ out << " specs:\n"
271
+ end
272
+
190
273
  def to_s
191
- "source code at #{@path}"
274
+ "source at #{@path}"
275
+ end
276
+
277
+ def hash
278
+ self.class.hash
279
+ end
280
+
281
+ def eql?(o)
282
+ Path === o &&
283
+ path == o.path &&
284
+ name == o.name &&
285
+ version == o.version
286
+ end
287
+
288
+ alias == eql?
289
+
290
+ def name
291
+ File.basename(@path.to_s)
192
292
  end
193
293
 
194
294
  def load_spec_files
@@ -213,7 +313,6 @@ module Bundler
213
313
  end
214
314
 
215
315
  if spec
216
- spec = Specification.from_gemspec(spec)
217
316
  spec.loaded_from = file.to_s
218
317
  spec.source = self
219
318
  index << spec
@@ -221,10 +320,11 @@ module Bundler
221
320
  end
222
321
 
223
322
  if index.empty? && @name && @version
224
- index << Specification.new do |s|
323
+ index << Gem::Specification.new do |s|
225
324
  s.name = @name
226
325
  s.source = self
227
326
  s.version = Gem::Version.new(@version)
327
+ s.platform = Gem::Platform::RUBY
228
328
  s.summary = "Fake gemspec for #{@name}"
229
329
  s.relative_loaded_from = "#{@name}.gemspec"
230
330
  if path.join("bin").exist?
@@ -240,19 +340,53 @@ module Bundler
240
340
  index
241
341
  end
242
342
 
343
+ def [](spec)
344
+ specs[spec].first
345
+ end
346
+
243
347
  def local_specs
244
348
  @local_specs ||= load_spec_files
245
349
  end
246
350
 
351
+ class Installer < Gem::Installer
352
+ def initialize(spec)
353
+ @spec = spec
354
+ @bin_dir = "#{Gem.dir}/bin"
355
+ @gem_dir = spec.full_gem_path
356
+ @wrappers = true
357
+ @env_shebang = true
358
+ @format_executable = false
359
+ end
360
+ end
361
+
247
362
  def install(spec)
248
- Bundler.ui.debug " * Using path #{path}"
249
- generate_bin(spec)
363
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
364
+ # Let's be honest, when we're working from a path, we can't
365
+ # really expect native extensions to work because the whole point
366
+ # is to just be able to modify what's in that path and go. So, let's
367
+ # not put ourselves through the pain of actually trying to generate
368
+ # the full gem.
369
+ Installer.new(spec).generate_bin
250
370
  end
251
371
 
252
372
  alias specs local_specs
253
373
 
374
+ def cache(spec)
375
+ unless path.to_s.index(Bundler.root.to_s) == 0
376
+ Bundler.ui.warn " * #{spec.name} at `#{path}` will not be cached."
377
+ end
378
+ end
379
+
254
380
  private
255
381
 
382
+ def relative_path
383
+ if path.to_s.include?(Bundler.root.to_s)
384
+ return path.relative_path_from(Bundler.root)
385
+ end
386
+
387
+ path
388
+ end
389
+
256
390
  def generate_bin(spec)
257
391
  gem_dir = Pathname.new(spec.full_gem_path)
258
392
 
@@ -302,32 +436,68 @@ module Bundler
302
436
 
303
437
  def initialize(options)
304
438
  super
305
- @uri = options["uri"]
306
- @ref = options["ref"] || options["branch"] || options["tag"] || 'master'
439
+ @uri = options["uri"]
440
+ @ref = options["ref"] || options["branch"] || options["tag"] || 'master'
441
+ @revision = options["revision"]
442
+ @update = false
443
+ end
444
+
445
+ def self.from_lock(options)
446
+ new(options.merge("uri" => options.delete("remote")))
447
+ end
448
+
449
+ def to_lock
450
+ out = "GIT\n"
451
+ out << " remote: #{@uri}\n"
452
+ out << " revision: #{shortref_for(revision)}\n"
453
+ %w(ref branch tag).each do |opt|
454
+ out << " #{opt}: #{options[opt]}\n" if options[opt]
455
+ end
456
+ out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB
457
+ out << " specs:\n"
307
458
  end
308
459
 
460
+ def eql?(o)
461
+ Git === o &&
462
+ uri == o.uri &&
463
+ ref == o.ref &&
464
+ name == o.name &&
465
+ version == o.version
466
+ end
467
+
468
+ alias == eql?
469
+
309
470
  def to_s
310
- ref = @options["ref"] ? @options["ref"][0..6] : @ref
471
+ ref = @options["ref"] ? shortref_for(@options["ref"]) : @ref
311
472
  "#{@uri} (at #{ref})"
312
473
  end
313
474
 
475
+ def name
476
+ File.basename(@uri, '.git')
477
+ end
478
+
314
479
  def path
315
- Bundler.install_path.join("#{base_name}-#{uri_hash}-#{ref}")
480
+ Bundler.install_path.join("#{base_name}-#{shortref_for(revision)}")
481
+ end
482
+
483
+ def unlock!
484
+ @revision = nil
316
485
  end
317
486
 
318
487
  def specs
488
+ if @allow_remote && !@update
319
489
  # Start by making sure the git cache is up to date
320
- cache
321
- checkout
490
+ cache
491
+ checkout
492
+ @update = true
493
+ end
322
494
  local_specs
323
495
  end
324
496
 
325
497
  def install(spec)
326
- Bundler.ui.debug " * Using git #{uri}"
498
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
327
499
 
328
- if @installed
329
- Bundler.ui.debug " * Already checked out revision: #{ref}"
330
- else
500
+ unless @installed
331
501
  Bundler.ui.debug " * Checking out revision: #{ref}"
332
502
  checkout
333
503
  @installed = true
@@ -335,11 +505,6 @@ module Bundler
335
505
  generate_bin(spec)
336
506
  end
337
507
 
338
- def lock
339
- @ref = @options["ref"] = revision
340
- checkout
341
- end
342
-
343
508
  def load_spec_files
344
509
  super
345
510
  rescue PathError
@@ -349,7 +514,12 @@ module Bundler
349
514
  private
350
515
 
351
516
  def git(command)
352
- out = %x{git #{command}}
517
+ if Bundler.requires_sudo?
518
+ out = %x{sudo -E git #{command}}
519
+ else
520
+ out = %x{git #{command}}
521
+ end
522
+
353
523
  if $? != 0
354
524
  raise GitError, "An error has occurred in git. Cannot complete bundling."
355
525
  end
@@ -360,6 +530,10 @@ module Bundler
360
530
  File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)},''), ".git")
361
531
  end
362
532
 
533
+ def shortref_for(ref)
534
+ ref[0..6]
535
+ end
536
+
363
537
  def uri_hash
364
538
  if uri =~ %r{^\w+://(\w+@)?}
365
539
  # Downcase the domain component of the URI