bundler 0.6.0 → 0.7.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.
- data/README.markdown +22 -2
- data/lib/bundler.rb +1 -1
- data/lib/bundler/cli.rb +14 -7
- data/lib/bundler/commands/bundle_command.rb +17 -0
- data/lib/bundler/commands/exec_command.rb +5 -0
- data/lib/bundler/dependency.rb +11 -4
- data/lib/bundler/dsl.rb +97 -38
- data/lib/bundler/environment.rb +35 -12
- data/lib/bundler/repository.rb +78 -37
- data/lib/bundler/resolver.rb +39 -17
- data/lib/bundler/source.rb +95 -65
- data/lib/bundler/templates/environment.erb +31 -15
- metadata +2 -2
data/README.markdown
CHANGED
@@ -175,11 +175,31 @@ will be compiled for the target platform without requiring that the
|
|
175
175
|
Assuming a Rails app with Bundler's standard setup, add something like
|
176
176
|
this to your top-level `.gitignore` to only keep the cache:
|
177
177
|
|
178
|
-
vendor/gems
|
178
|
+
vendor/gems/*
|
179
179
|
!vendor/gems/cache/
|
180
180
|
|
181
181
|
Make sure that you explicitly `git add vendor/gems/cache` before you commit.
|
182
182
|
|
183
|
+
### Gems with compile-time options
|
184
|
+
|
185
|
+
Some gems require you to pass compile-time options to the gem install command.
|
186
|
+
For instance, to install mysql, you might do:
|
187
|
+
|
188
|
+
gem install mysql -- --with-mysql-config=/usr/local/lib/mysql
|
189
|
+
|
190
|
+
You can pass these options to the bundler by creating a YAML file containing
|
191
|
+
the options in question:
|
192
|
+
|
193
|
+
mysql:
|
194
|
+
mysql-config: /usr/local/lib/mysql
|
195
|
+
|
196
|
+
You can then point the bundler at the file:
|
197
|
+
|
198
|
+
gem bundle --build-options build_options.yml
|
199
|
+
|
200
|
+
In general, you will want to keep the build options YAML out of version control,
|
201
|
+
and provide the appropriate options for the system in question.
|
202
|
+
|
183
203
|
### Running your application
|
184
204
|
|
185
205
|
The easiest way to run your application is to start it with an executable
|
@@ -196,7 +216,7 @@ You can use `gem exec bash` to enter a shell that will run all binaries in
|
|
196
216
|
the current context.
|
197
217
|
|
198
218
|
Yet another way is to manually require the environment file first. This is
|
199
|
-
located in `[bundle_path]/
|
219
|
+
located in `[bundle_path]/gems/environment.rb`. For example:
|
200
220
|
|
201
221
|
ruby -r vendor/gems/environment.rb my_ruby_script.rb
|
202
222
|
|
data/lib/bundler.rb
CHANGED
data/lib/bundler/cli.rb
CHANGED
@@ -25,33 +25,40 @@ module Bundler
|
|
25
25
|
rescue SourceNotCached => e
|
26
26
|
Bundler.logger.error e.message
|
27
27
|
exit 9
|
28
|
+
rescue ManifestFileNotFound => e
|
29
|
+
Bundler.logger.error e.message
|
30
|
+
exit 10
|
28
31
|
end
|
29
32
|
|
30
33
|
def initialize(options)
|
31
34
|
@options = options
|
32
|
-
@
|
35
|
+
@environment = Bundler::Environment.load(@options[:manifest])
|
33
36
|
end
|
34
37
|
|
35
38
|
def bundle
|
36
|
-
@
|
39
|
+
@environment.install(@options)
|
37
40
|
end
|
38
41
|
|
39
42
|
def cache
|
40
|
-
@
|
43
|
+
@environment.cache(@options)
|
41
44
|
end
|
42
45
|
|
43
46
|
def prune
|
44
|
-
@
|
47
|
+
@environment.prune(@options)
|
45
48
|
end
|
46
49
|
|
47
50
|
def list
|
48
|
-
@
|
51
|
+
@environment.list(@options)
|
52
|
+
end
|
53
|
+
|
54
|
+
def list_outdated
|
55
|
+
@environment.list_outdated(@options)
|
49
56
|
end
|
50
57
|
|
51
58
|
def exec
|
52
|
-
@
|
59
|
+
@environment.setup_environment
|
53
60
|
# w0t?
|
54
|
-
super(
|
61
|
+
super(*$command)
|
55
62
|
end
|
56
63
|
|
57
64
|
def run(command)
|
@@ -26,6 +26,21 @@ class Gem::Commands::BundleCommand < Gem::Command
|
|
26
26
|
add_option('--list', "List all gems that are part of the active bundle") do
|
27
27
|
options[:list] = true
|
28
28
|
end
|
29
|
+
|
30
|
+
add_option('--list-outdated', "List all outdated gems that are part of the active bundle") do
|
31
|
+
options[:list_outdated] = true
|
32
|
+
end
|
33
|
+
|
34
|
+
add_option('-b', '--build-options OPTION_FILE', "Specify a path to a yml file with build options for binary gems") do |option_file, options|
|
35
|
+
if File.exist?(option_file)
|
36
|
+
options[:build_options] = YAML.load_file(option_file)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
add_option('--only ENV', "Only expand the given environment. To specify multiple environments, use --only multiple times.") do |env, options|
|
41
|
+
options[:only] ||= []
|
42
|
+
options[:only] << env
|
43
|
+
end
|
29
44
|
end
|
30
45
|
|
31
46
|
def usage
|
@@ -47,6 +62,8 @@ Bundle stuff
|
|
47
62
|
Bundler::CLI.run(:prune, options)
|
48
63
|
elsif options[:list]
|
49
64
|
Bundler::CLI.run(:list, options)
|
65
|
+
elsif options[:list_outdated]
|
66
|
+
Bundler::CLI.run(:list_outdated, options)
|
50
67
|
else
|
51
68
|
Bundler::CLI.run(:bundle, options)
|
52
69
|
end
|
data/lib/bundler/dependency.rb
CHANGED
@@ -2,7 +2,7 @@ module Bundler
|
|
2
2
|
class InvalidEnvironmentName < StandardError; end
|
3
3
|
|
4
4
|
class Dependency
|
5
|
-
attr_reader :name, :version, :require_as, :only, :except
|
5
|
+
attr_reader :name, :version, :require_as, :only, :except, :bundle
|
6
6
|
|
7
7
|
def initialize(name, options = {}, &block)
|
8
8
|
options.each do |k, v|
|
@@ -11,9 +11,10 @@ module Bundler
|
|
11
11
|
|
12
12
|
@name = name
|
13
13
|
@version = options["version"] || ">= 0"
|
14
|
-
@require_as =
|
14
|
+
@require_as = options["require_as"]
|
15
15
|
@only = options["only"]
|
16
16
|
@except = options["except"]
|
17
|
+
@bundle = options.key?("bundle") ? options["bundle"] : true
|
17
18
|
@block = block
|
18
19
|
|
19
20
|
if (@only && @only.include?("rubygems")) || (@except && @except.include?("rubygems"))
|
@@ -36,8 +37,14 @@ module Bundler
|
|
36
37
|
def require_env(environment)
|
37
38
|
return unless in?(environment)
|
38
39
|
|
39
|
-
@require_as
|
40
|
-
require file
|
40
|
+
if @require_as
|
41
|
+
Array(@require_as).each { |file| require file }
|
42
|
+
else
|
43
|
+
begin
|
44
|
+
require name
|
45
|
+
rescue LoadError
|
46
|
+
# Do nothing
|
47
|
+
end
|
41
48
|
end
|
42
49
|
|
43
50
|
@block.call if @block
|
data/lib/bundler/dsl.rb
CHANGED
@@ -2,10 +2,16 @@ module Bundler
|
|
2
2
|
class ManifestFileNotFound < StandardError; end
|
3
3
|
|
4
4
|
class Dsl
|
5
|
+
def self.evaluate(environment, file)
|
6
|
+
builder = new(environment)
|
7
|
+
builder.instance_eval(File.read(file.to_s), file.to_s, 1)
|
8
|
+
end
|
9
|
+
|
5
10
|
def initialize(environment)
|
6
11
|
@environment = environment
|
7
|
-
@
|
8
|
-
@
|
12
|
+
@directory_sources = []
|
13
|
+
@git_sources = {}
|
14
|
+
@only, @except, @directory, @git = nil, nil, nil, nil
|
9
15
|
end
|
10
16
|
|
11
17
|
def bundle_path(path)
|
@@ -35,18 +41,36 @@ module Bundler
|
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
38
|
-
def only(env)
|
39
|
-
old, @only = @only,
|
44
|
+
def only(*env)
|
45
|
+
old, @only = @only, _combine_only(env)
|
40
46
|
yield
|
41
47
|
@only = old
|
42
48
|
end
|
43
49
|
|
44
|
-
def except(env)
|
45
|
-
old, @except = @except,
|
50
|
+
def except(*env)
|
51
|
+
old, @except = @except, _combine_except(env)
|
46
52
|
yield
|
47
53
|
@except = old
|
48
54
|
end
|
49
55
|
|
56
|
+
def directory(path, options = {})
|
57
|
+
raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
|
58
|
+
@directory = DirectorySource.new(options.merge(:location => path))
|
59
|
+
@directory_sources << @directory
|
60
|
+
@environment.add_priority_source(@directory)
|
61
|
+
yield if block_given?
|
62
|
+
@directory = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def git(uri, options = {})
|
66
|
+
raise DirectorySourceError, "cannot nest calls to directory or git" if @directory || @git
|
67
|
+
@git = GitSource.new(options.merge(:uri => uri))
|
68
|
+
@git_sources[uri] = @git
|
69
|
+
@environment.add_priority_source(@git)
|
70
|
+
yield if block_given?
|
71
|
+
@git = nil
|
72
|
+
end
|
73
|
+
|
50
74
|
def clear_sources
|
51
75
|
@environment.clear_sources
|
52
76
|
end
|
@@ -55,37 +79,22 @@ module Bundler
|
|
55
79
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
56
80
|
version = args.last
|
57
81
|
|
58
|
-
|
59
|
-
|
82
|
+
if path = options.delete(:vendored_at)
|
83
|
+
options[:path] = path
|
84
|
+
warn "The :vendored_at option is deprecated. Use :path instead.\nFrom #{caller[0]}"
|
85
|
+
end
|
86
|
+
|
87
|
+
options[:only] = _combine_only(options[:only] || options["only"])
|
88
|
+
options[:except] = _combine_except(options[:except] || options["except"])
|
60
89
|
|
61
90
|
dep = Dependency.new(name, options.merge(:version => version))
|
62
91
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
source = DirectorySource.new(
|
70
|
-
:name => name,
|
71
|
-
:version => version,
|
72
|
-
:location => vendored_at
|
73
|
-
)
|
74
|
-
@environment.add_priority_source(source)
|
75
|
-
true
|
76
|
-
end
|
77
|
-
elsif git = options[:git]
|
78
|
-
@sources[:git][git] ||= begin
|
79
|
-
source = GitSource.new(
|
80
|
-
:name => name,
|
81
|
-
:version => version,
|
82
|
-
:uri => git,
|
83
|
-
:ref => options[:commit] || options[:tag],
|
84
|
-
:branch => options[:branch]
|
85
|
-
)
|
86
|
-
@environment.add_priority_source(source)
|
87
|
-
true
|
88
|
-
end
|
92
|
+
if options.key?(:bundle) && !options[:bundle]
|
93
|
+
# We're using system gems for this one
|
94
|
+
elsif @git || options[:git]
|
95
|
+
_handle_git_option(name, version, options)
|
96
|
+
elsif @directory || options[:path]
|
97
|
+
_handle_vendored_option(name, version, options)
|
89
98
|
end
|
90
99
|
|
91
100
|
@environment.dependencies << dep
|
@@ -93,16 +102,66 @@ module Bundler
|
|
93
102
|
|
94
103
|
private
|
95
104
|
|
96
|
-
def
|
105
|
+
def _handle_vendored_option(name, version, options)
|
106
|
+
dir, path = _find_directory_source(options[:path])
|
107
|
+
|
108
|
+
if dir
|
109
|
+
dir.required_specs << name
|
110
|
+
dir.add_spec(path, name, version) if version
|
111
|
+
else
|
112
|
+
directory options[:path] do
|
113
|
+
_handle_vendored_option(name, version, {})
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def _find_directory_source(path)
|
119
|
+
if @directory
|
120
|
+
return @directory, Pathname.new(path || '')
|
121
|
+
end
|
122
|
+
|
123
|
+
path = @environment.filename.dirname.join(path)
|
124
|
+
|
125
|
+
@directory_sources.each do |s|
|
126
|
+
if s.location.expand_path.to_s < path.expand_path.to_s
|
127
|
+
return s, path.relative_path_from(s.location)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
|
134
|
+
def _handle_git_option(name, version, options)
|
135
|
+
git = options[:git].to_s
|
136
|
+
ref = options[:commit] || options[:tag]
|
137
|
+
branch = options[:branch]
|
138
|
+
|
139
|
+
if source = @git || @git_sources[git]
|
140
|
+
if ref && source.ref != ref
|
141
|
+
raise GitSourceError, "'#{git}' already specified with ref: #{source.ref}"
|
142
|
+
elsif branch && source.branch != branch
|
143
|
+
raise GitSourceError, "'#{git}' already specified with branch: #{source.branch}"
|
144
|
+
end
|
145
|
+
|
146
|
+
source.required_specs << name
|
147
|
+
source.add_spec(Pathname.new(options[:path] || '.'), name, version) if version
|
148
|
+
else
|
149
|
+
git(git, :ref => ref, :branch => branch) do
|
150
|
+
_handle_git_option(name, version, options)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def _combine_only(only)
|
97
156
|
return @only unless only
|
98
|
-
only =
|
157
|
+
only = Array(only).compact.uniq.map { |o| o.to_s }
|
99
158
|
only &= @only if @only
|
100
159
|
only
|
101
160
|
end
|
102
161
|
|
103
|
-
def
|
162
|
+
def _combine_except(except)
|
104
163
|
return @except unless except
|
105
|
-
except =
|
164
|
+
except = Array(except).compact.uniq.map { |o| o.to_s }
|
106
165
|
except |= @except if @except
|
107
166
|
except
|
108
167
|
end
|
data/lib/bundler/environment.rb
CHANGED
@@ -10,11 +10,11 @@ module Bundler
|
|
10
10
|
attr_accessor :rubygems, :system_gems
|
11
11
|
attr_writer :gem_path, :bindir
|
12
12
|
|
13
|
-
def self.load(
|
14
|
-
gemfile =
|
13
|
+
def self.load(file = nil)
|
14
|
+
gemfile = Pathname.new(file || default_manifest_file).expand_path
|
15
15
|
|
16
16
|
unless gemfile.file?
|
17
|
-
raise ManifestFileNotFound, "#{
|
17
|
+
raise ManifestFileNotFound, "Manifest file not found: #{gemfile.to_s.inspect}"
|
18
18
|
end
|
19
19
|
|
20
20
|
new(gemfile)
|
@@ -32,9 +32,9 @@ module Bundler
|
|
32
32
|
raise DefaultManifestNotFound
|
33
33
|
end
|
34
34
|
|
35
|
-
def initialize(filename)
|
35
|
+
def initialize(filename)
|
36
36
|
@filename = filename
|
37
|
-
@default_sources =
|
37
|
+
@default_sources = default_sources
|
38
38
|
@sources = []
|
39
39
|
@priority_sources = []
|
40
40
|
@dependencies = []
|
@@ -42,20 +42,27 @@ module Bundler
|
|
42
42
|
@system_gems = true
|
43
43
|
|
44
44
|
# Evaluate the Gemfile
|
45
|
-
|
46
|
-
builder.instance_eval(File.read(filename))
|
45
|
+
Dsl.evaluate(self, filename)
|
47
46
|
end
|
48
47
|
|
49
48
|
def install(options = {})
|
49
|
+
if only_envs = options[:only]
|
50
|
+
dependencies.reject! { |d| !only_envs.any? {|env| d.in?(env) } }
|
51
|
+
end
|
52
|
+
|
53
|
+
no_bundle = dependencies.map { |dep| !dep.bundle && dep.name }.compact
|
54
|
+
|
50
55
|
update = options[:update]
|
51
56
|
cached = options[:cached]
|
52
57
|
|
53
58
|
repository.install(gem_dependencies, sources,
|
54
|
-
:rubygems
|
55
|
-
:system_gems
|
56
|
-
:manifest
|
57
|
-
:update
|
58
|
-
:cached
|
59
|
+
:rubygems => rubygems,
|
60
|
+
:system_gems => system_gems,
|
61
|
+
:manifest => filename,
|
62
|
+
:update => options[:update],
|
63
|
+
:cached => options[:cached],
|
64
|
+
:build_options => options[:build_options],
|
65
|
+
:no_bundle => no_bundle
|
59
66
|
)
|
60
67
|
Bundler.logger.info "Done."
|
61
68
|
end
|
@@ -101,6 +108,18 @@ module Bundler
|
|
101
108
|
end
|
102
109
|
end
|
103
110
|
|
111
|
+
def list_outdated(options={})
|
112
|
+
outdated_gems = repository.outdated_gems
|
113
|
+
if outdated_gems.empty?
|
114
|
+
Bundler.logger.info "All gems are up to date."
|
115
|
+
else
|
116
|
+
Bundler.logger.info "Outdated gems:"
|
117
|
+
outdated_gems.each do |name|
|
118
|
+
Bundler.logger.info " * #{name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
104
123
|
def setup_environment
|
105
124
|
unless system_gems
|
106
125
|
ENV["GEM_HOME"] = gem_path
|
@@ -145,6 +164,10 @@ module Bundler
|
|
145
164
|
|
146
165
|
private
|
147
166
|
|
167
|
+
def default_sources
|
168
|
+
[GemSource.new(:uri => "http://gems.rubyforge.org"), SystemGemSource.instance]
|
169
|
+
end
|
170
|
+
|
148
171
|
def repository
|
149
172
|
@repository ||= Repository.new(gem_path, bindir)
|
150
173
|
end
|
data/lib/bundler/repository.rb
CHANGED
@@ -20,22 +20,31 @@ module Bundler
|
|
20
20
|
s.local = options[:cached]
|
21
21
|
end
|
22
22
|
|
23
|
+
source_requirements = {}
|
24
|
+
options[:no_bundle].each do |name|
|
25
|
+
source_requirements[name] = SystemGemSource.instance
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check to see whether the existing cache meets all the requirements
|
23
29
|
begin
|
24
|
-
valid = Resolver.resolve(dependencies, [source_index])
|
30
|
+
valid = Resolver.resolve(dependencies, [source_index], source_requirements)
|
25
31
|
rescue Bundler::GemNotFound
|
26
32
|
end
|
27
33
|
|
28
|
-
if options[:cached]
|
29
|
-
sources = sources.select { |s| s.can_be_local? }
|
30
|
-
end
|
34
|
+
sources = only_local(sources) if options[:cached]
|
31
35
|
|
36
|
+
# Check the remote sources if the existing cache does not meet the requirements
|
37
|
+
# or the user passed --update
|
32
38
|
if options[:update] || !valid
|
33
39
|
Bundler.logger.info "Calculating dependencies..."
|
34
|
-
bundle = Resolver.resolve(dependencies, [@cache] + sources)
|
40
|
+
bundle = Resolver.resolve(dependencies, [@cache] + sources, source_requirements)
|
41
|
+
download(bundle, options)
|
35
42
|
do_install(bundle, options)
|
36
43
|
valid = bundle
|
37
44
|
end
|
38
|
-
|
45
|
+
|
46
|
+
generate_bins(valid, options)
|
47
|
+
cleanup(valid, options)
|
39
48
|
configure(valid, options)
|
40
49
|
end
|
41
50
|
|
@@ -53,12 +62,14 @@ module Bundler
|
|
53
62
|
s.local = true
|
54
63
|
end
|
55
64
|
|
56
|
-
sources = sources
|
65
|
+
sources = only_local(sources)
|
57
66
|
bundle = Resolver.resolve(dependencies, [@cache] + sources)
|
58
|
-
@cache.gems.each do |name,
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
@cache.gems.each do |name, specs|
|
68
|
+
specs.each do |spec|
|
69
|
+
unless bundle.any? { |s| s.name == spec.name && s.version == spec.version }
|
70
|
+
Bundler.logger.info "Pruning #{spec.name} (#{spec.version}) from the cache"
|
71
|
+
FileUtils.rm @path.join("cache", "#{spec.full_name}.gem")
|
72
|
+
end
|
62
73
|
end
|
63
74
|
end
|
64
75
|
end
|
@@ -67,6 +78,10 @@ module Bundler
|
|
67
78
|
source_index.gems.values
|
68
79
|
end
|
69
80
|
|
81
|
+
def outdated_gems
|
82
|
+
source_index.outdated.sort
|
83
|
+
end
|
84
|
+
|
70
85
|
def source_index
|
71
86
|
index = Gem::SourceIndex.from_gems_in(@path.join("specifications"))
|
72
87
|
index.each { |n, spec| spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec") }
|
@@ -79,10 +94,20 @@ module Bundler
|
|
79
94
|
|
80
95
|
private
|
81
96
|
|
82
|
-
def
|
83
|
-
|
97
|
+
def only_local(sources)
|
98
|
+
sources.select { |s| s.can_be_local? }
|
99
|
+
end
|
100
|
+
|
101
|
+
def download(bundle, options)
|
102
|
+
bundle.sort_by {|s| s.full_name.downcase }.each do |spec|
|
103
|
+
next if options[:no_bundle].include?(spec.name)
|
104
|
+
spec.source.download(spec)
|
105
|
+
end
|
106
|
+
end
|
84
107
|
|
108
|
+
def do_install(bundle, options)
|
85
109
|
bundle.each do |spec|
|
110
|
+
next if options[:no_bundle].include?(spec.name)
|
86
111
|
spec.loaded_from = @path.join("specifications", "#{spec.full_name}.gemspec")
|
87
112
|
# Do nothing if the gem is already expanded
|
88
113
|
next if @path.join("gems", spec.full_name).directory?
|
@@ -96,11 +121,35 @@ module Bundler
|
|
96
121
|
end
|
97
122
|
end
|
98
123
|
|
124
|
+
def generate_bins(bundle, options)
|
125
|
+
bundle.each do |spec|
|
126
|
+
next if options[:no_bundle].include?(spec.name)
|
127
|
+
# HAX -- Generate the bin
|
128
|
+
bin_dir = @bindir
|
129
|
+
path = @path
|
130
|
+
installer = Gem::Installer.allocate
|
131
|
+
installer.instance_eval do
|
132
|
+
@spec = spec
|
133
|
+
@bin_dir = bin_dir
|
134
|
+
@gem_dir = path.join("gems", "#{spec.full_name}")
|
135
|
+
@gem_home = path
|
136
|
+
@wrappers = true
|
137
|
+
@format_executable = false
|
138
|
+
@env_shebang = false
|
139
|
+
end
|
140
|
+
installer.generate_bin
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
99
144
|
def expand_gemfile(spec, options)
|
100
145
|
Bundler.logger.info "Installing #{spec.name} (#{spec.version})"
|
101
146
|
|
102
147
|
gemfile = @path.join("cache", "#{spec.full_name}.gem").to_s
|
103
148
|
|
149
|
+
if build_args = options[:build_options] && options[:build_options][spec.name]
|
150
|
+
Gem::Command.build_args = build_args.map {|k,v| "--with-#{k}=#{v}"}
|
151
|
+
end
|
152
|
+
|
104
153
|
installer = Gem::Installer.new(gemfile, options.merge(
|
105
154
|
:install_dir => @path,
|
106
155
|
:ignore_dependencies => true,
|
@@ -109,25 +158,14 @@ module Bundler
|
|
109
158
|
:bin_dir => @bindir
|
110
159
|
))
|
111
160
|
installer.install
|
161
|
+
ensure
|
162
|
+
Gem::Command.build_args = []
|
112
163
|
end
|
113
164
|
|
114
165
|
def expand_vendored_gem(spec, options)
|
115
166
|
add_spec(spec)
|
116
167
|
FileUtils.mkdir_p(@path.join("gems"))
|
117
168
|
File.symlink(spec.location, @path.join("gems", spec.full_name))
|
118
|
-
|
119
|
-
# HAX -- Generate the bin
|
120
|
-
bin_dir = @bindir
|
121
|
-
path = @path
|
122
|
-
installer = Gem::Installer.allocate
|
123
|
-
installer.instance_eval do
|
124
|
-
@spec = spec
|
125
|
-
@bin_dir = bin_dir
|
126
|
-
@gem_dir = path.join("gems", "#{spec.full_name}")
|
127
|
-
@gem_home = path
|
128
|
-
@wrappers = true
|
129
|
-
end
|
130
|
-
installer.generate_bin
|
131
169
|
end
|
132
170
|
|
133
171
|
def add_spec(spec)
|
@@ -139,7 +177,7 @@ module Bundler
|
|
139
177
|
end
|
140
178
|
end
|
141
179
|
|
142
|
-
def cleanup(valid)
|
180
|
+
def cleanup(valid, options)
|
143
181
|
to_delete = gems
|
144
182
|
to_delete.delete_if do |spec|
|
145
183
|
valid.any? { |other| spec.name == other.name && spec.version == other.version }
|
@@ -173,13 +211,9 @@ module Bundler
|
|
173
211
|
def generate_environment(specs, options)
|
174
212
|
FileUtils.mkdir_p(path)
|
175
213
|
|
176
|
-
load_paths = load_paths_for_specs(specs)
|
214
|
+
load_paths = load_paths_for_specs(specs, options)
|
177
215
|
bindir = @bindir.relative_path_from(path).to_s
|
178
216
|
filename = options[:manifest].relative_path_from(path).to_s
|
179
|
-
spec_files = specs.inject({}) do |hash, spec|
|
180
|
-
relative = spec.loaded_from.relative_path_from(@path).to_s
|
181
|
-
hash.merge!(spec.name => relative)
|
182
|
-
end
|
183
217
|
|
184
218
|
File.open(path.join("environment.rb"), "w") do |file|
|
185
219
|
template = File.read(File.join(File.dirname(__FILE__), "templates", "environment.erb"))
|
@@ -188,20 +222,27 @@ module Bundler
|
|
188
222
|
end
|
189
223
|
end
|
190
224
|
|
191
|
-
def load_paths_for_specs(specs)
|
225
|
+
def load_paths_for_specs(specs, options)
|
192
226
|
load_paths = []
|
193
227
|
specs.each do |spec|
|
228
|
+
next if options[:no_bundle].include?(spec.name)
|
194
229
|
gem_path = Pathname.new(spec.full_gem_path)
|
195
|
-
if spec.bindir
|
196
|
-
load_paths << gem_path.join(spec.bindir).relative_path_from(@path).to_s
|
197
|
-
end
|
230
|
+
load_paths << load_path_for(gem_path, spec.bindir) if spec.bindir
|
198
231
|
spec.require_paths.each do |path|
|
199
|
-
load_paths << gem_path
|
232
|
+
load_paths << load_path_for(gem_path, path)
|
200
233
|
end
|
201
234
|
end
|
202
235
|
load_paths
|
203
236
|
end
|
204
237
|
|
238
|
+
def load_path_for(gem_path, path)
|
239
|
+
gem_path.join(path).relative_path_from(@path).to_s
|
240
|
+
end
|
241
|
+
|
242
|
+
def spec_file_for(spec)
|
243
|
+
spec.loaded_from.relative_path_from(@path).to_s
|
244
|
+
end
|
245
|
+
|
205
246
|
def require_code(file, dep)
|
206
247
|
constraint = case
|
207
248
|
when dep.only then %{ if #{dep.only.inspect}.include?(env)}
|
data/lib/bundler/resolver.rb
CHANGED
@@ -36,8 +36,8 @@ module Bundler
|
|
36
36
|
# ==== Returns
|
37
37
|
# <GemBundle>,nil:: If the list of dependencies can be resolved, a
|
38
38
|
# collection of gemspecs is returned. Otherwise, nil is returned.
|
39
|
-
def self.resolve(requirements, sources)
|
40
|
-
resolver = new(sources)
|
39
|
+
def self.resolve(requirements, sources, source_requirements = {})
|
40
|
+
resolver = new(sources, source_requirements)
|
41
41
|
result = catch(:success) do
|
42
42
|
resolver.resolve(requirements, {})
|
43
43
|
output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
|
@@ -49,22 +49,38 @@ module Bundler
|
|
49
49
|
raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
|
50
50
|
nil
|
51
51
|
end
|
52
|
-
|
52
|
+
if result
|
53
|
+
# Order gems in order of dependencies. Every gem's dependency is at
|
54
|
+
# a smaller index in the array.
|
55
|
+
ordered = []
|
56
|
+
result.values.each do |spec1|
|
57
|
+
index = nil
|
58
|
+
place = ordered.detect do |spec2|
|
59
|
+
spec1.dependencies.any? { |d| d.name == spec2.name }
|
60
|
+
end
|
61
|
+
place ?
|
62
|
+
ordered.insert(ordered.index(place), spec1) :
|
63
|
+
ordered << spec1
|
64
|
+
end
|
65
|
+
ordered.reverse
|
66
|
+
end
|
53
67
|
end
|
54
68
|
|
55
|
-
def initialize(sources)
|
69
|
+
def initialize(sources, source_requirements)
|
56
70
|
@errors = {}
|
57
71
|
@stack = []
|
58
|
-
@specs = Hash.new { |h,k| h[k] =
|
72
|
+
@specs = Hash.new { |h,k| h[k] = [] }
|
73
|
+
@by_gem = source_requirements
|
59
74
|
@cache = {}
|
60
75
|
@index = {}
|
61
76
|
|
62
|
-
sources.
|
63
|
-
source.gems.
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
77
|
+
sources.each do |source|
|
78
|
+
source.gems.each do |name, specs|
|
79
|
+
# Hack to work with a regular Gem::SourceIndex
|
80
|
+
[specs].flatten.compact.each do |spec|
|
81
|
+
next if @specs[spec.name].any? { |s| s.version == spec.version }
|
82
|
+
@specs[spec.name] << spec
|
83
|
+
end
|
68
84
|
end
|
69
85
|
end
|
70
86
|
end
|
@@ -74,11 +90,15 @@ module Bundler
|
|
74
90
|
# gem dependencies have been resolved.
|
75
91
|
throw :success, activated if reqs.empty?
|
76
92
|
|
77
|
-
# Sort
|
78
|
-
# Easiest to resolve is defined by:
|
79
|
-
#
|
80
|
-
|
81
|
-
|
93
|
+
# Sort dependencies so that the ones that are easiest to resolve are first.
|
94
|
+
# Easiest to resolve is defined by:
|
95
|
+
# 1) Is this gem already activated?
|
96
|
+
# 2) Do the version requirements include prereleased gems?
|
97
|
+
# 3) Sort by number of gems available in the source.
|
98
|
+
reqs = reqs.sort_by do |a|
|
99
|
+
[ activated[a.name] ? 0 : 1,
|
100
|
+
a.version_requirements.prerelease? ? 0 : 1,
|
101
|
+
activated[a.name] ? 0 : search(a).size ]
|
82
102
|
end
|
83
103
|
|
84
104
|
activated = activated.dup
|
@@ -178,7 +198,9 @@ module Bundler
|
|
178
198
|
|
179
199
|
def search(dependency)
|
180
200
|
@cache[dependency.hash] ||= begin
|
181
|
-
@
|
201
|
+
collection = @by_gem[dependency.name].gems if @by_gem[dependency.name]
|
202
|
+
collection ||= @specs
|
203
|
+
collection[dependency.name].select do |spec|
|
182
204
|
match = dependency =~ spec
|
183
205
|
match &= dependency.version_requirements.prerelease? if spec.version.prerelease?
|
184
206
|
match
|
data/lib/bundler/source.rb
CHANGED
@@ -1,8 +1,23 @@
|
|
1
1
|
module Bundler
|
2
|
+
class DirectorySourceError < StandardError; end
|
3
|
+
class GitSourceError < StandardError ; end
|
2
4
|
# Represents a source of rubygems. Initially, this is only gem repositories, but
|
3
5
|
# eventually, this will be git, svn, HTTP
|
4
6
|
class Source
|
5
7
|
attr_accessor :repository, :local
|
8
|
+
|
9
|
+
def initialize(options) ; end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def process_source_gems(gems)
|
14
|
+
new_gems = Hash.new { |h,k| h[k] = [] }
|
15
|
+
gems.values.each do |spec|
|
16
|
+
spec.source = self
|
17
|
+
new_gems[spec.name] << spec
|
18
|
+
end
|
19
|
+
new_gems
|
20
|
+
end
|
6
21
|
end
|
7
22
|
|
8
23
|
class GemSource < Source
|
@@ -38,7 +53,7 @@ module Bundler
|
|
38
53
|
destination = repository.path
|
39
54
|
|
40
55
|
unless destination.writable?
|
41
|
-
raise RubygemsRetardation
|
56
|
+
raise RubygemsRetardation, "destination: #{destination} is not writable"
|
42
57
|
end
|
43
58
|
|
44
59
|
# Download the gem
|
@@ -65,10 +80,11 @@ module Bundler
|
|
65
80
|
index = Marshal.load(main_index)
|
66
81
|
end
|
67
82
|
|
68
|
-
gems = {}
|
83
|
+
gems = Hash.new { |h,k| h[k] = [] }
|
69
84
|
index.each do |name, version, platform|
|
70
85
|
spec = RemoteSpecification.new(name, version, platform, @uri)
|
71
|
-
|
86
|
+
spec.source = self
|
87
|
+
gems[spec.name] << spec if Gem::Platform.match(spec.platform)
|
72
88
|
end
|
73
89
|
gems
|
74
90
|
rescue Gem::RemoteFetcher::FetchError => e
|
@@ -77,8 +93,13 @@ module Bundler
|
|
77
93
|
end
|
78
94
|
|
79
95
|
class SystemGemSource < Source
|
96
|
+
|
97
|
+
def self.instance
|
98
|
+
@instance ||= new({})
|
99
|
+
end
|
100
|
+
|
80
101
|
def initialize(options)
|
81
|
-
|
102
|
+
@source = Gem::SourceIndex.from_installed_gems
|
82
103
|
end
|
83
104
|
|
84
105
|
def can_be_local?
|
@@ -86,7 +107,7 @@ module Bundler
|
|
86
107
|
end
|
87
108
|
|
88
109
|
def gems
|
89
|
-
@
|
110
|
+
@gems ||= process_source_gems(@source.gems)
|
90
111
|
end
|
91
112
|
|
92
113
|
def ==(other)
|
@@ -98,9 +119,6 @@ module Bundler
|
|
98
119
|
end
|
99
120
|
|
100
121
|
def download(spec)
|
101
|
-
# gemfile = Pathname.new(local.loaded_from)
|
102
|
-
# gemfile = gemfile.dirname.join('..', 'cache', "#{local.full_name}.gem").expand_path
|
103
|
-
# repository.cache(File.join(Gem.dir, "cache", "#{local.full_name}.gem"))
|
104
122
|
gemfile = Pathname.new(spec.loaded_from)
|
105
123
|
gemfile = gemfile.dirname.join('..', 'cache', "#{spec.full_name}.gem")
|
106
124
|
repository.cache(gemfile)
|
@@ -108,10 +126,6 @@ module Bundler
|
|
108
126
|
|
109
127
|
private
|
110
128
|
|
111
|
-
def fetch_specs
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
129
|
end
|
116
130
|
|
117
131
|
class GemDirectorySource < Source
|
@@ -144,11 +158,12 @@ module Bundler
|
|
144
158
|
private
|
145
159
|
|
146
160
|
def fetch_specs
|
147
|
-
specs = {}
|
161
|
+
specs = Hash.new { |h,k| h[k] = [] }
|
148
162
|
|
149
163
|
Dir["#{@location}/*.gem"].each do |gemfile|
|
150
164
|
spec = Gem::Format.from_file_by_path(gemfile).spec
|
151
|
-
|
165
|
+
spec.source = self
|
166
|
+
specs[spec.name] << spec
|
152
167
|
end
|
153
168
|
|
154
169
|
specs
|
@@ -156,13 +171,23 @@ module Bundler
|
|
156
171
|
end
|
157
172
|
|
158
173
|
class DirectorySource < Source
|
159
|
-
attr_reader :location
|
174
|
+
attr_reader :location, :specs, :required_specs
|
160
175
|
|
161
176
|
def initialize(options)
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@
|
177
|
+
if options[:location]
|
178
|
+
@location = Pathname.new(options[:location]).expand_path
|
179
|
+
end
|
180
|
+
@glob = options[:glob] || "**/*.gemspec"
|
181
|
+
@specs = {}
|
182
|
+
@required_specs = []
|
183
|
+
end
|
184
|
+
|
185
|
+
def add_spec(path, name, version, require_paths = %w(lib))
|
186
|
+
raise DirectorySourceError, "already have a gem defined for '#{path}'" if @specs[path.to_s]
|
187
|
+
@specs[path.to_s] = Gem::Specification.new do |s|
|
188
|
+
s.name = name
|
189
|
+
s.version = Gem::Version.new(version)
|
190
|
+
end
|
166
191
|
end
|
167
192
|
|
168
193
|
def can_be_local?
|
@@ -171,67 +196,70 @@ module Bundler
|
|
171
196
|
|
172
197
|
def gems
|
173
198
|
@gems ||= begin
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
199
|
+
# Locate all gemspecs from the directory
|
200
|
+
specs = locate_gemspecs
|
201
|
+
specs = merge_defined_specs(specs)
|
202
|
+
|
203
|
+
required_specs.each do |required|
|
204
|
+
unless specs.any? {|k,v| v.name == required }
|
205
|
+
raise DirectorySourceError, "No gemspec for '#{required}' was found in" \
|
206
|
+
" '#{location}'. Please explicitly specify a version."
|
182
207
|
end
|
183
208
|
end
|
184
209
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
when @version.nil?
|
189
|
-
raise ArgumentError, "If you use :at, you must specify the gem " \
|
190
|
-
"and version you wish to stand in for"
|
191
|
-
when !Gem::Version.correct?(@version)
|
192
|
-
raise ArgumentError, "If you use :at, you must specify a gem and " \
|
193
|
-
"version. You specified #{@version} for the version"
|
194
|
-
end
|
210
|
+
process_source_gems(specs)
|
211
|
+
end
|
212
|
+
end
|
195
213
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
specs[
|
214
|
+
def locate_gemspecs
|
215
|
+
Dir["#{location}/#{@glob}"].inject({}) do |specs, file|
|
216
|
+
file = Pathname.new(file)
|
217
|
+
if spec = eval(File.read(file)) and validate_gemspec(file.dirname, spec)
|
218
|
+
spec.location = file.dirname.expand_path
|
219
|
+
specs[spec.full_name] = spec
|
202
220
|
end
|
203
|
-
|
204
221
|
specs
|
205
222
|
end
|
206
223
|
end
|
207
224
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
225
|
+
def merge_defined_specs(specs)
|
226
|
+
@specs.each do |path, spec|
|
227
|
+
# Set the spec location
|
228
|
+
spec.location = "#{location}/#{path}"
|
229
|
+
|
230
|
+
if existing = specs.values.find { |s| s.name == spec.name }
|
231
|
+
if existing.version != spec.version
|
232
|
+
raise DirectorySourceError, "The version you specified for #{spec.name}" \
|
233
|
+
" is #{spec.version}. The gemspec is #{existing.version}."
|
234
|
+
# Not sure if this is needed
|
235
|
+
# ====
|
236
|
+
# elsif File.expand_path(existing.location) != File.expand_path(spec.location)
|
237
|
+
# raise DirectorySourceError, "The location you specified for #{spec.name}" \
|
238
|
+
# " is '#{spec.location}'. The gemspec was found at '#{existing.location}'."
|
239
|
+
end
|
240
|
+
elsif !validate_gemspec(spec.location, spec)
|
241
|
+
raise "Your gem definition is not valid: #{spec}"
|
242
|
+
else
|
243
|
+
specs[spec.full_name] = spec
|
244
|
+
end
|
245
|
+
end
|
246
|
+
specs
|
247
|
+
end
|
248
|
+
|
249
|
+
def validate_gemspec(path, spec)
|
250
|
+
path = Pathname.new(path)
|
223
251
|
msg = "Gemspec for #{spec.name} (#{spec.version}) is invalid:"
|
224
252
|
# Check the require_paths
|
225
|
-
(spec.require_paths || []).each do |
|
226
|
-
unless
|
227
|
-
Bundler.logger.warn "#{msg} Missing require path: '#{
|
253
|
+
(spec.require_paths || []).each do |require_path|
|
254
|
+
unless path.join(require_path).directory?
|
255
|
+
Bundler.logger.warn "#{msg} Missing require path: '#{require_path}'"
|
228
256
|
return false
|
229
257
|
end
|
230
258
|
end
|
231
259
|
|
232
260
|
# Check the executables
|
233
261
|
(spec.executables || []).each do |exec|
|
234
|
-
unless
|
262
|
+
unless path.join(spec.bindir, exec).file?
|
235
263
|
Bundler.logger.warn "#{msg} Missing executable: '#{File.join(spec.bindir, exec)}'"
|
236
264
|
return false
|
237
265
|
end
|
@@ -255,6 +283,8 @@ module Bundler
|
|
255
283
|
end
|
256
284
|
|
257
285
|
class GitSource < DirectorySource
|
286
|
+
attr_reader :ref, :uri, :branch
|
287
|
+
|
258
288
|
def initialize(options)
|
259
289
|
super
|
260
290
|
@uri = options[:uri]
|
@@ -281,9 +311,9 @@ module Bundler
|
|
281
311
|
`git clone #{@uri} #{location} --no-hardlinks`
|
282
312
|
|
283
313
|
if @ref
|
284
|
-
Dir.chdir(location) { `git checkout #{@ref}` }
|
314
|
+
Dir.chdir(location) { `git checkout --quiet #{@ref}` }
|
285
315
|
elsif @branch && @branch != "master"
|
286
|
-
Dir.chdir(location) { `git checkout origin/#{@branch}` }
|
316
|
+
Dir.chdir(location) { `git checkout --quiet origin/#{@branch}` }
|
287
317
|
end
|
288
318
|
end
|
289
319
|
super
|
@@ -20,9 +20,14 @@ module Bundler
|
|
20
20
|
require "rubygems"
|
21
21
|
|
22
22
|
@bundled_specs = {}
|
23
|
-
<%
|
24
|
-
|
25
|
-
|
23
|
+
<% specs.each do |spec| -%>
|
24
|
+
<% if options[:no_bundle].include?(spec.name) -%>
|
25
|
+
gem "<%= spec.name %>", "<%= spec.version %>"
|
26
|
+
<% else -%>
|
27
|
+
<% path = spec_file_for(spec) -%>
|
28
|
+
@bundled_specs["<%= spec.name %>"] = eval(File.read("#{dir}/<%= path %>"))
|
29
|
+
@bundled_specs["<%= spec.name %>"].loaded_from = "#{dir}/<%= path %>"
|
30
|
+
<% end -%>
|
26
31
|
<% end -%>
|
27
32
|
|
28
33
|
def self.add_specs_to_loaded_specs
|
@@ -42,51 +47,62 @@ module Bundler
|
|
42
47
|
def self.require_env(env = nil)
|
43
48
|
context = Class.new do
|
44
49
|
def initialize(env) @env = env && env.to_s ; end
|
45
|
-
def method_missing(*) ; end
|
46
|
-
def only(env)
|
47
|
-
old, @only = @only,
|
50
|
+
def method_missing(*) ; yield if block_given? ; end
|
51
|
+
def only(*env)
|
52
|
+
old, @only = @only, _combine_only(env.flatten)
|
48
53
|
yield
|
49
54
|
@only = old
|
50
55
|
end
|
51
|
-
def except(env)
|
52
|
-
old, @except = @except,
|
56
|
+
def except(*env)
|
57
|
+
old, @except = @except, _combine_except(env.flatten)
|
53
58
|
yield
|
54
59
|
@except = old
|
55
60
|
end
|
56
61
|
def gem(name, *args)
|
57
|
-
opt = args.last
|
58
|
-
only =
|
59
|
-
except =
|
62
|
+
opt = args.last.is_a?(Hash) ? args.pop : {}
|
63
|
+
only = _combine_only(opt[:only] || opt["only"])
|
64
|
+
except = _combine_except(opt[:except] || opt["except"])
|
60
65
|
files = opt[:require_as] || opt["require_as"] || name
|
61
66
|
files = [files] unless files.respond_to?(:each)
|
62
67
|
|
63
68
|
return unless !only || only.any? {|e| e == @env }
|
64
69
|
return if except && except.any? {|e| e == @env }
|
65
70
|
|
66
|
-
files
|
71
|
+
if files = opt[:require_as] || opt["require_as"]
|
72
|
+
files = Array(files)
|
73
|
+
files.each { |f| require f }
|
74
|
+
else
|
75
|
+
begin
|
76
|
+
require name
|
77
|
+
rescue LoadError
|
78
|
+
# Do nothing
|
79
|
+
end
|
80
|
+
end
|
67
81
|
yield if block_given?
|
68
82
|
true
|
69
83
|
end
|
70
84
|
private
|
71
|
-
def
|
85
|
+
def _combine_only(only)
|
72
86
|
return @only unless only
|
73
87
|
only = [only].flatten.compact.uniq.map { |o| o.to_s }
|
74
88
|
only &= @only if @only
|
75
89
|
only
|
76
90
|
end
|
77
|
-
def
|
91
|
+
def _combine_except(except)
|
78
92
|
return @except unless except
|
79
93
|
except = [except].flatten.compact.uniq.map { |o| o.to_s }
|
80
94
|
except |= @except if @except
|
81
95
|
except
|
82
96
|
end
|
83
97
|
end
|
84
|
-
context.new(env && env.to_s).instance_eval(File.read(@gemfile))
|
98
|
+
context.new(env && env.to_s).instance_eval(File.read(@gemfile), @gemfile, 1)
|
85
99
|
end
|
86
100
|
end
|
87
101
|
|
88
102
|
<% if options[:rubygems] -%>
|
89
103
|
module Gem
|
104
|
+
@loaded_stacks = Hash.new { |h,k| h[k] = [] }
|
105
|
+
|
90
106
|
def source_index.refresh!
|
91
107
|
super
|
92
108
|
Bundler.add_specs_to_index
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yehuda Katz
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-
|
13
|
+
date: 2009-11-05 00:00:00 -08:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|