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.

@@ -0,0 +1,46 @@
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
@@ -0,0 +1,108 @@
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
@@ -20,6 +20,7 @@ end
20
20
 
21
21
  module Bundler
22
22
  class GemNotFound < StandardError; end
23
+ class VersionConflict < StandardError; end
23
24
 
24
25
  class Resolver
25
26
 
@@ -35,21 +36,39 @@ module Bundler
35
36
  # ==== Returns
36
37
  # <GemBundle>,nil:: If the list of dependencies can be resolved, a
37
38
  # collection of gemspecs is returned. Otherwise, nil is returned.
38
- def self.resolve(requirements, index = Gem.source_index)
39
+ def self.resolve(requirements, sources)
39
40
  Bundler.logger.info "Calculating dependencies..."
40
41
 
41
- resolver = new(index)
42
+ resolver = new(sources)
42
43
  result = catch(:success) do
43
44
  resolver.resolve(requirements, {})
45
+ output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
46
+ o << " Conflict on: #{conflict.inspect}:\n"
47
+ o << " * #{conflict} (#{origin.version}) activated by #{origin.required_by.first}\n"
48
+ o << " * #{requirement} required by #{requirement.required_by.first}\n"
49
+ o << " All possible versions of origin requirements conflict."
50
+ end
51
+ raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
44
52
  nil
45
53
  end
46
54
  result && GemBundle.new(result.values)
47
55
  end
48
56
 
49
- def initialize(index)
57
+ def initialize(sources)
50
58
  @errors = {}
51
59
  @stack = []
52
- @index = index
60
+ @specs = Hash.new { |h,k| h[k] = {} }
61
+ @cache = {}
62
+ @index = {}
63
+
64
+ sources.reverse_each do |source|
65
+ source.gems.values.each do |spec|
66
+ # TMP HAX FOR OPTZ
67
+ spec.source = source
68
+ next unless Gem::Platform.match(spec.platform)
69
+ @specs[spec.name][spec.version] = spec
70
+ end
71
+ end
53
72
  end
54
73
 
55
74
  def resolve(reqs, activated)
@@ -61,7 +80,7 @@ module Bundler
61
80
  # Easiest to resolve is defined by: Is this gem already activated? Otherwise,
62
81
  # check the number of child dependencies this requirement has.
63
82
  reqs = reqs.sort_by do |req|
64
- activated[req.name] ? 0 : @index.search(req).size
83
+ activated[req.name] ? 0 : search(req).size
65
84
  end
66
85
 
67
86
  activated = activated.dup
@@ -77,7 +96,7 @@ module Bundler
77
96
  # the remaining requirements.
78
97
  resolve(reqs, activated)
79
98
  else
80
- @errors[existing.name] = { :gem => existing, :requirement => current }
99
+ @errors[existing.name] = [existing, current]
81
100
  # Since the current requirement conflicts with an activated gem, we need
82
101
  # to backtrack to the current requirement's parent and try another version
83
102
  # of it (maybe the current requirement won't be present anymore). If the
@@ -99,7 +118,7 @@ module Bundler
99
118
  # Fetch all gem versions matching the requirement
100
119
  #
101
120
  # TODO: Warn / error when no matching versions are found.
102
- matching_versions = @index.search(current)
121
+ matching_versions = search(current)
103
122
 
104
123
  if matching_versions.empty?
105
124
  if current.required_by.empty?
@@ -159,5 +178,12 @@ module Bundler
159
178
  retval
160
179
  end
161
180
 
181
+ def search(dependency)
182
+ @cache[dependency.hash] ||= begin
183
+ @specs[dependency.name].values.select do |spec|
184
+ dependency =~ spec
185
+ end.sort_by {|s| s.version }
186
+ end
187
+ end
162
188
  end
163
189
  end
@@ -2,14 +2,19 @@ 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
6
+ end
7
+
8
+ class GemSource < Source
5
9
  attr_reader :uri
6
10
 
7
- def initialize(uri)
8
- @uri = uri.is_a?(URI) ? uri : URI.parse(uri)
11
+ def initialize(options)
12
+ @uri = options[:uri]
13
+ @uri = URI.parse(@uri) unless @uri.is_a?(URI)
9
14
  raise ArgumentError, "The source must be an absolute URI" unless @uri.absolute?
10
15
  end
11
16
 
12
- def specs
17
+ def gems
13
18
  @specs ||= fetch_specs
14
19
  end
15
20
 
@@ -21,9 +26,18 @@ module Bundler
21
26
  @uri.to_s
22
27
  end
23
28
 
24
- def download(spec, destination)
29
+ class RubygemsRetardation < StandardError; end
30
+
31
+ def download(spec, repository)
25
32
  Bundler.logger.info "Downloading #{spec.full_name}.gem"
26
- Gem::RemoteFetcher.fetcher.download(spec, uri, destination)
33
+
34
+ destination = repository.download_path_for(:gem)
35
+
36
+ unless destination.writable?
37
+ raise RubygemsRetardation
38
+ end
39
+
40
+ Gem::RemoteFetcher.fetcher.download(spec, uri, repository.download_path_for(:gem))
27
41
  end
28
42
 
29
43
  private
@@ -35,17 +49,102 @@ module Bundler
35
49
  inflated = Gem.inflate deflated
36
50
 
37
51
  index = Marshal.load(inflated)
38
- specs = Hash.new { |h,k| h[k] = {} }
52
+ index.gems
53
+ rescue Gem::RemoteFetcher::FetchError => e
54
+ raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
55
+ end
56
+ end
57
+
58
+ class DirectorySource < Source
59
+ def initialize(options)
60
+ @name = options[:name]
61
+ @version = options[:version]
62
+ @location = options[:location]
63
+ @require_paths = options[:require_paths] || %w(lib)
64
+ end
65
+
66
+ def gems
67
+ @gems ||= begin
68
+ specs = {}
69
+
70
+ # 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
76
+ end
39
77
 
40
- index.gems.values.each do |spec|
41
- next unless Gem::Platform.match(spec.platform)
42
- spec.source = self
43
- specs[spec.name][spec.version] = spec
78
+ # If a gemspec for the dependency was not found, add it to the list
79
+ if specs.keys.grep(/^#{Regexp.escape(@name)}/).empty?
80
+ case
81
+ when @version.nil?
82
+ raise ArgumentError, "If you use :at, you must specify the gem" \
83
+ "and version you wish to stand in for"
84
+ when !Gem::Version.correct?(@version)
85
+ raise ArgumentError, "If you use :at, you must specify a gem and" \
86
+ "version. You specified #{@version} for the version"
87
+ end
88
+
89
+ default = Gem::Specification.new do |s|
90
+ s.name = @name
91
+ s.version = Gem::Version.new(@version) if @version
92
+ end
93
+ specs[default.full_name] = default
94
+ end
95
+
96
+ specs
44
97
  end
98
+ end
45
99
 
46
- specs
47
- rescue Gem::RemoteFetcher::FetchError => e
48
- raise ArgumentError, "#{to_s} is not a valid source: #{e.message}"
100
+ def ==(other)
101
+ # TMP HAX
102
+ other.is_a?(DirectorySource)
103
+ end
104
+
105
+ def to_s
106
+ "#{@name} (#{@version}) Located at: '#{@location}'"
107
+ end
108
+
109
+ def download(spec, repository)
110
+ spec.require_paths.map! { |p| File.join(@location, p) }
111
+ repository.add_spec(:directory, spec)
112
+ end
113
+ end
114
+
115
+ class GitSource < DirectorySource
116
+ def initialize(options)
117
+ super
118
+ @uri = options[:uri]
119
+ @ref = options[:ref]
120
+ @branch = options[:branch]
121
+ end
122
+
123
+ def gems
124
+ FileUtils.mkdir_p(tmp_path.join("gitz"))
125
+
126
+ # TMP HAX to get the *.gemspec reading to work
127
+ @location = tmp_path.join("gitz", @name)
128
+
129
+ Bundler.logger.info "Cloning git repository at: #{@uri}"
130
+ `git clone #{@uri} #{@location} --no-hardlinks`
131
+
132
+ if @ref
133
+ Dir.chdir(@location) { `git checkout #{@ref}` }
134
+ elsif @branch && @branch != "master"
135
+ Dir.chdir(@location) { `git checkout origin/#{@branch}` }
136
+ end
137
+ super
138
+ end
139
+
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
49
148
  end
50
149
  end
51
150
  end
@@ -1,7 +1,8 @@
1
+ # DO NOT MODIFY THIS FILE
1
2
  module Bundler
2
3
  dir = File.dirname(__FILE__)
3
4
 
4
- <% unless @system_gems -%>
5
+ <% unless options[:system_gems] -%>
5
6
  ENV["GEM_HOME"] = dir
6
7
  ENV["GEM_PATH"] = dir
7
8
  <% end -%>
@@ -9,12 +10,12 @@ module Bundler
9
10
  ENV["RUBYOPT"] = "-r#{__FILE__} #{ENV["RUBYOPT"]}"
10
11
 
11
12
  <% load_paths.each do |load_path| -%>
12
- $LOAD_PATH.unshift "#{dir}/<%= load_path %>"
13
+ $LOAD_PATH.unshift File.expand_path("#{dir}/<%= load_path %>")
13
14
  <% end -%>
14
15
 
15
16
  @gemfile = "#{dir}/<%= filename %>"
16
17
 
17
- <% if @rubygems -%>
18
+ <% if options[:rubygems] -%>
18
19
  require "rubygems"
19
20
 
20
21
  @bundled_specs = {}
@@ -37,10 +38,52 @@ module Bundler
37
38
  add_specs_to_index
38
39
  <% end -%>
39
40
 
40
- require File.join(dir, "bundler", "runtime")
41
+ def self.require_env(env = nil)
42
+ context = Class.new do
43
+ def initialize(env) @env = env && env.to_s ; end
44
+ def method_missing(*) ; end
45
+ def only(env)
46
+ old, @only = @only, _combine_onlys(env)
47
+ yield
48
+ @only = old
49
+ end
50
+ def except(env)
51
+ old, @except = @except, _combine_excepts(env)
52
+ yield
53
+ @except = old
54
+ end
55
+ def gem(name, *args)
56
+ opt = args.last || {}
57
+ only = _combine_onlys(opt[:only] || opt["only"])
58
+ except = _combine_excepts(opt[:except] || opt["except"])
59
+ files = opt[:require_as] || opt["require_as"] || name
60
+
61
+ return unless !only || only.any? {|e| e == @env }
62
+ return if except && except.any? {|e| e == @env }
63
+
64
+ files.each { |f| require f }
65
+ yield if block_given?
66
+ true
67
+ end
68
+ private
69
+ def _combine_onlys(only)
70
+ return @only unless only
71
+ only = [only].flatten.compact.uniq.map { |o| o.to_s }
72
+ only &= @only if @only
73
+ only
74
+ end
75
+ def _combine_excepts(except)
76
+ return @except unless except
77
+ except = [except].flatten.compact.uniq.map { |o| o.to_s }
78
+ except |= @except if @except
79
+ except
80
+ end
81
+ end
82
+ context.new(env && env.to_s).instance_eval(File.read(@gemfile))
83
+ end
41
84
  end
42
85
 
43
- <% if @rubygems -%>
86
+ <% if options[:rubygems] -%>
44
87
  module Gem
45
88
  def source_index.refresh!
46
89
  super