bowline-bundler 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/LICENSE +20 -0
  2. data/README.markdown +291 -0
  3. data/Rakefile +45 -0
  4. data/VERSION +1 -0
  5. data/bin/bowline-bundle +59 -0
  6. data/lib/bowline/bundler/bundle.rb +323 -0
  7. data/lib/bowline/bundler/cli.rb +87 -0
  8. data/lib/bowline/bundler/dependency.rb +62 -0
  9. data/lib/bowline/bundler/dsl.rb +182 -0
  10. data/lib/bowline/bundler/environment.rb +87 -0
  11. data/lib/bowline/bundler/finder.rb +51 -0
  12. data/lib/bowline/bundler/gem_bundle.rb +11 -0
  13. data/lib/bowline/bundler/gem_ext.rb +34 -0
  14. data/lib/bowline/bundler/remote_specification.rb +53 -0
  15. data/lib/bowline/bundler/resolver.rb +250 -0
  16. data/lib/bowline/bundler/runtime.rb +2 -0
  17. data/lib/bowline/bundler/source.rb +361 -0
  18. data/lib/bowline/bundler/templates/app_script.erb +3 -0
  19. data/lib/bowline/bundler/templates/environment.erb +156 -0
  20. data/lib/bowline/bundler/templates/environment_picker.erb +4 -0
  21. data/lib/bowline/bundler.rb +35 -0
  22. data/spec/bundler/cli_spec.rb +558 -0
  23. data/spec/bundler/directory_spec.rb +255 -0
  24. data/spec/bundler/dsl_spec.rb +126 -0
  25. data/spec/bundler/fetcher_spec.rb +138 -0
  26. data/spec/bundler/git_spec.rb +266 -0
  27. data/spec/bundler/installer_spec.rb +155 -0
  28. data/spec/bundler/manifest_file_spec.rb +105 -0
  29. data/spec/bundler/manifest_spec.rb +257 -0
  30. data/spec/bundler/runtime_spec.rb +141 -0
  31. data/spec/bundler/system_gems_spec.rb +42 -0
  32. data/spec/quality_spec.rb +57 -0
  33. data/spec/resolver/engine_spec.rb +112 -0
  34. data/spec/resolver/error_spec.rb +50 -0
  35. data/spec/resolver/fake_source_index_spec.rb +43 -0
  36. data/spec/resolver/prerelease_spec.rb +113 -0
  37. data/spec/spec_helper.rb +46 -0
  38. data/spec/support/builders.rb +257 -0
  39. data/spec/support/core_ext.rb +18 -0
  40. data/spec/support/helpers.rb +126 -0
  41. data/spec/support/matchers.rb +201 -0
  42. data/spec/support/path_utils.rb +63 -0
  43. metadata +126 -0
@@ -0,0 +1,361 @@
1
+ module Bundler
2
+ class DirectorySourceError < StandardError; end
3
+ class GitSourceError < StandardError ; end
4
+ # Represents a source of rubygems. Initially, this is only gem repositories, but
5
+ # eventually, this will be git, svn, HTTP
6
+ class Source
7
+ attr_accessor :local
8
+ attr_reader :bundle
9
+
10
+ def initialize(bundle, options)
11
+ @bundle = bundle
12
+ end
13
+
14
+ private
15
+
16
+ def process_source_gems(gems)
17
+ new_gems = Hash.new { |h,k| h[k] = [] }
18
+ gems.values.each do |spec|
19
+ spec.source = self
20
+ new_gems[spec.name] << spec
21
+ end
22
+ new_gems
23
+ end
24
+ end
25
+
26
+ class GemSource < Source
27
+ attr_reader :uri
28
+
29
+ def initialize(bundle, options)
30
+ super
31
+ @uri = options[:uri]
32
+ @uri = URI.parse(@uri) unless @uri.is_a?(URI)
33
+ raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
34
+ end
35
+
36
+ def can_be_local?
37
+ false
38
+ end
39
+
40
+ def gems
41
+ @specs ||= fetch_specs
42
+ end
43
+
44
+ def ==(other)
45
+ uri == other.uri
46
+ end
47
+
48
+ def to_s
49
+ @uri.to_s
50
+ end
51
+
52
+ class RubygemsRetardation < StandardError; end
53
+
54
+ def download(spec)
55
+ Bundler.logger.info "Downloading #{spec.full_name}.gem"
56
+
57
+ destination = bundle.gem_path
58
+
59
+ unless destination.writable?
60
+ raise RubygemsRetardation, "destination: #{destination} is not writable"
61
+ end
62
+
63
+ # Download the gem
64
+ Gem::RemoteFetcher.fetcher.download(spec, uri, destination)
65
+
66
+ # Re-read the gemspec from the downloaded gem to correct
67
+ # any errors that were present in the Rubyforge specification.
68
+ new_spec = Gem::Format.from_file_by_path(destination.join('cache', "#{spec.full_name}.gem")).spec
69
+ spec.__swap__(new_spec)
70
+ end
71
+
72
+ private
73
+
74
+ def fetch_specs
75
+ Bundler.logger.info "Updating source: #{to_s}"
76
+ build_gem_index(fetch_main_specs + fetch_prerelease_specs)
77
+ end
78
+
79
+ def build_gem_index(index)
80
+ gems = Hash.new { |h,k| h[k] = [] }
81
+ index.each do |name, version, platform|
82
+ spec = RemoteSpecification.new(name, version, platform, @uri)
83
+ spec.source = self
84
+ gems[spec.name] << spec if Gem::Platform.match(spec.platform)
85
+ end
86
+ gems
87
+ end
88
+
89
+ def fetch_main_specs
90
+ Marshal.load(Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/specs.4.8.gz"))
91
+ rescue Gem::RemoteFetcher::FetchError => e
92
+ raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
93
+ end
94
+
95
+ def fetch_prerelease_specs
96
+ Marshal.load(Gem::RemoteFetcher.fetcher.fetch_path("#{uri}/prerelease_specs.4.8.gz"))
97
+ rescue Gem::RemoteFetcher::FetchError
98
+ Bundler.logger.warn "Source '#{uri}' does not support prerelease gems"
99
+ []
100
+ end
101
+ end
102
+
103
+ class SystemGemSource < Source
104
+
105
+ def self.instance
106
+ @instance
107
+ end
108
+
109
+ def self.new(*args)
110
+ @instance ||= super
111
+ end
112
+
113
+ def initialize(bundle, options = {})
114
+ super
115
+ @source = Gem::SourceIndex.from_installed_gems
116
+ end
117
+
118
+ def can_be_local?
119
+ false
120
+ end
121
+
122
+ def gems
123
+ @gems ||= process_source_gems(@source.gems)
124
+ end
125
+
126
+ def ==(other)
127
+ other.is_a?(SystemGemSource)
128
+ end
129
+
130
+ def to_s
131
+ "system"
132
+ end
133
+
134
+ def download(spec)
135
+ gemfile = Pathname.new(spec.loaded_from)
136
+ gemfile = gemfile.dirname.join('..', 'cache', "#{spec.full_name}.gem")
137
+ bundle.cache(gemfile)
138
+ end
139
+
140
+ private
141
+
142
+ end
143
+
144
+ class GemDirectorySource < Source
145
+ attr_reader :location
146
+
147
+ def initialize(bundle, options)
148
+ super
149
+ @location = options[:location]
150
+ end
151
+
152
+ def can_be_local?
153
+ true
154
+ end
155
+
156
+ def gems
157
+ @specs ||= fetch_specs
158
+ end
159
+
160
+ def ==(other)
161
+ location == other.location
162
+ end
163
+
164
+ def to_s
165
+ location.to_s
166
+ end
167
+
168
+ def download(spec)
169
+ # raise NotImplementedError
170
+ end
171
+
172
+ private
173
+
174
+ def fetch_specs
175
+ specs = Hash.new { |h,k| h[k] = [] }
176
+
177
+ Dir["#{@location}/*.gem"].each do |gemfile|
178
+ spec = Gem::Format.from_file_by_path(gemfile).spec
179
+ spec.source = self
180
+ specs[spec.name] << spec
181
+ end
182
+
183
+ specs
184
+ end
185
+ end
186
+
187
+ class DirectorySource < Source
188
+ attr_reader :location, :specs, :required_specs
189
+
190
+ def initialize(bundle, options)
191
+ super
192
+ if options[:location]
193
+ @location = Pathname.new(options[:location]).expand_path
194
+ end
195
+ @glob = options[:glob] || "**/*.gemspec"
196
+ @specs = {}
197
+ @required_specs = []
198
+ end
199
+
200
+ def add_spec(path, name, version, require_paths = %w(lib))
201
+ raise DirectorySourceError, "already have a gem defined for '#{path}'" if @specs[path.to_s]
202
+ @specs[path.to_s] = Gem::Specification.new do |s|
203
+ s.name = name
204
+ s.version = Gem::Version.new(version)
205
+ end
206
+ end
207
+
208
+ def can_be_local?
209
+ true
210
+ end
211
+
212
+ def gems
213
+ @gems ||= begin
214
+ # Locate all gemspecs from the directory
215
+ specs = locate_gemspecs
216
+ specs = merge_defined_specs(specs)
217
+
218
+ required_specs.each do |required|
219
+ unless specs.any? {|k,v| v.name == required }
220
+ raise DirectorySourceError, "No gemspec for '#{required}' was found in" \
221
+ " '#{location}'. Please explicitly specify a version."
222
+ end
223
+ end
224
+
225
+ process_source_gems(specs)
226
+ end
227
+ end
228
+
229
+ def locate_gemspecs
230
+ Dir["#{location}/#{@glob}"].inject({}) do |specs, file|
231
+ file = Pathname.new(file)
232
+ if spec = eval(File.read(file)) # and validate_gemspec(file.dirname, spec)
233
+ spec.location = file.dirname.expand_path
234
+ specs[spec.full_name] = spec
235
+ end
236
+ specs
237
+ end
238
+ end
239
+
240
+ def merge_defined_specs(specs)
241
+ @specs.each do |path, spec|
242
+ # Set the spec location
243
+ spec.location = "#{location}/#{path}"
244
+
245
+ if existing = specs.values.find { |s| s.name == spec.name }
246
+ if existing.version != spec.version
247
+ raise DirectorySourceError, "The version you specified for #{spec.name}" \
248
+ " is #{spec.version}. The gemspec is #{existing.version}."
249
+ # Not sure if this is needed
250
+ # ====
251
+ # elsif File.expand_path(existing.location) != File.expand_path(spec.location)
252
+ # raise DirectorySourceError, "The location you specified for #{spec.name}" \
253
+ # " is '#{spec.location}'. The gemspec was found at '#{existing.location}'."
254
+ end
255
+ # elsif !validate_gemspec(spec.location, spec)
256
+ # raise "Your gem definition is not valid: #{spec}"
257
+ else
258
+ specs[spec.full_name] = spec
259
+ end
260
+ end
261
+ specs
262
+ end
263
+
264
+ def validate_gemspec(path, spec)
265
+ path = Pathname.new(path)
266
+ msg = "Gemspec for #{spec.name} (#{spec.version}) is invalid:"
267
+ # Check the require_paths
268
+ (spec.require_paths || []).each do |require_path|
269
+ unless path.join(require_path).directory?
270
+ Bundler.logger.warn "#{msg} Missing require path: '#{require_path}'"
271
+ return false
272
+ end
273
+ end
274
+
275
+ # Check the executables
276
+ (spec.executables || []).each do |exec|
277
+ unless path.join(spec.bindir, exec).file?
278
+ Bundler.logger.warn "#{msg} Missing executable: '#{File.join(spec.bindir, exec)}'"
279
+ return false
280
+ end
281
+ end
282
+
283
+ true
284
+ end
285
+
286
+ def ==(other)
287
+ # TMP HAX
288
+ other.is_a?(DirectorySource)
289
+ end
290
+
291
+ def to_s
292
+ "directory: '#{location}'"
293
+ end
294
+
295
+ def download(spec)
296
+ # Nothing needed here
297
+ end
298
+ end
299
+
300
+ class GitSource < DirectorySource
301
+ attr_reader :ref, :uri, :branch
302
+
303
+ def initialize(bundle, options)
304
+ super
305
+ @uri = options[:uri]
306
+ @branch = options[:branch] || 'master'
307
+ @ref = options[:ref] || "origin/#{@branch}"
308
+ end
309
+
310
+ def location
311
+ # TMP HAX to get the *.gemspec reading to work
312
+ bundle.gem_path.join('dirs', File.basename(@uri, '.git'))
313
+ end
314
+
315
+ def gems
316
+ update
317
+ checkout
318
+ super
319
+ end
320
+
321
+ def download(spec)
322
+ # Nothing needed here
323
+ end
324
+
325
+ def to_s
326
+ "git: #{uri}"
327
+ end
328
+
329
+ private
330
+ def update
331
+ if location.directory?
332
+ fetch
333
+ else
334
+ clone
335
+ end
336
+ end
337
+
338
+ def fetch
339
+ unless local
340
+ Bundler.logger.info "Fetching git repository at: #{@uri}"
341
+ Dir.chdir(location) { `git fetch origin` }
342
+ end
343
+ end
344
+
345
+ def clone
346
+ # Raise an error if the source should run in local mode,
347
+ # but it has not been cached yet.
348
+ if local
349
+ raise SourceNotCached, "Git repository '#{@uri}' has not been cloned yet"
350
+ end
351
+
352
+ Bundler.logger.info "Cloning git repository at: #{@uri}"
353
+ FileUtils.mkdir_p(location.dirname)
354
+ `git clone #{@uri} #{location} --no-hardlinks`
355
+ end
356
+
357
+ def checkout
358
+ Dir.chdir(location) { `git checkout --quiet #{@ref}` }
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,3 @@
1
+ <%= shebang bin_file_name %>
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "<%= path.join("environment").relative_path_from(Pathname.new(bin_dir)) %>"))
3
+ load File.expand_path(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,156 @@
1
+ # DO NOT MODIFY THIS FILE
2
+ module Bundler
3
+ file = File.expand_path(__FILE__)
4
+ dir = File.dirname(file)
5
+
6
+ <% unless system_gems? -%>
7
+ ENV["GEM_HOME"] = dir
8
+ ENV["GEM_PATH"] = dir
9
+
10
+ # handle 1.9 where system gems are always on the load path
11
+ if defined?(::Gem)
12
+ $LOAD_PATH.reject! do |p|
13
+ p != File.dirname(__FILE__) &&
14
+ Gem.path.any? { |gp| p.include?(gp) }
15
+ end
16
+ end
17
+
18
+ <% end -%>
19
+ ENV["PATH"] = "#{dir}/<%= bindir %><%= File::PATH_SEPARATOR %>#{ENV["PATH"]}"
20
+ ENV["RUBYOPT"] = "-r#{file} #{ENV["RUBYOPT"]}"
21
+
22
+ <% load_paths.each do |load_path| -%>
23
+ $LOAD_PATH.unshift File.expand_path("#{dir}/<%= load_path %>")
24
+ <% end -%>
25
+
26
+ @gemfile = "#{dir}/<%= filename %>"
27
+
28
+ <% if rubygems? -%>
29
+ require "rubygems" unless respond_to?(:gem) # 1.9 already has RubyGems loaded
30
+
31
+ @bundled_specs = {}
32
+ <% specs.each do |spec| -%>
33
+ <% if spec.no_bundle? -%>
34
+ gem "<%= spec.name %>", "<%= spec.version %>"
35
+ <% else -%>
36
+ <% path = spec_file_for(spec) -%>
37
+ @bundled_specs["<%= spec.name %>"] = eval(File.read("#{dir}/<%= path %>"))
38
+ @bundled_specs["<%= spec.name %>"].loaded_from = "#{dir}/<%= path %>"
39
+ <% end -%>
40
+ <% end -%>
41
+
42
+ def self.add_specs_to_loaded_specs
43
+ Gem.loaded_specs.merge! @bundled_specs
44
+ end
45
+
46
+ def self.add_specs_to_index
47
+ @bundled_specs.each do |name, spec|
48
+ Gem.source_index.add_spec spec
49
+ end
50
+ end
51
+
52
+ add_specs_to_loaded_specs
53
+ add_specs_to_index
54
+ <% end -%>
55
+
56
+ def self.require_env(env = nil)
57
+ context = Class.new do
58
+ def initialize(env) @env = env && env.to_s ; end
59
+ def method_missing(*) ; yield if block_given? ; end
60
+ def only(*env)
61
+ old, @only = @only, _combine_only(env.flatten)
62
+ yield
63
+ @only = old
64
+ end
65
+ def except(*env)
66
+ old, @except = @except, _combine_except(env.flatten)
67
+ yield
68
+ @except = old
69
+ end
70
+ def gem(name, *args)
71
+ opt = args.last.is_a?(Hash) ? args.pop : {}
72
+ only = _combine_only(opt[:only] || opt["only"])
73
+ except = _combine_except(opt[:except] || opt["except"])
74
+ files = opt[:require_as] || opt["require_as"] || name
75
+ files = [files] unless files.respond_to?(:each)
76
+
77
+ return unless !only || only.any? {|e| e == @env }
78
+ return if except && except.any? {|e| e == @env }
79
+
80
+ if files = opt[:require_as] || opt["require_as"]
81
+ files = Array(files)
82
+ files.each { |f| require f }
83
+ else
84
+ begin
85
+ require name
86
+ rescue LoadError
87
+ # Do nothing
88
+ end
89
+ end
90
+ yield if block_given?
91
+ true
92
+ end
93
+ private
94
+ def _combine_only(only)
95
+ return @only unless only
96
+ only = [only].flatten.compact.uniq.map { |o| o.to_s }
97
+ only &= @only if @only
98
+ only
99
+ end
100
+ def _combine_except(except)
101
+ return @except unless except
102
+ except = [except].flatten.compact.uniq.map { |o| o.to_s }
103
+ except |= @except if @except
104
+ except
105
+ end
106
+ end
107
+ context.new(env && env.to_s).instance_eval(File.read(@gemfile), @gemfile, 1)
108
+ end
109
+ end
110
+
111
+ <% if rubygems? -%>
112
+ module Gem
113
+ @loaded_stacks = Hash.new { |h,k| h[k] = [] }
114
+
115
+ def source_index.refresh!
116
+ super
117
+ Bundler.add_specs_to_index
118
+ end
119
+ end
120
+ <% else -%>
121
+ $" << "rubygems.rb"
122
+
123
+ module Kernel
124
+ def gem(*)
125
+ # Silently ignore calls to gem, since, in theory, everything
126
+ # is activated correctly already.
127
+ end
128
+ end
129
+
130
+ # Define all the Gem errors for gems that reference them.
131
+ module Gem
132
+ def self.ruby ; <%= Gem.ruby.inspect %> ; end
133
+ def self.dir ; @dir ||= File.dirname(File.expand_path(__FILE__)) ; end
134
+ class << self ; alias default_dir dir; alias path dir ; end
135
+ class LoadError < ::LoadError; end
136
+ class Exception < RuntimeError; end
137
+ class CommandLineError < Exception; end
138
+ class DependencyError < Exception; end
139
+ class DependencyRemovalException < Exception; end
140
+ class GemNotInHomeException < Exception ; end
141
+ class DocumentError < Exception; end
142
+ class EndOfYAMLException < Exception; end
143
+ class FilePermissionError < Exception; end
144
+ class FormatException < Exception; end
145
+ class GemNotFoundException < Exception; end
146
+ class InstallError < Exception; end
147
+ class InvalidSpecificationException < Exception; end
148
+ class OperationNotSupportedError < Exception; end
149
+ class RemoteError < Exception; end
150
+ class RemoteInstallationCancelled < Exception; end
151
+ class RemoteInstallationSkipped < Exception; end
152
+ class RemoteSourceException < Exception; end
153
+ class VerificationError < Exception; end
154
+ class SystemExitException < SystemExit; end
155
+ end
156
+ <% end -%>
@@ -0,0 +1,4 @@
1
+ require 'rbconfig'
2
+ engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
3
+ version = '1.9.1'
4
+ require File.expand_path("../#{engine}/#{version}/environment", __FILE__)
@@ -0,0 +1,35 @@
1
+ require 'pathname'
2
+ require 'logger'
3
+ require 'set'
4
+ require 'erb'
5
+ # Required elements of rubygems
6
+ require "rubygems/remote_fetcher"
7
+ require "rubygems/installer"
8
+
9
+ require "bowline/bundler/gem_bundle"
10
+ require "bowline/bundler/source"
11
+ require "bowline/bundler/finder"
12
+ require "bowline/bundler/gem_ext"
13
+ require "bowline/bundler/resolver"
14
+ require "bowline/bundler/environment"
15
+ require "bowline/bundler/dsl"
16
+ require "bowline/bundler/cli"
17
+ require "bowline/bundler/bundle"
18
+ require "bowline/bundler/dependency"
19
+ require "bowline/bundler/remote_specification"
20
+
21
+ module Bundler
22
+ VERSION = "0.0.1"
23
+
24
+ class << self
25
+ attr_writer :logger
26
+
27
+ def logger
28
+ @logger ||= begin
29
+ logger = Logger.new(STDOUT, Logger::INFO)
30
+ logger.formatter = proc {|_,_,_,msg| "#{msg}\n" }
31
+ logger
32
+ end
33
+ end
34
+ end
35
+ end