376 lines
9.8 KiB
Ruby
Raw Normal View History

require "cxxstdlib"
require "ostruct"
require "options"
require "json"
require "development_tools"
require "extend/cachable"
# Inherit from OpenStruct to gain a generic initialization method that takes a
# hash and creates an attribute for each key and value. `Tab.new` probably
# should not be called directly, instead use one of the class methods like
2013-01-23 00:26:20 -06:00
# `Tab.create`.
class Tab < OpenStruct
extend Cachable
FILENAME = "INSTALL_RECEIPT.json".freeze
2013-01-23 00:26:20 -06:00
# Instantiates a Tab for a new installation of a formula.
def self.create(formula, compiler, stdlib)
build = formula.build
runtime_deps = formula.runtime_dependencies(undeclared: false)
attributes = {
2018-11-02 17:18:07 +00:00
"homebrew_version" => HOMEBREW_VERSION,
"used_options" => build.used_options.as_flags,
"unused_options" => build.unused_options.as_flags,
"tabfile" => formula.prefix/FILENAME,
"built_as_bottle" => build.bottle?,
"installed_as_dependency" => false,
2018-11-02 17:18:07 +00:00
"installed_on_request" => true,
"poured_from_bottle" => false,
"time" => Time.now.to_i,
"source_modified_time" => formula.source_modified_time.to_i,
"HEAD" => HOMEBREW_REPOSITORY.git_head,
"compiler" => compiler,
"stdlib" => stdlib,
"aliases" => formula.aliases,
"runtime_dependencies" => Tab.runtime_deps_hash(runtime_deps),
"source" => {
"path" => formula.specified_path.to_s,
"tap" => formula.tap&.name,
"spec" => formula.active_spec_sym.to_s,
2016-07-04 12:39:08 +03:00
"versions" => {
2018-11-02 17:18:07 +00:00
"stable" => formula.stable&.version.to_s,
"devel" => formula.devel&.version.to_s,
"head" => formula.head&.version.to_s,
2016-08-11 10:00:39 +02:00
"version_scheme" => formula.version_scheme,
},
},
}
new(attributes)
end
2016-09-07 23:17:19 +01:00
# Returns the Tab for an install receipt at `path`.
# Results are cached.
def self.from_file(path)
cache.fetch(path) { |p| cache[p] = from_file_content(File.read(p), p) }
2015-05-23 18:08:07 +08:00
end
2016-09-07 23:17:19 +01:00
# Like Tab.from_file, but bypass the cache.
def self.from_file_content(content, path)
attributes = begin
JSON.parse(content)
rescue JSON::ParserError => e
raise e, "Cannot parse #{path}: #{e}", e.backtrace
end
attributes["tabfile"] = path
2016-01-14 18:55:47 +08:00
attributes["source_modified_time"] ||= 0
attributes["source"] ||= {}
tapped_from = attributes["tapped_from"]
if !tapped_from.nil? && tapped_from != "path or URL"
attributes["source"]["tap"] = attributes.delete("tapped_from")
end
if attributes["source"]["tap"] == "mxcl/master" ||
attributes["source"]["tap"] == "Homebrew/homebrew"
attributes["source"]["tap"] = "homebrew/core"
end
2015-07-28 15:33:07 +08:00
if attributes["source"]["spec"].nil?
version = PkgVersion.parse path.to_s.split("/").second_to_last
2015-07-28 15:33:07 +08:00
if version.head?
attributes["source"]["spec"] = "head"
else
attributes["source"]["spec"] = "stable"
end
end
2016-07-04 12:39:08 +03:00
if attributes["source"]["versions"].nil?
attributes["source"]["versions"] = {
2018-11-02 17:18:07 +00:00
"stable" => nil,
"devel" => nil,
"head" => nil,
2016-08-11 10:00:39 +02:00
"version_scheme" => 0,
2016-07-04 12:39:08 +03:00
}
end
2014-06-29 23:15:13 -05:00
new(attributes)
end
def self.for_keg(keg)
2017-06-01 16:06:51 +02:00
path = keg/FILENAME
tab = if path.exist?
2014-06-29 22:26:14 -05:00
from_file(path)
else
2015-02-21 12:15:39 -05:00
empty
end
tab["tabfile"] = path
tab
end
2016-09-07 23:17:19 +01:00
# Returns a tab for the named formula's installation,
# or a fake one if the formula is not installed.
def self.for_name(name)
for_formula(Formulary.factory(name))
end
def self.remap_deprecated_options(deprecated_options, options)
deprecated_options.each do |deprecated_option|
option = options.find { |o| o.name == deprecated_option.old }
next unless option
2018-09-17 02:45:00 +02:00
options -= [option]
options << Option.new(deprecated_option.current, option.description)
end
options
end
# Returns a Tab for an already installed formula,
# or a fake one if the formula is not installed.
def self.for_formula(f)
paths = []
paths << f.opt_prefix.resolved_path if f.opt_prefix.symlink? && f.opt_prefix.directory?
paths << f.linked_keg.resolved_path if f.linked_keg.symlink? && f.linked_keg.directory?
2015-11-29 15:40:45 +08:00
if (dirs = f.installed_prefixes).length == 1
paths << dirs.first
end
paths << f.installed_prefix
2017-06-01 16:06:51 +02:00
path = paths.map { |pn| pn/FILENAME }.find(&:file?)
if path
tab = from_file(path)
used_options = remap_deprecated_options(f.deprecated_options, tab.used_options)
tab.used_options = used_options.as_flags
else
# Formula is not installed. Return a fake tab.
2015-02-21 12:15:39 -05:00
tab = empty
tab.unused_options = f.options.as_flags
2015-12-06 22:33:41 +08:00
tab.source = {
2018-11-02 17:18:07 +00:00
"path" => f.specified_path.to_s,
"tap" => f.tap&.name,
"spec" => f.active_spec_sym.to_s,
"versions" => {
2018-11-02 17:18:07 +00:00
"stable" => f.stable&.version.to_s,
"devel" => f.devel&.version.to_s,
"head" => f.head&.version.to_s,
2016-08-11 10:00:39 +02:00
"version_scheme" => f.version_scheme,
},
2015-12-06 22:33:41 +08:00
}
end
2015-02-21 12:15:39 -05:00
tab
end
2015-02-21 12:15:39 -05:00
def self.empty
attributes = {
2018-11-02 17:18:07 +00:00
"homebrew_version" => HOMEBREW_VERSION,
"used_options" => [],
"unused_options" => [],
"built_as_bottle" => false,
"installed_as_dependency" => false,
2018-11-02 17:18:07 +00:00
"installed_on_request" => true,
"poured_from_bottle" => false,
"time" => nil,
"source_modified_time" => 0,
"HEAD" => nil,
"stdlib" => nil,
"compiler" => DevelopmentTools.default_compiler,
"aliases" => [],
"runtime_dependencies" => nil,
"source" => {
"path" => nil,
"tap" => nil,
"spec" => "stable",
2016-07-04 12:39:08 +03:00
"versions" => {
2018-11-02 17:18:07 +00:00
"stable" => nil,
"devel" => nil,
"head" => nil,
2016-08-11 10:00:39 +02:00
"version_scheme" => 0,
},
},
}
new(attributes)
end
def self.runtime_deps_hash(deps)
deps.map do |dep|
f = dep.to_formula
{ "full_name" => f.full_name, "version" => f.version.to_s }
end
end
def any_args_or_options?
!used_options.empty? || !unused_options.empty?
end
def with?(val)
option_names = val.respond_to?(:option_names) ? val.option_names : [val]
option_names.any? do |name|
include?("with-#{name}") || unused_options.include?("without-#{name}")
end
end
def without?(val)
!with?(val)
2014-07-30 21:04:17 -05:00
end
def include?(opt)
used_options.include? opt
end
def universal?
include?("universal")
end
def cxx11?
include?("c++11")
end
def head?
spec == :head
end
def devel?
spec == :devel
end
def stable?
spec == :stable
end
def used_options
Options.create(super)
end
def unused_options
Options.create(super)
end
def compiler
super || DevelopmentTools.default_compiler
end
def parsed_homebrew_version
return Version::NULL if homebrew_version.nil?
2018-09-17 02:45:00 +02:00
Version.new(homebrew_version)
end
def runtime_dependencies
# Homebrew versions prior to 1.1.6 generated incorrect runtime dependency
# lists.
super unless parsed_homebrew_version < "1.1.6"
end
def cxxstdlib
# Older tabs won't have these values, so provide sensible defaults
lib = stdlib.to_sym if stdlib
CxxStdlib.create(lib, compiler.to_sym)
end
2014-10-15 00:52:57 -05:00
def build_bottle?
built_as_bottle && !poured_from_bottle
end
def bottle?
built_as_bottle
end
def tap
2015-12-06 22:33:41 +08:00
tap_name = source["tap"]
Tap.fetch(tap_name) if tap_name
end
2015-05-27 09:57:41 +01:00
def tap=(tap)
2015-12-06 22:33:41 +08:00
tap_name = tap.respond_to?(:name) ? tap.name : tap
source["tap"] = tap_name
2015-05-27 09:57:41 +01:00
end
2015-07-28 15:33:07 +08:00
def spec
source["spec"].to_sym
end
2016-07-04 12:39:08 +03:00
def versions
source["versions"]
end
def stable_version
Version.create(versions["stable"]) if versions["stable"]
end
def devel_version
Version.create(versions["devel"]) if versions["devel"]
end
def head_version
Version.create(versions["head"]) if versions["head"]
end
2016-08-11 10:00:39 +02:00
def version_scheme
versions["version_scheme"] || 0
end
2016-01-14 18:55:47 +08:00
def source_modified_time
Time.at(super)
end
2019-03-18 15:04:37 +00:00
def to_json(options = nil)
attributes = {
2018-11-02 17:18:07 +00:00
"homebrew_version" => homebrew_version,
"used_options" => used_options.as_flags,
"unused_options" => unused_options.as_flags,
"built_as_bottle" => built_as_bottle,
"poured_from_bottle" => poured_from_bottle,
"installed_as_dependency" => installed_as_dependency,
2018-11-02 17:18:07 +00:00
"installed_on_request" => installed_on_request,
"changed_files" => changed_files&.map(&:to_s),
"time" => time,
"source_modified_time" => source_modified_time.to_i,
"HEAD" => self.HEAD,
"stdlib" => (stdlib&.to_s),
"compiler" => (compiler&.to_s),
"aliases" => aliases,
"runtime_dependencies" => runtime_dependencies,
"source" => source,
}
2019-03-18 15:04:37 +00:00
JSON.generate(attributes, options)
end
def write
# If this is a new installation, the cache of installed formulae
# will no longer be valid.
Formula.clear_installed_formulae_cache unless tabfile.exist?
self.class.cache[tabfile] = self
2014-03-22 11:13:33 -05:00
tabfile.atomic_write(to_json)
end
def to_s
s = []
if poured_from_bottle
s << "Poured from bottle"
else
s << "Built from source"
end
2016-09-23 11:01:40 +02:00
s << Time.at(time).strftime("on %Y-%m-%d at %H:%M:%S") if time
unless used_options.empty?
s << "with:"
s << used_options.to_a.join(" ")
end
s.join(" ")
end
end