summaryrefslogtreecommitdiff
path: root/lib/bundler/compact_index_client.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/compact_index_client.rb')
-rw-r--r--lib/bundler/compact_index_client.rb92
1 files changed, 92 insertions, 0 deletions
diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb
new file mode 100644
index 0000000000..6865e30dbc
--- /dev/null
+++ b/lib/bundler/compact_index_client.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+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