bowline-bundler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,323 @@
1
+ module Bundler
2
+ class InvalidRepository < StandardError ; end
3
+
4
+ class Bundle
5
+ attr_reader :gemfile, :environment
6
+
7
+ def self.load(gemfile = nil)
8
+ gemfile = Pathname.new(gemfile || default_gemfile).expand_path
9
+
10
+ unless gemfile.file?
11
+ raise ManifestFileNotFound, "Manifest file not found: #{gemfile.to_s.inspect}"
12
+ end
13
+
14
+ new(gemfile)
15
+ end
16
+
17
+ def self.default_gemfile
18
+ current = Pathname.new(Dir.pwd)
19
+
20
+ until current.root?
21
+ filename = current.join("Gemfile")
22
+ return filename if filename.exist?
23
+ current = current.parent
24
+ end
25
+
26
+ raise DefaultManifestNotFound
27
+ end
28
+
29
+ # TODO: passing in the filename is not good
30
+ def initialize(gemfile)
31
+ @gemfile = gemfile
32
+ @environment = Environment.new(self)
33
+ Dsl.evaluate(gemfile, self, @environment)
34
+
35
+ # path = env.gem_path
36
+
37
+ FileUtils.mkdir_p(gem_path)
38
+
39
+ @cache_path = gem_path.join('cache')
40
+ @cache = GemDirectorySource.new(self, :location => @cache_path)
41
+
42
+ @specs_path = gem_path.join('specifications')
43
+ @gems_path = gem_path.join('gems')
44
+ end
45
+
46
+ def root
47
+ gemfile.parent
48
+ end
49
+
50
+ def path
51
+ @path ||= root.join("vendor/gems")
52
+ end
53
+
54
+ def path=(path)
55
+ @path = (path.relative? ? root.join(path) : path).expand_path
56
+ end
57
+
58
+ def gem_path
59
+ path.join("#{Gem.ruby_engine}/#{Gem::ConfigMap[:ruby_version]}")
60
+ end
61
+
62
+ def bindir
63
+ @bindir ||= root.join("bin")
64
+ end
65
+
66
+ def bindir=(path)
67
+ @bindir = (path.relative? ? root.join(path) : path).expand_path
68
+ end
69
+
70
+ def install(options = {})
71
+ dependencies = @environment.dependencies
72
+ sources = @environment.sources
73
+
74
+ # ========== from env
75
+ if only_envs = options[:only]
76
+ dependencies.reject! { |d| !only_envs.any? {|env| d.in?(env) } }
77
+ end
78
+ # ==========
79
+
80
+ # TODO: clean this up
81
+ sources.each do |s|
82
+ s.local = options[:cached]
83
+ end
84
+
85
+ # Check to see whether the existing cache meets all the requirements
86
+ begin
87
+ valid = nil
88
+ # valid = Resolver.resolve(dependencies, [source_index], source_requirements)
89
+ rescue Bundler::GemNotFound
90
+ end
91
+
92
+ sources = only_local(sources) if options[:cached]
93
+
94
+ # Check the remote sources if the existing cache does not meet the requirements
95
+ # or the user passed --update
96
+ if options[:update] || !valid
97
+ Bundler.logger.info "Calculating dependencies..."
98
+ bundle = Resolver.resolve(dependencies, [@cache] + sources)
99
+ download(bundle, options)
100
+ do_install(bundle, options)
101
+ valid = bundle
102
+ end
103
+
104
+ generate_bins(valid, options)
105
+ cleanup(valid, options)
106
+ configure(valid, options)
107
+
108
+ Bundler.logger.info "Done."
109
+ end
110
+
111
+ def cache(*gemfiles)
112
+ FileUtils.mkdir_p(@cache_path)
113
+ gemfiles.each do |gemfile|
114
+ Bundler.logger.info "Caching: #{File.basename(gemfile)}"
115
+ FileUtils.cp(gemfile, @cache_path)
116
+ end
117
+ end
118
+
119
+ def list_outdated(options={})
120
+ outdated_gems = source_index.outdated.sort
121
+
122
+ if outdated_gems.empty?
123
+ Bundler.logger.info "All gems are up to date."
124
+ else
125
+ Bundler.logger.info "Outdated gems:"
126
+ outdated_gems.each do |name|
127
+ Bundler.logger.info " * #{name}"
128
+ end
129
+ end
130
+ end
131
+
132
+ def prune(options = {})
133
+ dependencies, sources = @environment.gem_dependencies, @environment.sources
134
+
135
+ sources.each do |s|
136
+ s.local = true
137
+ end
138
+
139
+ sources = only_local(sources)
140
+ bundle = Resolver.resolve(dependencies, [@cache] + sources)
141
+ @cache.gems.each do |name, specs|
142
+ specs.each do |spec|
143
+ unless bundle.any? { |s| s.name == spec.name && s.version == spec.version }
144
+ Bundler.logger.info "Pruning #{spec.name} (#{spec.version}) from the cache"
145
+ FileUtils.rm @cache_path.join("#{spec.full_name}.gem")
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ def list(options = {})
152
+ Bundler.logger.info "Currently bundled gems:"
153
+ gems.each do |spec|
154
+ Bundler.logger.info " * #{spec.name} (#{spec.version})"
155
+ end
156
+ end
157
+
158
+ def gems
159
+ source_index.gems.values
160
+ end
161
+
162
+ def source_index
163
+ index = Gem::SourceIndex.from_gems_in(@specs_path)
164
+ index.each { |n, spec| spec.loaded_from = @specs_path.join("#{spec.full_name}.gemspec") }
165
+ index
166
+ end
167
+
168
+ def download_path_for(type)
169
+ @repos[type].download_path_for
170
+ end
171
+
172
+ def setup_environment
173
+ unless @environment.system_gems
174
+ ENV["GEM_HOME"] = gem_path
175
+ ENV["GEM_PATH"] = gem_path
176
+ end
177
+ ENV["PATH"] = "#{bindir}:#{ENV["PATH"]}"
178
+ ENV["RUBYOPT"] = "-r#{gem_path}/environment #{ENV["RUBYOPT"]}"
179
+ end
180
+
181
+ private
182
+
183
+ def only_local(sources)
184
+ sources.select { |s| s.can_be_local? }
185
+ end
186
+
187
+ def download(bundle, options)
188
+ bundle.sort_by {|s| s.full_name.downcase }.each do |spec|
189
+ next if spec.no_bundle?
190
+ spec.source.download(spec)
191
+ end
192
+ end
193
+
194
+ def do_install(bundle, options)
195
+ bundle.each do |spec|
196
+ next if spec.no_bundle?
197
+ spec.loaded_from = @specs_path.join("#{spec.full_name}.gemspec")
198
+ # Do nothing if the gem is already expanded
199
+ next if @gems_path.join(spec.full_name).directory?
200
+
201
+ case spec.source
202
+ when GemSource, GemDirectorySource, SystemGemSource
203
+ expand_gemfile(spec, options)
204
+ else
205
+ expand_vendored_gem(spec, options)
206
+ end
207
+ end
208
+ end
209
+
210
+ def generate_bins(bundle, options)
211
+ bundle.each do |spec|
212
+ next if spec.no_bundle?
213
+ # HAX -- Generate the bin
214
+ bin_dir = bindir
215
+ path = gem_path
216
+ gems_path = @gems_path
217
+ installer = Gem::Installer.allocate
218
+ installer.instance_eval do
219
+ @spec = spec
220
+ @bin_dir = bin_dir
221
+ @gem_dir = gems_path.join(spec.full_name)
222
+ @gem_home = path
223
+ @wrappers = true
224
+ @format_executable = false
225
+ @env_shebang = false
226
+ end
227
+ installer.generate_bin
228
+ end
229
+ end
230
+
231
+ def expand_gemfile(spec, options)
232
+ Bundler.logger.info "Installing #{spec.name} (#{spec.version})"
233
+
234
+ gemfile = @cache_path.join("#{spec.full_name}.gem").to_s
235
+
236
+ if build_args = options[:build_options] && options[:build_options][spec.name]
237
+ Gem::Command.build_args = build_args.map {|k,v| "--with-#{k}=#{v}"}
238
+ end
239
+
240
+ installer = Gem::Installer.new(gemfile, options.merge(
241
+ :install_dir => gem_path,
242
+ :ignore_dependencies => true,
243
+ :env_shebang => true,
244
+ :wrappers => true,
245
+ :bin_dir => bindir
246
+ ))
247
+ installer.install
248
+ rescue Gem::InstallError
249
+ cleanup_spec(spec)
250
+ raise
251
+ ensure
252
+ Gem::Command.build_args = []
253
+ end
254
+
255
+ def expand_vendored_gem(spec, options)
256
+ add_spec(spec)
257
+ FileUtils.mkdir_p(@gems_path)
258
+ File.symlink(spec.location, @gems_path.join(spec.full_name))
259
+ end
260
+
261
+ def add_spec(spec)
262
+ destination = @specs_path
263
+ destination.mkdir unless destination.exist?
264
+
265
+ File.open(destination.join("#{spec.full_name}.gemspec"), 'w') do |f|
266
+ f.puts spec.to_ruby
267
+ end
268
+ end
269
+
270
+ def cleanup(valid, options)
271
+ to_delete = gems
272
+ to_delete.delete_if do |spec|
273
+ valid.any? { |other| spec.name == other.name && spec.version == other.version }
274
+ end
275
+
276
+ valid_executables = valid.map { |s| s.executables }.flatten.compact
277
+
278
+ to_delete.each do |spec|
279
+ Bundler.logger.info "Deleting gem: #{spec.name} (#{spec.version})"
280
+ cleanup_spec(spec)
281
+ # Cleanup the bin directory
282
+ spec.executables.each do |bin|
283
+ next if valid_executables.include?(bin)
284
+ Bundler.logger.info "Deleting bin file: #{bin}"
285
+ FileUtils.rm_rf(bindir.join(bin))
286
+ end
287
+ end
288
+ end
289
+
290
+ def cleanup_spec(spec)
291
+ FileUtils.rm_rf(@specs_path.join("#{spec.full_name}.gemspec"))
292
+ FileUtils.rm_rf(@gems_path.join(spec.full_name))
293
+ end
294
+
295
+ def expand(options)
296
+ each_repo do |repo|
297
+ repo.expand(options)
298
+ end
299
+ end
300
+
301
+ def configure(specs, options)
302
+ FileUtils.mkdir_p(gem_path)
303
+
304
+ File.open(gem_path.join("environment.rb"), "w") do |file|
305
+ file.puts @environment.environment_rb(specs, options)
306
+ end
307
+
308
+ generate_environment_picker
309
+ end
310
+
311
+ def generate_environment_picker
312
+ FileUtils.cp("#{File.dirname(__FILE__)}/templates/environment_picker.erb", path.join("environment.rb"))
313
+ end
314
+
315
+ def require_code(file, dep)
316
+ constraint = case
317
+ when dep.only then %{ if #{dep.only.inspect}.include?(env)}
318
+ when dep.except then %{ unless #{dep.except.inspect}.include?(env)}
319
+ end
320
+ "require #{file.inspect}#{constraint}"
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,87 @@
1
+ require "optparse"
2
+
3
+ module Bundler
4
+ class CLI
5
+ def self.run(command, options = {})
6
+ new(options).run(command)
7
+ rescue DefaultManifestNotFound => e
8
+ Bundler.logger.error "Could not find a Gemfile to use"
9
+ exit 3
10
+ rescue InvalidEnvironmentName => e
11
+ Bundler.logger.error "Gemfile error: #{e.message}"
12
+ exit 4
13
+ rescue InvalidRepository => e
14
+ Bundler.logger.error e.message
15
+ exit 5
16
+ rescue VersionConflict => e
17
+ Bundler.logger.error e.message
18
+ exit 6
19
+ rescue GemNotFound => e
20
+ Bundler.logger.error e.message
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
28
+ rescue ManifestFileNotFound => e
29
+ Bundler.logger.error e.message
30
+ exit 10
31
+ end
32
+
33
+ def initialize(options)
34
+ @options = options
35
+ @bundle = Bundle.load(@options[:manifest])
36
+ end
37
+
38
+ def bundle
39
+ @bundle.install(@options)
40
+ end
41
+
42
+ def cache
43
+ gemfile = @options[:cache]
44
+
45
+ if File.extname(gemfile) == ".gem"
46
+ if !File.exist?(gemfile)
47
+ raise InvalidCacheArgument, "'#{gemfile}' does not exist."
48
+ end
49
+ @bundle.cache(gemfile)
50
+ elsif File.directory?(gemfile) || gemfile.include?('/')
51
+ if !File.directory?(gemfile)
52
+ raise InvalidCacheArgument, "'#{gemfile}' does not exist."
53
+ end
54
+ gemfiles = Dir["#{gemfile}/*.gem"]
55
+ if gemfiles.empty?
56
+ raise InvalidCacheArgument, "'#{gemfile}' contains no gemfiles"
57
+ end
58
+ @bundle.cache(*gemfiles)
59
+ else
60
+ raise InvalidCacheArgument, "w0t? '#{gemfile}' means nothing to me."
61
+ end
62
+ end
63
+
64
+ def prune
65
+ @bundle.prune(@options)
66
+ end
67
+
68
+ def list
69
+ @bundle.list(@options)
70
+ end
71
+
72
+ def list_outdated
73
+ @bundle.list_outdated(@options)
74
+ end
75
+
76
+ def exec
77
+ @bundle.setup_environment
78
+ # w0t?
79
+ super(*$command)
80
+ end
81
+
82
+ def run(command)
83
+ send(command)
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,62 @@
1
+ module Bundler
2
+ class InvalidEnvironmentName < StandardError; end
3
+
4
+ class Dependency < Gem::Dependency
5
+ attr_reader :name, :version, :require_as, :only, :except
6
+ attr_accessor :source
7
+
8
+ def initialize(name, options = {}, &block)
9
+ options.each do |k, v|
10
+ options[k.to_s] = v
11
+ end
12
+
13
+ super(name, options["version"] || ">= 0")
14
+
15
+ @require_as = options["require_as"]
16
+ @only = options["only"]
17
+ @except = options["except"]
18
+ @source = options["source"]
19
+ @block = block
20
+
21
+ if (@only && @only.include?("rubygems")) || (@except && @except.include?("rubygems"))
22
+ raise InvalidEnvironmentName, "'rubygems' is not a valid environment name"
23
+ end
24
+ end
25
+
26
+ def in?(environment)
27
+ environment = environment.to_s
28
+
29
+ return false unless !@only || @only.include?(environment)
30
+ return false if @except && @except.include?(environment)
31
+ true
32
+ end
33
+
34
+ def require_env(environment)
35
+ return unless in?(environment)
36
+
37
+ if @require_as
38
+ Array(@require_as).each { |file| require file }
39
+ else
40
+ begin
41
+ require name
42
+ rescue LoadError
43
+ # Do nothing
44
+ end
45
+ end
46
+
47
+ @block.call if @block
48
+ end
49
+
50
+ def no_bundle?
51
+ source == SystemGemSource.instance
52
+ end
53
+
54
+ def ==(o)
55
+ [name, version, require_as, only, except] ==
56
+ [o.name, o.version, o.require_as, o.only, o.except]
57
+ end
58
+
59
+ alias version version_requirements
60
+
61
+ end
62
+ end
@@ -0,0 +1,182 @@
1
+ module Bundler
2
+ class ManifestFileNotFound < StandardError; end
3
+ class InvalidKey < StandardError; end
4
+ class DefaultManifestNotFound < StandardError; end
5
+
6
+ class Dsl
7
+ def self.evaluate(file, bundle, environment)
8
+ builder = new(bundle, environment)
9
+ builder.instance_eval(File.read(file.to_s), file.to_s, 1)
10
+ environment
11
+ end
12
+
13
+ def initialize(bundle, environment)
14
+ @bundle = bundle
15
+ @environment = environment
16
+ @directory_sources = []
17
+ @git_sources = {}
18
+ @only, @except, @directory, @git = nil, nil, nil, nil
19
+ end
20
+
21
+ def bundle_path(path)
22
+ @bundle.path = Pathname.new(path)
23
+ end
24
+
25
+ def bin_path(path)
26
+ @bundle.bindir = Pathname.new(path)
27
+ end
28
+
29
+ def disable_rubygems
30
+ @environment.rubygems = false
31
+ end
32
+
33
+ def disable_system_gems
34
+ @environment.system_gems = false
35
+ end
36
+
37
+ def source(source)
38
+ source = GemSource.new(@bundle, :uri => source)
39
+ unless @environment.sources.include?(source)
40
+ @environment.add_source(source)
41
+ end
42
+ end
43
+
44
+ def only(*env)
45
+ old, @only = @only, _combine_only(env)
46
+ yield
47
+ @only = old
48
+ end
49
+
50
+ def except(*env)
51
+ old, @except = @except, _combine_except(env)
52
+ yield
53
+ @except = old
54
+ end
55
+
56
+ def directory(path, options = {})
57
+ raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
58
+ @directory = DirectorySource.new(@bundle, options.merge(:location => path))
59
+ @directory_sources << @directory
60
+ @environment.add_priority_source(@directory)
61
+ retval = yield if block_given?
62
+ @directory = nil
63
+ retval
64
+ end
65
+
66
+ def git(uri, options = {})
67
+ raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
68
+ @git = GitSource.new(@bundle, options.merge(:uri => uri))
69
+ @git_sources[uri] = @git
70
+ @environment.add_priority_source(@git)
71
+ retval = yield if block_given?
72
+ @git = nil
73
+ retval
74
+ end
75
+
76
+ def clear_sources
77
+ @environment.clear_sources
78
+ end
79
+
80
+ def gem(name, *args)
81
+ options = args.last.is_a?(Hash) ? args.pop : {}
82
+ version = args.last
83
+
84
+ keys = :vendored_at, :path, :only, :except, :git, :path, :bundle, :require_as, :tag, :branch, :ref
85
+ unless (invalid = options.keys - keys).empty?
86
+ raise InvalidKey, "Only #{keys.join(", ")} are valid options to #gem. You used #{invalid.join(", ")}"
87
+ end
88
+
89
+ if path = options.delete(:vendored_at)
90
+ options[:path] = path
91
+ warn "The :vendored_at option is deprecated. Use :path instead.\nFrom #{caller[0]}"
92
+ end
93
+
94
+ options[:only] = _combine_only(options[:only] || options["only"])
95
+ options[:except] = _combine_except(options[:except] || options["except"])
96
+
97
+ dep = Dependency.new(name, options.merge(:version => version))
98
+
99
+ if options.key?(:bundle) && !options[:bundle]
100
+ dep.source = SystemGemSource.new(@bundle)
101
+ elsif @git || options[:git]
102
+ dep.source = _handle_git_option(name, version, options)
103
+ elsif @directory || options[:path]
104
+ dep.source = _handle_vendored_option(name, version, options)
105
+ end
106
+
107
+ @environment.dependencies << dep
108
+ end
109
+
110
+ private
111
+
112
+ def _version?(version)
113
+ version && Gem::Version.new(version) rescue false
114
+ end
115
+
116
+ def _handle_vendored_option(name, version, options)
117
+ dir, path = _find_directory_source(options[:path])
118
+
119
+ if dir
120
+ dir.required_specs << name
121
+ dir.add_spec(path, name, version) if _version?(version)
122
+ dir
123
+ else
124
+ directory options[:path] do
125
+ _handle_vendored_option(name, version, {})
126
+ end
127
+ end
128
+ end
129
+
130
+ def _find_directory_source(path)
131
+ if @directory
132
+ return @directory, Pathname.new(path || '')
133
+ end
134
+
135
+ path = @bundle.gemfile.dirname.join(path)
136
+
137
+ @directory_sources.each do |s|
138
+ if s.location.expand_path.to_s < path.expand_path.to_s
139
+ return s, path.relative_path_from(s.location)
140
+ end
141
+ end
142
+
143
+ nil
144
+ end
145
+
146
+ def _handle_git_option(name, version, options)
147
+ git = options[:git].to_s
148
+ ref = options[:ref] || options[:tag]
149
+ branch = options[:branch]
150
+
151
+ if source = @git || @git_sources[git]
152
+ if ref && source.ref != ref
153
+ raise GitSourceError, "'#{git}' already specified with ref: #{source.ref}"
154
+ elsif branch && source.branch != branch
155
+ raise GitSourceError, "'#{git}' already specified with branch: #{source.branch}"
156
+ end
157
+
158
+ source.required_specs << name
159
+ source.add_spec(Pathname.new(options[:path] || '.'), name, version) if _version?(version)
160
+ source
161
+ else
162
+ git(git, :ref => ref, :branch => branch) do
163
+ _handle_git_option(name, version, options)
164
+ end
165
+ end
166
+ end
167
+
168
+ def _combine_only(only)
169
+ return @only unless only
170
+ only = Array(only).compact.uniq.map { |o| o.to_s }
171
+ only &= @only if @only
172
+ only
173
+ end
174
+
175
+ def _combine_except(except)
176
+ return @except unless except
177
+ except = Array(except).compact.uniq.map { |o| o.to_s }
178
+ except |= @except if @except
179
+ except
180
+ end
181
+ end
182
+ end