rubygems-update 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rubygems-update might be problematic. Click here for more details.
- data/.document +4 -0
- data/ChangeLog +458 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +53 -0
- data/Rakefile +26 -10
- data/TODO +0 -1
- data/bin/gem_server +2 -434
- data/bin/gemlock +1 -1
- data/bin/index_gem_repository.rb +34 -12
- data/examples/application/an-app.gemspec +4 -2
- data/examples/application/ext/Makefile +139 -0
- data/examples/application/ext/extconf.rb +3 -0
- data/examples/application/ext/foo.c +1 -0
- data/lib/gemconfigure.rb +2 -2
- data/lib/rubygems.rb +85 -46
- data/lib/rubygems/cmd_manager.rb +14 -11
- data/lib/rubygems/command.rb +18 -9
- data/lib/rubygems/config_file.rb +17 -16
- data/lib/rubygems/custom_require.rb +7 -12
- data/lib/rubygems/dependency_list.rb +38 -38
- data/lib/rubygems/gem_commands.rb +471 -242
- data/lib/rubygems/gem_openssl.rb +1 -1
- data/lib/rubygems/gem_runner.rb +2 -1
- data/lib/rubygems/installer.rb +189 -143
- data/lib/rubygems/package.rb +625 -621
- data/lib/rubygems/remote_fetcher.rb +142 -0
- data/lib/rubygems/remote_installer.rb +85 -465
- data/lib/rubygems/rubygems_version.rb +1 -1
- data/lib/rubygems/security.rb +54 -11
- data/lib/rubygems/server.rb +486 -0
- data/lib/rubygems/source_index.rb +187 -21
- data/lib/rubygems/source_info_cache.rb +153 -0
- data/lib/rubygems/source_info_cache_entry.rb +31 -0
- data/lib/rubygems/specification.rb +71 -30
- data/lib/rubygems/user_interaction.rb +22 -20
- data/lib/rubygems/validator.rb +8 -7
- data/lib/rubygems/version.rb +32 -17
- data/pkgs/sources/sources-0.0.1.gem +0 -0
- data/post-install.rb +42 -3
- data/scripts/gemdoc.rb +3 -3
- data/scripts/runtest.rb +1 -1
- data/scripts/upload_gemdoc.rb +11 -11
- data/setup.rb +14 -7
- data/test/brokenbuildgem.rb +35 -0
- data/test/data/a-0.0.1.gem +0 -0
- data/test/data/a-0.0.2.gem +0 -0
- data/test/data/b-0.0.2.gem +0 -0
- data/test/data/broken-1.0.0.gem +0 -0
- data/test/data/broken_build/broken-build.gemspec +20 -0
- data/test/data/broken_build/ext/extconf.rb +3 -0
- data/test/data/broken_build/ext/foo.c +1 -0
- data/test/data/c-1.2.gem +0 -0
- data/test/data/gemhome/cache/a-0.0.1.gem +0 -0
- data/test/data/gemhome/cache/a-0.0.2.gem +0 -0
- data/test/data/gemhome/cache/b-0.0.2.gem +0 -0
- data/test/data/gemhome/cache/c-1.2.gem +0 -0
- data/test/data/gemhome/gems/a-0.0.1/lib/code.rb +0 -6
- data/test/data/gemhome/gems/a-0.0.2/lib/code.rb +0 -6
- data/test/data/gemhome/gems/b-0.0.2/lib/code.rb +0 -6
- data/test/data/gemhome/gems/c-1.2/lib/code.rb +0 -6
- data/test/data/gemhome/specifications/a-0.0.1.gemspec +1 -1
- data/test/data/gemhome/specifications/a-0.0.2.gemspec +1 -1
- data/test/data/gemhome/specifications/b-0.0.2.gemspec +1 -1
- data/test/data/gemhome/specifications/c-1.2.gemspec +1 -1
- data/test/data/lib/code.rb +0 -6
- data/test/data/one/one-0.0.1.gem +0 -0
- data/test/functional.rb +24 -21
- data/test/functional_extension_gems.rb +48 -0
- data/test/functional_generate_yaml_index.rb +6 -3
- data/test/gemenvironment.rb +27 -22
- data/test/gemutilities.rb +95 -5
- data/test/insure_session.rb +2 -2
- data/test/io_capture.rb +33 -0
- data/test/test_check_command.rb +2 -2
- data/test/test_command.rb +16 -13
- data/test/test_dependency_list.rb +20 -20
- data/test/test_file_list.rb +10 -11
- data/test/test_format.rb +19 -46
- data/test/test_gem.rb +25 -0
- data/test/test_gem_ext_configure_builder.rb +88 -0
- data/test/test_gem_ext_ext_conf_builder.rb +122 -0
- data/test/test_gem_ext_rake_builder.rb +61 -0
- data/test/test_gem_outdated_command.rb +37 -0
- data/test/test_gem_source_info_cache.rb +196 -0
- data/test/test_gem_source_info_cache_entry.rb +44 -0
- data/test/test_gem_sources_command.rb +130 -0
- data/test/test_installer.rb +137 -9
- data/test/test_package.rb +521 -531
- data/test/test_parse_commands.rb +7 -7
- data/test/test_process_commands.rb +1 -1
- data/test/test_remote_fetcher.rb +187 -45
- data/test/test_remote_installer.rb +35 -52
- data/test/test_require_gem.rb +12 -12
- data/test/test_source_index.rb +194 -48
- data/test/test_specification.rb +154 -0
- data/test/test_user_interaction.rb +48 -0
- data/test/test_validator.rb +1 -1
- data/test/test_version_comparison.rb +116 -5
- metadata +33 -10
- data/lib/rubygems/incremental_fetcher.rb +0 -136
- data/lib/rubygems/loadpath_manager.rb +0 -114
- data/lib/rubygems/open-uri.rb +0 -756
- data/test/test_cached_fetcher.rb +0 -64
- data/test/test_incremental_fetcher.rb +0 -175
- data/test/test_local_cache.rb +0 -106
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rubygems/user_interaction'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Represents an error communicating via HTTP.
|
11
|
+
|
12
|
+
class Gem::RemoteSourceException < Gem::Exception; end
|
13
|
+
|
14
|
+
##
|
15
|
+
# RemoteFetcher handles the details of fetching gems and gem information from
|
16
|
+
# a remote source.
|
17
|
+
|
18
|
+
class Gem::RemoteFetcher
|
19
|
+
|
20
|
+
class FetchError < Gem::Exception; end
|
21
|
+
|
22
|
+
@fetcher = nil
|
23
|
+
|
24
|
+
# Cached RemoteFetcher instance.
|
25
|
+
def self.fetcher
|
26
|
+
@fetcher ||= new Gem.configuration[:http_proxy]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Initialize a remote fetcher using the source URI and possible proxy
|
30
|
+
# information.
|
31
|
+
#
|
32
|
+
# +proxy+
|
33
|
+
# * [String]: explicit specification of proxy; overrides any environment
|
34
|
+
# variable setting
|
35
|
+
# * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER,
|
36
|
+
# HTTP_PROXY_PASS)
|
37
|
+
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_ use a proxy
|
38
|
+
def initialize(proxy)
|
39
|
+
@proxy_uri =
|
40
|
+
case proxy
|
41
|
+
when :no_proxy then nil
|
42
|
+
when nil then get_proxy_from_env
|
43
|
+
else URI.parse(proxy.to_str)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Downloads +uri+.
|
48
|
+
def fetch_path(uri)
|
49
|
+
open_uri_or_path(uri) do |input|
|
50
|
+
input.read
|
51
|
+
end
|
52
|
+
rescue Timeout::Error
|
53
|
+
raise FetchError, "timed out fetching #{uri}"
|
54
|
+
rescue IOError, SocketError, SystemCallError => e
|
55
|
+
raise FetchError, "#{e.class} reading #{uri}"
|
56
|
+
rescue
|
57
|
+
old_uri = uri
|
58
|
+
uri = uri.downcase
|
59
|
+
retry if old_uri != uri
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the size of +uri+ in bytes.
|
64
|
+
def fetch_size(uri)
|
65
|
+
return File.size(get_file_uri_path(uri)) if file_uri?(uri)
|
66
|
+
require 'net/http'
|
67
|
+
require 'uri'
|
68
|
+
u = URI.parse(uri)
|
69
|
+
raise ArgumentError, 'uri is not an HTTP URI' unless URI::HTTP === u
|
70
|
+
http = connect_to(u.host, u.port)
|
71
|
+
resp = http.head(u.request_uri)
|
72
|
+
raise Gem::RemoteSourceException, "HTTP Response #{resp.code}" if resp.code !~ /^2/
|
73
|
+
resp['content-length'].to_i
|
74
|
+
rescue SocketError, SystemCallError, Timeout::Error => e
|
75
|
+
raise FetchError, "#{e.message}(#{e.class})"
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def escape(str)
|
81
|
+
return unless str
|
82
|
+
URI.escape(str)
|
83
|
+
end
|
84
|
+
|
85
|
+
def unescape(str)
|
86
|
+
return unless str
|
87
|
+
URI.unescape(str)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns an HTTP proxy URI if one is set in the environment variables.
|
91
|
+
def get_proxy_from_env
|
92
|
+
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
93
|
+
uri = env_proxy ? URI.parse(env_proxy) : nil
|
94
|
+
if uri and uri.user.nil? and uri.password.nil? then
|
95
|
+
# Probably we have http_proxy_* variables?
|
96
|
+
uri.user = escape(ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER'])
|
97
|
+
uri.password = escape(ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS'])
|
98
|
+
end
|
99
|
+
uri
|
100
|
+
end
|
101
|
+
|
102
|
+
# Normalize the URI by adding "http://" if it is missing.
|
103
|
+
def normalize_uri(uri)
|
104
|
+
(uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Connect to the source host/port, using a proxy if needed.
|
108
|
+
def connect_to(host, port)
|
109
|
+
if @proxy_uri
|
110
|
+
Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, unescape(@proxy_uri.user), unescape(@proxy_uri.password)).new(host, port)
|
111
|
+
else
|
112
|
+
Net::HTTP.new(host, port)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Read the data from the (source based) URI, but if it is a file:// URI,
|
117
|
+
# read from the filesystem instead.
|
118
|
+
def open_uri_or_path(uri, &block)
|
119
|
+
require 'open-uri'
|
120
|
+
if file_uri?(uri)
|
121
|
+
open(get_file_uri_path(uri), &block)
|
122
|
+
else
|
123
|
+
connection_options = {
|
124
|
+
"User-Agent" => "RubyGems/#{Gem::RubyGemsVersion}",
|
125
|
+
:proxy => @proxy_uri,
|
126
|
+
}
|
127
|
+
open(uri, connection_options, &block)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Checks if the provided string is a file:// URI.
|
132
|
+
def file_uri?(uri)
|
133
|
+
uri =~ %r{\Afile://}
|
134
|
+
end
|
135
|
+
|
136
|
+
# Given a file:// URI, returns its local path.
|
137
|
+
def get_file_uri_path(uri)
|
138
|
+
uri.sub(%r{\Afile://}, '')
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
@@ -4,402 +4,23 @@
|
|
4
4
|
# See LICENSE.txt for permissions.
|
5
5
|
#++
|
6
6
|
|
7
|
-
require 'rubygems'
|
8
|
-
require 'socket'
|
9
7
|
require 'fileutils'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'rubygems/installer'
|
12
|
+
require 'rubygems/source_info_cache'
|
13
|
+
|
14
|
+
require 'sources'
|
10
15
|
|
11
16
|
module Gem
|
12
17
|
class DependencyError < Gem::Exception; end
|
13
|
-
class RemoteSourceException < Gem::Exception; end
|
14
18
|
class GemNotFoundException < Gem::Exception; end
|
15
19
|
class RemoteInstallationCancelled < Gem::Exception; end
|
16
|
-
|
17
|
-
####################################################################
|
18
|
-
# RemoteSourceFetcher handles the details of fetching gems and gem
|
19
|
-
# information from a remote source.
|
20
|
-
class RemoteSourceFetcher
|
21
|
-
include UserInteraction
|
22
|
-
|
23
|
-
# Initialize a remote fetcher using the source URI (and possible
|
24
|
-
# proxy information).
|
25
|
-
# +proxy+
|
26
|
-
# * [String]: explicit specification of proxy; overrides any
|
27
|
-
# environment variable setting
|
28
|
-
# * nil: respect environment variables (HTTP_PROXY, HTTP_PROXY_USER, HTTP_PROXY_PASS)
|
29
|
-
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_
|
30
|
-
# use a proxy
|
31
|
-
def initialize(source_uri, proxy)
|
32
|
-
@uri = normalize_uri(source_uri)
|
33
|
-
@proxy_uri =
|
34
|
-
case proxy
|
35
|
-
when :no_proxy
|
36
|
-
nil
|
37
|
-
when nil
|
38
|
-
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
39
|
-
uri = env_proxy ? URI.parse(env_proxy) : nil
|
40
|
-
if uri and uri.user.nil? and uri.password.nil?
|
41
|
-
#Probably we have http_proxy_* variables?
|
42
|
-
uri.user = ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
|
43
|
-
uri.password = ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
|
44
|
-
end
|
45
|
-
uri
|
46
|
-
else
|
47
|
-
URI.parse(proxy.to_str)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# The uncompressed +size+ of the source's directory (e.g. source
|
52
|
-
# info).
|
53
|
-
def size
|
54
|
-
@size ||= get_size("/yaml")
|
55
|
-
end
|
56
|
-
|
57
|
-
# Fetch the data from the source at the given path.
|
58
|
-
def fetch_path(path="")
|
59
|
-
read_data(@uri + path)
|
60
|
-
end
|
61
|
-
|
62
|
-
# Get the source index from the gem source. The source index is a
|
63
|
-
# directory of the gems available on the source, formatted as a
|
64
|
-
# Gem::Cache object. The cache object allows easy searching for
|
65
|
-
# gems by name and version requirement.
|
66
|
-
#
|
67
|
-
# Notice that the gem specs in the cache are adequate for searches
|
68
|
-
# and queries, but may have some information elided (hence
|
69
|
-
# "abbreviated").
|
70
|
-
def source_index
|
71
|
-
say "Bulk updating Gem source index for: #{@uri}"
|
72
|
-
begin
|
73
|
-
require 'zlib'
|
74
|
-
yaml_spec = fetch_path("/yaml.Z")
|
75
|
-
yaml_spec = Zlib::Inflate.inflate(yaml_spec)
|
76
|
-
rescue
|
77
|
-
yaml_spec = nil
|
78
|
-
end
|
79
|
-
begin
|
80
|
-
yaml_spec = fetch_path("/yaml") unless yaml_spec
|
81
|
-
convert_spec(yaml_spec)
|
82
|
-
rescue SocketError => e
|
83
|
-
raise RemoteSourceException.new("Error fetching remote gem cache: #{e.to_s}")
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
# Normalize the URI by adding "http://" if it is missing.
|
90
|
-
def normalize_uri(uri)
|
91
|
-
(uri =~ /^(https?|ftp|file):/) ? uri : "http://#{uri}"
|
92
|
-
end
|
93
|
-
|
94
|
-
# Connect to the source host/port, using a proxy if needed.
|
95
|
-
def connect_to(host, port)
|
96
|
-
if @proxy_uri
|
97
|
-
Net::HTTP::Proxy(@proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password).new(host, port)
|
98
|
-
else
|
99
|
-
Net::HTTP.new(host, port)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Get the size of the (non-compressed) data from the source at the
|
104
|
-
# given path.
|
105
|
-
def get_size(path)
|
106
|
-
read_size(@uri + path)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Read the size of the (source based) URI using an HTTP HEAD
|
110
|
-
# command.
|
111
|
-
def read_size(uri)
|
112
|
-
return File.size(get_file_uri_path(uri)) if is_file_uri(uri)
|
113
|
-
require 'net/http'
|
114
|
-
require 'uri'
|
115
|
-
u = URI.parse(uri)
|
116
|
-
http = connect_to(u.host, u.port)
|
117
|
-
path = (u.path == "") ? "/" : u.path
|
118
|
-
resp = http.head(path)
|
119
|
-
fail RemoteSourceException, "HTTP Response #{resp.code}" if resp.code !~ /^2/
|
120
|
-
resp['content-length'].to_i
|
121
|
-
end
|
122
|
-
|
123
|
-
# Read the data from the (source based) URI.
|
124
|
-
def read_data(uri)
|
125
|
-
begin
|
126
|
-
open_uri_or_path(uri) do |input|
|
127
|
-
input.read
|
128
|
-
end
|
129
|
-
rescue
|
130
|
-
old_uri = uri
|
131
|
-
uri = uri.downcase
|
132
|
-
retry if old_uri != uri
|
133
|
-
raise
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Read the data from the (source based) URI, but if it is a
|
138
|
-
# file:// URI, read from the filesystem instead.
|
139
|
-
def open_uri_or_path(uri, &block)
|
140
|
-
require 'rubygems/open-uri'
|
141
|
-
if is_file_uri(uri)
|
142
|
-
open(get_file_uri_path(uri), &block)
|
143
|
-
else
|
144
|
-
connection_options = {"User-Agent" => "RubyGems/#{Gem::RubyGemsVersion}"}
|
145
|
-
if @proxy_uri
|
146
|
-
http_proxy_url = "#{@proxy_uri.scheme}://#{@proxy_uri.host}:#{@proxy_uri.port}"
|
147
|
-
connection_options[:proxy_http_basic_authentication] = [http_proxy_url, @proxy_uri.user||'', @proxy_uri.password||'']
|
148
|
-
end
|
149
|
-
|
150
|
-
open(uri, connection_options, &block)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# Checks if the provided string is a file:// URI.
|
155
|
-
def is_file_uri(uri)
|
156
|
-
uri =~ %r{\Afile://}
|
157
|
-
end
|
158
|
-
|
159
|
-
# Given a file:// URI, returns its local path.
|
160
|
-
def get_file_uri_path(uri)
|
161
|
-
uri.sub(%r{\Afile://}, '')
|
162
|
-
end
|
163
|
-
|
164
|
-
# Convert the yamlized string spec into a real spec (actually,
|
165
|
-
# these are hashes of specs.).
|
166
|
-
def convert_spec(yaml_spec)
|
167
|
-
YAML.load(reduce_spec(yaml_spec)) or
|
168
|
-
fail "Didn't get a valid YAML document"
|
169
|
-
end
|
170
|
-
|
171
|
-
# This reduces the source spec in size so that YAML bugs with
|
172
|
-
# large data sets will be dodged. Obviously this is a workaround,
|
173
|
-
# but it allows Gems to continue to work until the YAML bug is
|
174
|
-
# fixed.
|
175
|
-
def reduce_spec(yaml_spec)
|
176
|
-
result = ""
|
177
|
-
state = :copy
|
178
|
-
yaml_spec.each do |line|
|
179
|
-
if state == :copy && line =~ /^\s+files:\s*$/
|
180
|
-
state = :skip
|
181
|
-
result << line.sub(/$/, " []")
|
182
|
-
elsif state == :skip
|
183
|
-
if line !~ /^\s+-/
|
184
|
-
state = :copy
|
185
|
-
end
|
186
|
-
end
|
187
|
-
result << line if state == :copy
|
188
|
-
end
|
189
|
-
result
|
190
|
-
end
|
191
|
-
|
192
|
-
class << self
|
193
|
-
# Sent by the client when it is done with all the sources,
|
194
|
-
# allowing any cleanup activity to take place.
|
195
|
-
def finish
|
196
|
-
# Nothing to do
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
####################################################################
|
202
|
-
# Entrys held by a SourceInfoCache.
|
203
|
-
class SourceInfoCacheEntry
|
204
|
-
# The source index for this cache entry.
|
205
|
-
attr_reader :source_index
|
206
|
-
|
207
|
-
# The size of the of the source entry. Used to determine if the
|
208
|
-
# source index has changed.
|
209
|
-
attr_reader :size
|
210
|
-
|
211
|
-
# Create a cache entry.
|
212
|
-
def initialize(si, size)
|
213
|
-
replace_source_index(si, size)
|
214
|
-
end
|
215
|
-
|
216
|
-
# Replace the source index and the index size with given values.
|
217
|
-
def replace_source_index(si, size)
|
218
|
-
@source_index = si || SourceIndex.new({})
|
219
|
-
@size = size
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
####################################################################
|
224
|
-
# SourceInfoCache implements the cache management policy on where
|
225
|
-
# the source info is stored on local file system. There are two
|
226
|
-
# possible cache locations: (1) the system wide cache, and (2) the
|
227
|
-
# user specific cache.
|
228
|
-
#
|
229
|
-
# * The system cache is prefered if it is writable (or can be
|
230
|
-
# created).
|
231
|
-
# * The user cache is used if the system cache is not writable (or
|
232
|
-
# can not be created).
|
233
|
-
#
|
234
|
-
# Once a cache is selected, it will be used for all operations. It
|
235
|
-
# will not switch between cache files dynamically.
|
236
|
-
#
|
237
|
-
# Cache data is a simple hash indexed by the source URI. Retrieving
|
238
|
-
# and entry from the cache data will return a SourceInfoCacheEntry.
|
239
|
-
#
|
240
|
-
class SourceInfoCache
|
241
|
-
|
242
|
-
# The most recent cache data.
|
243
|
-
def cache_data
|
244
|
-
@dirty = false
|
245
|
-
@cache_data ||= read_cache
|
246
|
-
end
|
247
|
-
|
248
|
-
# Write data to the proper cache.
|
249
|
-
def write_cache
|
250
|
-
data = cache_data
|
251
|
-
open(writable_file, "wb") do |f|
|
252
|
-
f.write Marshal.dump(data)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
# The name of the system cache file.
|
257
|
-
def system_cache_file
|
258
|
-
@sysetm_cache ||= File.join(Gem.dir, "source_cache")
|
259
|
-
end
|
260
|
-
|
261
|
-
# The name of the user cache file.
|
262
|
-
def user_cache_file
|
263
|
-
@user_cache ||=
|
264
|
-
ENV['GEMCACHE'] || File.join(Gem.user_home, ".gem/source_cache")
|
265
|
-
end
|
266
|
-
|
267
|
-
# Mark the cache as updated (i.e. dirty).
|
268
|
-
def update
|
269
|
-
@dirty = true
|
270
|
-
end
|
271
|
-
|
272
|
-
# Write the cache to a local file (if it is dirty).
|
273
|
-
def flush
|
274
|
-
write_cache if @dirty
|
275
|
-
@dirty = false
|
276
|
-
end
|
277
|
-
|
278
|
-
private
|
279
|
-
|
280
|
-
# Find a writable cache file.
|
281
|
-
def writable_file
|
282
|
-
@cache_file
|
283
|
-
end
|
284
|
-
|
285
|
-
# Read the most current cache data.
|
286
|
-
def read_cache
|
287
|
-
@cache_file = select_cache_file
|
288
|
-
begin
|
289
|
-
open(@cache_file, "rb") { |f| load_local_cache(f) } || {}
|
290
|
-
rescue StandardError => ex
|
291
|
-
{}
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
def load_local_cache(f)
|
296
|
-
Marshal.load(f)
|
297
|
-
end
|
298
|
-
|
299
|
-
# Select a writable cache file
|
300
|
-
def select_cache_file
|
301
|
-
try_file(system_cache_file) or
|
302
|
-
try_file(user_cache_file) or
|
303
|
-
fail "Unable to locate a writable cache file."
|
304
|
-
end
|
305
|
-
|
306
|
-
# Determine if +fn+ is a candidate for a cache file. Return fn if
|
307
|
-
# it is. Return nil if it is not.
|
308
|
-
def try_file(fn)
|
309
|
-
return fn if File.writable?(fn)
|
310
|
-
return nil if File.exist?(fn)
|
311
|
-
dir = File.dirname(fn)
|
312
|
-
if ! File.exist? dir
|
313
|
-
begin
|
314
|
-
FileUtils.mkdir_p(dir)
|
315
|
-
rescue RuntimeError
|
316
|
-
return nil
|
317
|
-
end
|
318
|
-
end
|
319
|
-
if File.writable?(dir)
|
320
|
-
FileUtils.touch fn
|
321
|
-
return fn
|
322
|
-
end
|
323
|
-
nil
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
####################################################################
|
328
|
-
# CachedFetcher is a decorator that adds local file caching to
|
329
|
-
# RemoteSourceFetcher objects.
|
330
|
-
class CachedFetcher
|
331
|
-
|
332
|
-
# Create a cached fetcher (based on a RemoteSourceFetcher) for the
|
333
|
-
# source at +source_uri+ (through the proxy +proxy+).
|
334
|
-
def initialize(source_uri, proxy)
|
335
|
-
require 'rubygems/incremental_fetcher'
|
336
|
-
@source_uri = source_uri
|
337
|
-
rsf = RemoteSourceFetcher.new(source_uri, proxy)
|
338
|
-
@fetcher = IncrementalFetcher.new(source_uri, rsf, manager)
|
339
|
-
end
|
340
|
-
|
341
|
-
# The uncompressed +size+ of the source's directory (e.g. source
|
342
|
-
# info).
|
343
|
-
def size
|
344
|
-
@fetcher.size
|
345
|
-
end
|
346
|
-
|
347
|
-
# Fetch the data from the source at the given path.
|
348
|
-
def fetch_path(path="")
|
349
|
-
@fetcher.fetch_path(path)
|
350
|
-
end
|
351
|
-
|
352
|
-
# Get the source index from the gem source. The source index is a
|
353
|
-
# directory of the gems available on the source, formatted as a
|
354
|
-
# Gem::Cache object. The cache object allows easy searching for
|
355
|
-
# gems by name and version requirement.
|
356
|
-
#
|
357
|
-
# Notice that the gem specs in the cache are adequate for searches
|
358
|
-
# and queries, but may have some information elided (hence
|
359
|
-
# "abbreviated").
|
360
|
-
def source_index
|
361
|
-
cache = manager.cache_data[@source_uri]
|
362
|
-
if cache && cache.size == @fetcher.size
|
363
|
-
cache.source_index
|
364
|
-
else
|
365
|
-
result = @fetcher.source_index
|
366
|
-
manager.cache_data[@source_uri] = SourceInfoCacheEntry.new(result, @fetcher.size)
|
367
|
-
manager.update
|
368
|
-
result
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
# Flush the cache to a local file, if needed.
|
373
|
-
def flush
|
374
|
-
manager.flush
|
375
|
-
end
|
376
|
-
|
377
|
-
private
|
378
|
-
|
379
|
-
# The cache manager for this cached source.
|
380
|
-
def manager
|
381
|
-
self.class.manager
|
382
|
-
end
|
383
|
-
|
384
|
-
# The cache is shared between all caching fetchers, so the cache
|
385
|
-
# is put in the class object.
|
386
|
-
class << self
|
387
|
-
|
388
|
-
# The Cache manager for all instances of this class.
|
389
|
-
def manager
|
390
|
-
@manager ||= SourceInfoCache.new
|
391
|
-
end
|
392
|
-
|
393
|
-
# Sent by the client when it is done with all the sources,
|
394
|
-
# allowing any cleanup activity to take place.
|
395
|
-
def finish
|
396
|
-
manager.flush
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
end
|
20
|
+
class RemoteInstallationSkipped < Gem::Exception; end
|
401
21
|
|
402
22
|
class RemoteInstaller
|
23
|
+
|
403
24
|
include UserInteraction
|
404
25
|
|
405
26
|
# <tt>options[:http_proxy]</tt>::
|
@@ -409,12 +30,10 @@ module Gem
|
|
409
30
|
# * <tt>:no_proxy</tt>: ignore environment variables and _don't_
|
410
31
|
# use a proxy
|
411
32
|
#
|
33
|
+
# * <tt>:cache_dir</tt>: override where downloaded gems are cached.
|
412
34
|
def initialize(options={})
|
413
|
-
require 'uri'
|
414
|
-
|
415
|
-
# Ensure http_proxy env vars are used if no proxy explicitly supplied.
|
416
35
|
@options = options
|
417
|
-
@
|
36
|
+
@source_index_hash = nil
|
418
37
|
end
|
419
38
|
|
420
39
|
# This method will install package_name onto the local system.
|
@@ -428,109 +47,110 @@ module Gem
|
|
428
47
|
# Returns::
|
429
48
|
# an array of Gem::Specification objects, one for each gem installed.
|
430
49
|
#
|
431
|
-
def install(gem_name, version_requirement = "> 0.0.0", force=false,
|
50
|
+
def install(gem_name, version_requirement = "> 0.0.0", force=false,
|
51
|
+
install_dir=Gem.dir, install_stub=true)
|
432
52
|
unless version_requirement.respond_to?(:satisfied_by?)
|
433
|
-
version_requirement = Version::Requirement.new
|
53
|
+
version_requirement = Version::Requirement.new [version_requirement]
|
434
54
|
end
|
435
55
|
installed_gems = []
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
installed_gems << install_dependencies(dependencies, force, install_dir)
|
440
|
-
cache_dir = File.join(install_dir, "cache")
|
441
|
-
destination_file = File.join(cache_dir, spec.full_name + ".gem")
|
442
|
-
download_gem(destination_file, source, spec)
|
443
|
-
installer = new_installer(destination_file)
|
444
|
-
installed_gems.unshift installer.install(force, install_dir, install_stub)
|
445
|
-
installed_gems.flatten
|
446
|
-
end
|
56
|
+
begin
|
57
|
+
spec, source = find_gem_to_install(gem_name, version_requirement)
|
58
|
+
dependencies = find_dependencies_not_installed(spec.dependencies)
|
447
59
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
results << cache[1].search(pattern_to_match)
|
455
|
-
end
|
456
|
-
results
|
457
|
-
end
|
60
|
+
installed_gems << install_dependencies(dependencies, force, install_dir)
|
61
|
+
|
62
|
+
cache_dir = @options[:cache_dir] || File.join(install_dir, "cache")
|
63
|
+
destination_file = File.join(cache_dir, spec.full_name + ".gem")
|
64
|
+
|
65
|
+
download_gem(destination_file, source, spec)
|
458
66
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
@sources = Gem.sources
|
67
|
+
installer = new_installer(destination_file)
|
68
|
+
installed_gems.unshift installer.install(force, install_dir, install_stub)
|
69
|
+
rescue RemoteInstallationSkipped => e
|
70
|
+
puts e.message
|
464
71
|
end
|
465
|
-
|
72
|
+
installed_gems.flatten
|
466
73
|
end
|
467
|
-
|
74
|
+
|
468
75
|
# Return a hash mapping the available source names to the source
|
469
76
|
# index of that source.
|
470
77
|
def source_index_hash
|
471
|
-
|
472
|
-
|
473
|
-
|
78
|
+
return @source_index_hash if @source_index_hash
|
79
|
+
@source_index_hash = {}
|
80
|
+
Gem::SourceInfoCache.cache_data.each do |source_uri, sic_entry|
|
81
|
+
@source_index_hash[source_uri] = sic_entry.source_index
|
474
82
|
end
|
475
|
-
@
|
476
|
-
result
|
83
|
+
@source_index_hash
|
477
84
|
end
|
478
85
|
|
479
|
-
#
|
480
|
-
|
481
|
-
|
482
|
-
rsf.source_index
|
483
|
-
end
|
484
|
-
|
485
|
-
# Find a gem to be installed by interacting with the user.
|
486
|
-
def find_gem_to_install(gem_name, version_requirement, caches)
|
86
|
+
# Finds the Gem::Specification objects and the corresponding source URI
|
87
|
+
# for gems matching +gem_name+ and +version_requirement+
|
88
|
+
def specs_n_sources_matching(gem_name, version_requirement)
|
487
89
|
specs_n_sources = []
|
488
90
|
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
91
|
+
source_index_hash.each do |source_uri, source_index|
|
92
|
+
specs = source_index.search(/^#{Regexp.escape gem_name}$/i,
|
93
|
+
version_requirement)
|
94
|
+
# TODO move to SourceIndex#search?
|
95
|
+
ruby_version = Gem::Version.new RUBY_VERSION
|
96
|
+
specs = specs.select do |spec|
|
97
|
+
spec.required_ruby_version.nil? or
|
98
|
+
spec.required_ruby_version.satisfied_by? ruby_version
|
495
99
|
end
|
100
|
+
specs.each { |spec| specs_n_sources << [spec, source_uri] }
|
496
101
|
end
|
497
102
|
|
498
103
|
if specs_n_sources.empty? then
|
499
|
-
raise GemNotFoundException
|
104
|
+
raise GemNotFoundException, "Could not find #{gem_name} (#{version_requirement}) in any repository"
|
500
105
|
end
|
501
106
|
|
502
107
|
specs_n_sources = specs_n_sources.sort_by { |gs,| gs.version }.reverse
|
503
108
|
|
504
|
-
|
109
|
+
specs_n_sources
|
110
|
+
end
|
111
|
+
|
112
|
+
# Find a gem to be installed by interacting with the user.
|
113
|
+
def find_gem_to_install(gem_name, version_requirement)
|
114
|
+
specs_n_sources = specs_n_sources_matching gem_name, version_requirement
|
115
|
+
|
116
|
+
top_3_versions = specs_n_sources.map{|gs| gs.first.version}.uniq[0..3]
|
117
|
+
specs_n_sources.reject!{|gs| !top_3_versions.include?(gs.first.version)}
|
118
|
+
|
119
|
+
binary_gems = specs_n_sources.reject { |item|
|
505
120
|
item[0].platform.nil? || item[0].platform==Platform::RUBY
|
506
121
|
}
|
507
122
|
|
508
123
|
# only non-binary gems...return latest
|
509
|
-
return specs_n_sources.first if
|
124
|
+
return specs_n_sources.first if binary_gems.empty?
|
510
125
|
|
511
|
-
list = specs_n_sources.collect { |
|
512
|
-
|
126
|
+
list = specs_n_sources.collect { |spec, source_uri|
|
127
|
+
"#{spec.name} #{spec.version} (#{spec.platform})"
|
513
128
|
}
|
514
129
|
|
130
|
+
list << "Skip this gem"
|
515
131
|
list << "Cancel installation"
|
516
132
|
|
517
133
|
string, index = choose_from_list(
|
518
|
-
|
519
|
-
|
134
|
+
"Select which gem to install for your platform (#{RUBY_PLATFORM})",
|
135
|
+
list)
|
520
136
|
|
521
|
-
if index == (list.size - 1) then
|
137
|
+
if index.nil? or index == (list.size - 1) then
|
522
138
|
raise RemoteInstallationCancelled, "Installation of #{gem_name} cancelled."
|
523
139
|
end
|
524
140
|
|
141
|
+
if index == (list.size - 2) then
|
142
|
+
raise RemoteInstallationSkipped, "Installation of #{gem_name} skipped."
|
143
|
+
end
|
144
|
+
|
525
145
|
specs_n_sources[index]
|
526
146
|
end
|
527
147
|
|
528
148
|
def find_dependencies_not_installed(dependencies)
|
529
149
|
to_install = []
|
530
150
|
dependencies.each do |dependency|
|
531
|
-
|
532
|
-
|
533
|
-
|
151
|
+
srcindex = Gem::SourceIndex.from_installed_gems
|
152
|
+
matches = srcindex.find_name(dependency.name, dependency.requirement_list)
|
153
|
+
to_install.push dependency if matches.empty?
|
534
154
|
end
|
535
155
|
to_install
|
536
156
|
end
|
@@ -544,27 +164,27 @@ module Gem
|
|
544
164
|
def install_dependencies(dependencies, force, install_dir)
|
545
165
|
return if @options[:ignore_dependencies]
|
546
166
|
installed_gems = []
|
547
|
-
dependencies.each do |
|
167
|
+
dependencies.each do |dep|
|
548
168
|
if @options[:include_dependencies] ||
|
549
|
-
|
550
|
-
remote_installer =
|
551
|
-
installed_gems << remote_installer.install(
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
169
|
+
ask_yes_no("Install required dependency #{dep.name}?", true)
|
170
|
+
remote_installer = RemoteInstaller.new @options
|
171
|
+
installed_gems << remote_installer.install(dep.name,
|
172
|
+
dep.version_requirements,
|
173
|
+
force, install_dir)
|
174
|
+
elsif force then
|
175
|
+
# ignore
|
556
176
|
else
|
557
|
-
raise DependencyError
|
177
|
+
raise DependencyError, "Required dependency #{dep.name} not installed"
|
558
178
|
end
|
559
179
|
end
|
560
180
|
installed_gems
|
561
181
|
end
|
562
182
|
|
563
183
|
def download_gem(destination_file, source, spec)
|
564
|
-
|
565
|
-
|
566
|
-
response =
|
567
|
-
write_gem_to_file
|
184
|
+
return if File.exist? destination_file
|
185
|
+
uri = source + "/gems/#{spec.full_name}.gem"
|
186
|
+
response = Gem::RemoteFetcher.fetcher.fetch_path uri
|
187
|
+
write_gem_to_file response, destination_file
|
568
188
|
end
|
569
189
|
|
570
190
|
def write_gem_to_file(body, destination_file)
|