bundler 0.4.1 → 0.5.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.

@@ -149,6 +149,11 @@ If the `disable_rubygems` option is used, Bundler will stub out the most common
149
149
  of these features, but it is possible that things will not go as intended quite
150
150
  yet. So, if you are brave, try your code without rubygems at runtime.
151
151
 
152
+ ## Known Issues
153
+
154
+ * When a gem points to a git repository, the git repository will be cloned
155
+ every time Bundler does a gem dependency resolve.
156
+
152
157
  ## Reporting bugs
153
158
 
154
159
  Please report all bugs on the github issue tracker for the project located
data/Rakefile CHANGED
@@ -1,12 +1,10 @@
1
1
  require 'rubygems' unless ENV['NO_RUBYGEMS']
2
- require 'rake/gempackagetask'
3
2
  require 'rubygems/specification'
4
3
  require 'date'
5
- require 'spec/rake/spectask'
6
4
 
7
5
  spec = Gem::Specification.new do |s|
8
6
  s.name = "bundler"
9
- s.version = "0.4.1"
7
+ s.version = "0.5.0"
10
8
  s.author = "Yehuda Katz"
11
9
  s.email = "wycats@gmail.com"
12
10
  s.homepage = "http://github.com/wycats/bundler"
@@ -24,15 +22,26 @@ end
24
22
 
25
23
  task :default => :spec
26
24
 
27
- desc "Run specs"
28
- Spec::Rake::SpecTask.new do |t|
29
- t.spec_files = FileList['spec/**/*_spec.rb'] - FileList['spec/fixtures/**/*_spec.rb']
30
- t.spec_opts = %w(-fs --color)
25
+ begin
26
+ require 'spec/rake/spectask'
27
+ rescue LoadError
28
+ task(:spec) { $stderr.puts '`gem install rspec` to run specs' }
29
+ else
30
+ desc "Run specs"
31
+ Spec::Rake::SpecTask.new do |t|
32
+ t.spec_files = FileList['spec/**/*_spec.rb'] - FileList['spec/fixtures/**/*_spec.rb']
33
+ t.spec_opts = %w(-fs --color)
34
+ end
31
35
  end
32
36
 
33
-
34
- Rake::GemPackageTask.new(spec) do |pkg|
35
- pkg.gem_spec = spec
37
+ begin
38
+ require 'rake/gempackagetask'
39
+ rescue LoadError
40
+ task(:gem) { $stderr.puts '`gem install rake` to package gems' }
41
+ else
42
+ Rake::GemPackageTask.new(spec) do |pkg|
43
+ pkg.gem_spec = spec
44
+ end
36
45
  end
37
46
 
38
47
  desc "install the gem locally"
@@ -45,4 +54,4 @@ task :make_spec do
45
54
  File.open("#{spec.name}.gemspec", "w") do |file|
46
55
  file.puts spec.to_ruby
47
56
  end
48
- end
57
+ end
@@ -11,12 +11,11 @@ require "bundler/source"
11
11
  require "bundler/finder"
12
12
  require "bundler/gem_ext"
13
13
  require "bundler/resolver"
14
- require "bundler/manifest_file"
15
- require "bundler/manifest"
14
+ require "bundler/environment"
16
15
  require "bundler/dsl"
17
16
  require "bundler/cli"
18
17
  require "bundler/repository"
19
- require "bundler/runtime/dependency"
18
+ require "bundler/dependency"
20
19
 
21
20
  module Bundler
22
21
  VERSION = "0.5.0"
@@ -23,15 +23,15 @@ module Bundler
23
23
 
24
24
  def initialize(options)
25
25
  @options = options
26
- @manifest_file = Bundler::ManifestFile.load(@options[:manifest])
26
+ @manifest = Bundler::Environment.load(@options[:manifest])
27
27
  end
28
28
 
29
29
  def bundle
30
- @manifest_file.install(@options[:update])
30
+ @manifest.install(@options[:update])
31
31
  end
32
32
 
33
33
  def exec
34
- @manifest_file.setup_environment
34
+ @manifest.setup_environment
35
35
  # w0t?
36
36
  super(*@options[:args])
37
37
  end
@@ -12,8 +12,8 @@ module Bundler
12
12
  @name = name
13
13
  @version = options["version"] || ">= 0"
14
14
  @require_as = Array(options["require_as"] || name)
15
- @only = Array(options["only"]).map {|e| e.to_s } if options["only"]
16
- @except = Array(options["except"]).map {|e| e.to_s } if options["except"]
15
+ @only = options["only"]
16
+ @except = options["except"]
17
17
  @block = block
18
18
 
19
19
  if (@only && @only.include?("rubygems")) || (@except && @except.include?("rubygems"))
@@ -33,11 +33,11 @@ module Bundler
33
33
  to_gem_dependency.to_s
34
34
  end
35
35
 
36
- def require(environment)
36
+ def require_env(environment)
37
37
  return unless in?(environment)
38
38
 
39
39
  @require_as.each do |file|
40
- super(file)
40
+ require file
41
41
  end
42
42
 
43
43
  @block.call if @block
@@ -1,61 +1,109 @@
1
1
  module Bundler
2
2
  class ManifestFileNotFound < StandardError; end
3
3
 
4
- class ManifestBuilder
5
- def self.build(manifest_file, string)
6
- builder = new(manifest_file)
7
- builder.instance_eval(string)
8
- builder
9
- end
10
-
11
- def self.load(manifest_file, filename)
12
- unless File.exist?(filename)
13
- raise ManifestFileNotFound, "#{filename.inspect} does not exist"
14
- end
15
- string = File.read(filename)
16
- build(manifest_file, string)
17
- end
18
-
19
- def initialize(manifest_file)
20
- @manifest_file = manifest_file
4
+ class Dsl
5
+ def initialize(environment)
6
+ @environment = environment
7
+ @sources = Hash.new { |h,k| h[k] = {} }
21
8
  end
22
9
 
23
10
  def bundle_path(path)
24
11
  path = Pathname.new(path)
25
- @manifest_file.gem_path = (path.relative? ?
26
- @manifest_file.root.join(path) : path).expand_path
12
+ @environment.gem_path = (path.relative? ?
13
+ @environment.root.join(path) : path).expand_path
27
14
  end
28
15
 
29
16
  def bin_path(path)
30
17
  path = Pathname.new(path)
31
- @manifest_file.bindir = (path.relative? ?
32
- @manifest_file.root.join(path) : path).expand_path
18
+ @environment.bindir = (path.relative? ?
19
+ @environment.root.join(path) : path).expand_path
33
20
  end
34
21
 
35
22
  def disable_rubygems
36
- @manifest_file.rubygems = false
23
+ @environment.rubygems = false
37
24
  end
38
25
 
39
26
  def disable_system_gems
40
- @manifest_file.system_gems = false
27
+ @environment.system_gems = false
41
28
  end
42
29
 
43
30
  def source(source)
44
- source = Source.new(source)
45
- unless @manifest_file.sources.include?(source)
46
- @manifest_file.add_source(source)
31
+ source = GemSource.new(:uri => source)
32
+ unless @environment.sources.include?(source)
33
+ @environment.add_source(source)
47
34
  end
48
35
  end
49
36
 
37
+ def only(env)
38
+ old, @only = @only, _combine_onlys(env)
39
+ yield
40
+ @only = old
41
+ end
42
+
43
+ def except(env)
44
+ old, @except = @except, _combine_excepts(env)
45
+ yield
46
+ @except = old
47
+ end
48
+
50
49
  def clear_sources
51
- @manifest_file.clear_sources
50
+ @environment.clear_sources
52
51
  end
53
52
 
54
53
  def gem(name, *args)
55
54
  options = args.last.is_a?(Hash) ? args.pop : {}
56
55
  version = args.last
57
56
 
58
- @manifest_file.dependencies << Dependency.new(name, options.merge(:version => version))
57
+ options[:only] = _combine_onlys(options[:only] || options["only"])
58
+ options[:except] = _combine_excepts(options[:except] || options["except"])
59
+
60
+ dep = Dependency.new(name, options.merge(:version => version))
61
+
62
+ # OMG REFACTORZ. KTHX
63
+ if vendored_at = options[:vendored_at]
64
+ vendored_at = Pathname.new(vendored_at)
65
+ vendored_at = @environment.filename.dirname.join(vendored_at) if vendored_at.relative?
66
+
67
+ @sources[:directory][vendored_at.to_s] ||= begin
68
+ source = DirectorySource.new(
69
+ :name => name,
70
+ :version => version,
71
+ :location => vendored_at
72
+ )
73
+ @environment.add_priority_source(source)
74
+ true
75
+ end
76
+ elsif git = options[:git]
77
+ @sources[:git][git] ||= begin
78
+ source = GitSource.new(
79
+ :name => name,
80
+ :version => version,
81
+ :uri => git,
82
+ :ref => options[:commit] || options[:tag],
83
+ :branch => options[:branch]
84
+ )
85
+ @environment.add_priority_source(source)
86
+ true
87
+ end
88
+ end
89
+
90
+ @environment.dependencies << dep
91
+ end
92
+
93
+ private
94
+
95
+ def _combine_onlys(only)
96
+ return @only unless only
97
+ only = [only].flatten.compact.uniq.map { |o| o.to_s }
98
+ only &= @only if @only
99
+ only
100
+ end
101
+
102
+ def _combine_excepts(except)
103
+ return @except unless except
104
+ except = [except].flatten.compact.uniq.map { |o| o.to_s }
105
+ except |= @except if @except
106
+ except
59
107
  end
60
108
  end
61
109
  end
@@ -0,0 +1,115 @@
1
+ require "rubygems/source_index"
2
+
3
+ module Bundler
4
+ class DefaultManifestNotFound < StandardError; end
5
+
6
+ class Environment
7
+ attr_reader :filename, :dependencies
8
+ attr_accessor :rubygems, :system_gems, :gem_path, :bindir
9
+
10
+ def self.load(gemfile = nil)
11
+ gemfile = gemfile ? Pathname.new(gemfile) : default_manifest_file
12
+
13
+ unless gemfile.file?
14
+ raise ManifestFileNotFound, "#{filename.inspect} does not exist"
15
+ end
16
+
17
+ new(gemfile)
18
+ end
19
+
20
+ def self.default_manifest_file
21
+ current = Pathname.new(Dir.pwd)
22
+
23
+ until current.root?
24
+ filename = current.join("Gemfile")
25
+ return filename if filename.exist?
26
+ current = current.parent
27
+ end
28
+
29
+ raise DefaultManifestNotFound
30
+ end
31
+
32
+ def initialize(filename) #, sources, dependencies, bindir, path, rubygems, system_gems)
33
+ @filename = filename
34
+ @default_sources = [GemSource.new(:uri => "http://gems.rubyforge.org")]
35
+ @sources = []
36
+ @priority_sources = []
37
+ @dependencies = []
38
+ @rubygems = true
39
+ @system_gems = true
40
+
41
+ # Evaluate the Gemfile
42
+ builder = Dsl.new(self)
43
+ builder.instance_eval(File.read(filename))
44
+ end
45
+
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
60
+ Bundler.logger.info "Done."
61
+ end
62
+
63
+ def setup_environment
64
+ unless system_gems
65
+ ENV["GEM_HOME"] = gem_path
66
+ ENV["GEM_PATH"] = gem_path
67
+ end
68
+ ENV["PATH"] = "#{bindir}:#{ENV["PATH"]}"
69
+ ENV["RUBYOPT"] = "-r#{gem_path}/environment #{ENV["RUBYOPT"]}"
70
+ end
71
+
72
+ def require_env(env = nil)
73
+ dependencies.each { |d| d.require_env(env) }
74
+ end
75
+
76
+ def root
77
+ filename.parent
78
+ end
79
+
80
+ def gem_path
81
+ @gem_path ||= root.join("vendor", "gems")
82
+ end
83
+
84
+ def bindir
85
+ @bindir ||= root.join("bin")
86
+ end
87
+
88
+ def sources
89
+ @priority_sources + @sources + @default_sources
90
+ end
91
+
92
+ def add_source(source)
93
+ @sources << source
94
+ end
95
+
96
+ def add_priority_source(source)
97
+ @priority_sources << source
98
+ end
99
+
100
+ def clear_sources
101
+ @sources.clear
102
+ @default_sources.clear
103
+ end
104
+
105
+ private
106
+
107
+ def repository
108
+ @repository ||= Repository.new(gem_path, bindir)
109
+ end
110
+
111
+ def gem_dependencies
112
+ @gem_dependencies ||= dependencies.map { |d| d.to_gem_dependency }
113
+ end
114
+ end
115
+ end
@@ -2,7 +2,7 @@ module Bundler
2
2
  class GemBundle < Array
3
3
  def download(repository)
4
4
  sort_by {|s| s.full_name.downcase }.each do |spec|
5
- repository.download(spec)
5
+ spec.source.download(spec, repository)
6
6
  end
7
7
 
8
8
  self
@@ -9,16 +9,17 @@ module Gem
9
9
  end
10
10
 
11
11
  class Specification
12
- attribute :source
13
-
14
- def source=(source)
15
- source = Bundler::Source.new(source) unless source.is_a?(Bundler::Source)
16
- @source = source
17
- end
12
+ attr_accessor :source
13
+ attr_accessor :location
18
14
 
19
15
  # Hack to fix github's strange marshal file
20
16
  def specification_version
21
17
  @specification_version && @specification_version.to_i
22
18
  end
19
+
20
+ alias full_gem_path_without_location full_gem_path
21
+ def full_gem_path
22
+ @location ? @location : full_gem_path_without_location
23
+ end
23
24
  end
24
25
  end
@@ -1,116 +1,151 @@
1
+ require "bundler/repository/gem_repository"
2
+ require "bundler/repository/directory_repository"
3
+
1
4
  module Bundler
2
5
  class InvalidRepository < StandardError ; end
3
6
 
4
7
  class Repository
5
-
6
8
  attr_reader :path
7
9
 
8
- def initialize(path, bindir = nil)
10
+ def initialize(path, bindir)
11
+ FileUtils.mkdir_p(path)
12
+
9
13
  @path = Pathname.new(path)
10
- @bindir = Pathname.new(bindir) || @path.join("bin")
11
- unless valid?
12
- raise InvalidRepository, "'#{path}' is not a valid gem repository"
14
+ @bindir = Pathname.new(bindir)
15
+
16
+ @repos = {
17
+ :gem => Gems.new(@path, @bindir),
18
+ :directory => Directory.new(@path.join("dirs"), @bindir)
19
+ }
20
+ end
21
+
22
+ 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]))
13
31
  end
32
+ configure(options)
33
+ sync
14
34
  end
15
35
 
16
- # Returns the source index for all gems installed in the
17
- # repository
18
- def source_index
19
- Gem::SourceIndex.from_gems_in(@path.join("specifications"))
36
+ def gems
37
+ gems = []
38
+ each_repo do |repo|
39
+ gems.concat repo.gems
40
+ end
41
+ gems
20
42
  end
21
43
 
22
- def valid?
23
- (Dir[@path.join("*")] - Dir[@path.join("{cache,doc,gems,bundler,environment.rb,specifications}")]).empty?
44
+ def satisfies?(dependencies)
45
+ index = source_index
46
+ dependencies.all? { |dep| index.search(dep).size > 0 }
24
47
  end
25
48
 
26
- def download(spec)
27
- FileUtils.mkdir_p(@path)
49
+ def source_index
50
+ index = Gem::SourceIndex.new
28
51
 
29
- unless @path.join("cache", "#{spec.full_name}.gem").file?
30
- spec.source.download(spec, @path)
52
+ each_repo do |repo|
53
+ index.gems.merge!(repo.source_index.gems)
31
54
  end
55
+
56
+ index
32
57
  end
33
58
 
34
- # Checks whether a gem is installed
35
- def install_cached_gems(options = {})
36
- cached_gems.each do |name, version|
37
- unless installed?(name, version)
38
- install_cached_gem(name, version, options)
39
- end
40
- end
59
+ def add_spec(type, spec)
60
+ @repos[type].add_spec(spec)
41
61
  end
42
62
 
43
- def install_cached_gem(name, version, options = {})
44
- cached_gem = cache_path.join("#{name}-#{version}.gem")
45
- # TODO: Add a warning if cached_gem is not a file
46
- if cached_gem.file?
47
- Bundler.logger.info "Installing #{name}-#{version}.gem"
48
- installer = Gem::Installer.new(cached_gem.to_s, options.merge(
49
- :install_dir => @path,
50
- :ignore_dependencies => true,
51
- :env_shebang => true,
52
- :wrappers => true
53
- ))
54
- installer.install
55
- end
63
+ def download_path_for(type)
64
+ @repos[type].download_path_for
56
65
  end
57
66
 
58
- def cleanup(gems)
59
- glob = gems.map { |g| g.full_name }.join(',')
60
- base = path.join("{cache,specifications,gems}")
67
+ private
61
68
 
62
- (Dir[base.join("*")] - Dir[base.join("{#{glob}}{.gemspec,.gem,}")]).each do |file|
63
- if File.basename(file) =~ /\.gem$/
64
- name = File.basename(file, '.gem')
65
- Bundler.logger.info "Deleting gem: #{name}"
66
- end
67
- FileUtils.rm_rf(file)
69
+ def cleanup(bundle)
70
+ each_repo do |repo|
71
+ repo.cleanup(bundle)
68
72
  end
73
+ end
69
74
 
70
- glob = gems.map { |g| g.executables }.flatten.join(',')
71
- (Dir[@bindir.join("*")] - Dir[@bindir.join("{#{glob}}")]).each do |file|
72
- Bundler.logger.info "Deleting bin file: #{File.basename(file)}"
73
- FileUtils.rm_rf(file)
75
+ def each_repo
76
+ @repos.each do |k, repo|
77
+ yield repo
74
78
  end
75
79
  end
76
80
 
77
- private
78
-
79
- def cache_path
80
- @path.join("cache")
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)
81
89
  end
82
90
 
83
- def cache_files
84
- Dir[cache_path.join("*.gem")]
85
- end
91
+ def sync
92
+ glob = gems.map { |g| g.executables }.flatten.join(',')
86
93
 
87
- def cached_gems
88
- cache_files.map do |f|
89
- full_name = File.basename(f).gsub(/\.gem$/, '')
90
- full_name.split(/-(?=[^-]+$)/)
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)
91
97
  end
92
98
  end
93
99
 
94
- def spec_path
95
- @path.join("specifications")
100
+ def expand(options)
101
+ each_repo do |repo|
102
+ repo.expand(options)
103
+ end
96
104
  end
97
105
 
98
- def spec_files
99
- Dir[spec_path.join("*.gemspec")]
106
+ def configure(options)
107
+ generate_environment(options)
100
108
  end
101
109
 
102
- def gem_path
103
- @path.join("gems")
104
- end
110
+ def generate_environment(options)
111
+ FileUtils.mkdir_p(path)
105
112
 
106
- def gems
107
- Dir[gem_path.join("*")]
113
+ specs = gems
114
+ load_paths = load_paths_for_specs(specs)
115
+ bindir = @bindir.relative_path_from(path).to_s
116
+ filename = options[:manifest].relative_path_from(path).to_s
117
+ spec_files = specs.inject({}) do |hash, spec|
118
+ relative = spec.loaded_from.relative_path_from(@path).to_s
119
+ hash.merge!(spec.name => relative)
120
+ end
121
+
122
+ File.open(path.join("environment.rb"), "w") do |file|
123
+ template = File.read(File.join(File.dirname(__FILE__), "templates", "environment.erb"))
124
+ erb = ERB.new(template, nil, '-')
125
+ file.puts erb.result(binding)
126
+ end
108
127
  end
109
128
 
110
- def installed?(name, version)
111
- spec_files.any? { |g| File.basename(g) == "#{name}-#{version}.gemspec" } &&
112
- gems.any? { |g| File.basename(g) == "#{name}-#{version}" }
129
+ def load_paths_for_specs(specs)
130
+ load_paths = []
131
+ specs.each do |spec|
132
+ gem_path = Pathname.new(spec.full_gem_path)
133
+ if spec.bindir
134
+ load_paths << gem_path.join(spec.bindir).relative_path_from(@path).to_s
135
+ end
136
+ spec.require_paths.each do |path|
137
+ load_paths << gem_path.join(path).relative_path_from(@path).to_s
138
+ end
139
+ end
140
+ load_paths
113
141
  end
114
142
 
143
+ def require_code(file, dep)
144
+ constraint = case
145
+ when dep.only then %{ if #{dep.only.inspect}.include?(env)}
146
+ when dep.except then %{ unless #{dep.except.inspect}.include?(env)}
147
+ end
148
+ "require #{file.inspect}#{constraint}"
149
+ end
115
150
  end
116
151
  end