blob: 37e2ccced8e1d1d9293e6a02d846f4a79b8e9c4d (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
# frozen_string_literal: true
require "pathname"
require "set"
module Bundler
# The CompactIndexClient is responsible for fetching and parsing the compact index.
#
# The compact index is a set of caching optimized files that are used to fetch gem information.
# The files are:
# - names: a list of all gem names
# - versions: a list of all gem versions
# - info/[gem]: a list of all versions of a gem
#
# The client is instantiated with:
# - `directory`: the root directory where the cache files are stored.
# - `fetcher`: (optional) an object that responds to #call(uri_path, headers) and returns an http response.
# If the `fetcher` is not provided, the client will only read cached files from disk.
#
# The client is organized into:
# - `Updater`: updates the cached files on disk using the fetcher.
# - `Cache`: calls the updater, caches files, read and return them from disk
# - `Parser`: parses the compact index file data
# - `CacheFile`: a concurrency safe file reader/writer that verifies checksums
#
# The client is intended to optimize memory usage and performance.
# It is called 100s or 1000s of times, parsing files with hundreds of thousands of lines.
# It may be called concurrently without global interpreter lock in some Rubies.
# As a result, some methods may look more complex than necessary to save memory or time.
class CompactIndexClient
SUPPORTED_DIGESTS = { "sha-256" => :SHA256 }.freeze
DEBUG_MUTEX = Thread::Mutex.new
# info returns an Array of INFO Arrays. Each INFO Array has the following indices:
INFO_NAME = 0
INFO_VERSION = 1
INFO_PLATFORM = 2
INFO_DEPS = 3
INFO_REQS = 4
def self.debug
return unless ENV["DEBUG_COMPACT_INDEX"]
DEBUG_MUTEX.synchronize { warn("[#{self}] #{yield}") }
end
class Error < StandardError; end
require_relative "compact_index_client/cache"
require_relative "compact_index_client/cache_file"
require_relative "compact_index_client/parser"
require_relative "compact_index_client/updater"
def initialize(directory, fetcher = nil)
@cache = Cache.new(directory, fetcher)
@parser = Parser.new(@cache)
end
def names
Bundler::CompactIndexClient.debug { "names" }
@parser.names
end
def versions
Bundler::CompactIndexClient.debug { "versions" }
@parser.versions
end
def dependencies(names)
Bundler::CompactIndexClient.debug { "dependencies(#{names})" }
names.map {|name| info(name) }
end
def info(name)
Bundler::CompactIndexClient.debug { "info(#{name})" }
@parser.info(name)
end
def latest_version(name)
Bundler::CompactIndexClient.debug { "latest_version(#{name})" }
@parser.info(name).map {|d| Gem::Version.new(d[INFO_VERSION]) }.max
end
def available?
Bundler::CompactIndexClient.debug { "available?" }
@parser.available?
end
def reset!
Bundler::CompactIndexClient.debug { "reset!" }
@cache.reset!
end
end
end
|