208 lines
5.1 KiB
Ruby
208 lines
5.1 KiB
Ruby
require "dbm"
|
|
require "json"
|
|
|
|
#
|
|
# `DatabaseCache` is a class acting as an interface to a persistent storage
|
|
# mechanism residing in the `HOMEBREW_CACHE`
|
|
#
|
|
class DatabaseCache
|
|
# Name of the database cache file located at <HOMEBREW_CACHE>/<name>.db
|
|
#
|
|
# @return [String]
|
|
attr_accessor :name
|
|
|
|
# Instantiates new `DatabaseCache` object
|
|
#
|
|
# @param [String] name
|
|
# @return [nil]
|
|
def initialize(name)
|
|
@name = name
|
|
end
|
|
|
|
# Memoized `DBM` database object with on-disk database located in the
|
|
# `HOMEBREW_CACHE`
|
|
#
|
|
# @return [DBM] db
|
|
def db
|
|
@db ||= DBM.open("#{HOMEBREW_CACHE}/#{name}", 0666, DBM::WRCREAT)
|
|
end
|
|
|
|
# Close the `DBM` database object after usage
|
|
#
|
|
# @return [nil]
|
|
def close
|
|
db.close
|
|
end
|
|
end
|
|
|
|
#
|
|
# `CacheStore` is an abstract base class which provides methods to mutate and
|
|
# fetch data from a persistent storage mechanism
|
|
#
|
|
# @abstract
|
|
#
|
|
class CacheStore
|
|
# Instantiates a new `CacheStore` class
|
|
#
|
|
# @param [DatabaseCache] database_cache
|
|
# @return [nil]
|
|
def initialize(database_cache)
|
|
@db = database_cache.db
|
|
end
|
|
|
|
# Inserts new values or updates existing cached values to persistent storage
|
|
# mechanism
|
|
#
|
|
# @abstract
|
|
# @param [Any]
|
|
# @return [nil]
|
|
def update!(*)
|
|
raise NotImplementedError
|
|
end
|
|
|
|
# Fetches cached values in persistent storage according to the type of data
|
|
# stored
|
|
#
|
|
# @abstract
|
|
# @param [Any]
|
|
# @return [Any]
|
|
def fetch(*)
|
|
raise NotImplementedError
|
|
end
|
|
|
|
# Deletes data from the cache based on a condition defined in a concrete class
|
|
#
|
|
# @abstract
|
|
# @return [nil]
|
|
def flush_cache!
|
|
raise NotImplementedError
|
|
end
|
|
|
|
protected
|
|
|
|
# A class instance providing access to the `DBM` database object
|
|
#
|
|
# @return [DBM]
|
|
attr_reader :db
|
|
end
|
|
|
|
#
|
|
# `LinkageStore` is a concrete class providing methods to fetch and mutate
|
|
# linkage-specific data used by the `brew linkage` command
|
|
#
|
|
# If the cache hasn't changed, don't do extra processing in `LinkageChecker`.
|
|
# Instead, just fetch the data stored in the cache
|
|
#
|
|
class LinkageStore < CacheStore
|
|
# Types of dylibs of the form (label -> array)
|
|
HASH_LINKAGE_TYPES = %w[brewed_dylibs reverse_links].freeze
|
|
|
|
# The keg name for the `LinkageChecker` class
|
|
#
|
|
# @return [String]
|
|
attr_reader :key
|
|
|
|
# Initializes new `LinkageStore` class
|
|
#
|
|
# @param [String] keg_name
|
|
# @param [DatabaseCache] database_cache
|
|
# @return [nil]
|
|
def initialize(keg_name, database_cache)
|
|
@key = keg_name
|
|
super(database_cache)
|
|
end
|
|
|
|
# Inserts new values or updates existing cached values to persistent storage
|
|
# mechanism according to the type of data
|
|
#
|
|
# @param [Hash] path_values
|
|
# @param [Hash] hash_values
|
|
# @return [nil]
|
|
def update!(
|
|
path_values: {
|
|
"system_dylibs" => %w[], "variable_dylibs" => %w[], "broken_dylibs" => %w[],
|
|
"indirect_deps" => %w[], "undeclared_deps" => %w[], "unnecessary_deps" => %w[]
|
|
},
|
|
hash_values: {
|
|
"brewed_dylibs" => {}, "reverse_links" => {}
|
|
}
|
|
)
|
|
db[key] = {
|
|
"path_values" => format_path_values(path_values),
|
|
"hash_values" => format_hash_values(hash_values),
|
|
}
|
|
end
|
|
|
|
# Fetches cached values in persistent storage according to the type of data
|
|
# stored
|
|
#
|
|
# @param [String] type
|
|
# @return [Any]
|
|
def fetch(type:)
|
|
if HASH_LINKAGE_TYPES.include?(type)
|
|
fetch_hash_values(type: type)
|
|
else
|
|
fetch_path_values(type: type)
|
|
end
|
|
end
|
|
|
|
# A condition for where to flush the cache
|
|
#
|
|
# @return [String]
|
|
def flush_cache!
|
|
db.delete(key)
|
|
end
|
|
|
|
private
|
|
|
|
# Fetches a subset of paths where the name = `key`
|
|
#
|
|
# @param [String] type
|
|
# @return [Array[String]]
|
|
def fetch_path_values(type:)
|
|
return [] unless db.key?(key) && !db[key].nil?
|
|
string_to_hash(db[key])["path_values"][type]
|
|
end
|
|
|
|
# Fetches a subset of paths and labels where the name = `key`. Formats said
|
|
# paths/labels into `key => [value]` syntax expected by `LinkageChecker`
|
|
#
|
|
# @param [String] type
|
|
# @return [Hash]
|
|
def fetch_hash_values(type:)
|
|
return {} unless db.key?(key) && !db[key].nil?
|
|
string_to_hash(db[key])["hash_values"][type]
|
|
end
|
|
|
|
# Parses `DBM` stored `String` into ruby `Hash`
|
|
#
|
|
# @param [String] string
|
|
# @return [Hash]
|
|
def string_to_hash(string)
|
|
JSON.parse(string.gsub("=>", ":"))
|
|
end
|
|
|
|
# Formats the linkage data for `path_values` into a kind which can be parsed
|
|
# by the `string_to_hash` method. Converts ruby `Set`s to `Array`s.
|
|
#
|
|
# @param [Hash(String, Set(String))] hash
|
|
# @return [Hash(String, Array(String))]
|
|
def format_path_values(hash)
|
|
hash.each_with_object({}) { |(k, v), h| h[k] = v.to_a }
|
|
end
|
|
|
|
# Formats the linkage data for `hash_values` into a kind which can be parsed
|
|
# by the `string_to_hash` method. Converts ruby `Set`s to `Array`s, and
|
|
# converts ruby `Pathname`s to `String`s
|
|
#
|
|
# @param [Hash(String, Set(Pathname))] hash
|
|
# @return [Hash(String, Array(String))]
|
|
def format_hash_values(hash)
|
|
hash.each_with_object({}) do |(outer_key, outer_values), outer_hash|
|
|
outer_hash[outer_key] = outer_values.each_with_object({}) do |(k, v), h|
|
|
h[k] = v.to_a.map(&:to_s)
|
|
end
|
|
end
|
|
end
|
|
end
|