brew/Library/Homebrew/formula.rb

4440 lines
140 KiB
Ruby
Raw Normal View History

2025-02-16 22:20:37 -08:00
# typed: strict
# frozen_string_literal: true
require "cache_store"
2021-06-19 00:14:33 +01:00
require "did_you_mean"
require "formula_support"
2017-05-22 03:23:50 +02:00
require "lock_file"
require "formula_pin"
require "hardware"
require "utils"
2016-04-25 17:57:51 +01:00
require "utils/bottles"
2024-07-15 10:09:12 -04:00
require "utils/gzip"
2024-07-15 21:37:31 -07:00
require "utils/inreplace"
2020-03-27 13:04:10 +00:00
require "utils/shebang"
2016-09-05 14:45:35 +10:00
require "utils/shell"
2024-07-15 09:50:14 -04:00
require "utils/git_repository"
require "build_environment"
require "build_options"
require "formulary"
require "software_spec"
require "bottle"
require "pour_bottle_check"
require "head_software_spec"
require "bottle_specification"
2020-03-16 01:37:49 +05:30
require "livecheck"
2020-12-11 23:14:50 +01:00
require "service"
require "install_renamed"
require "pkg_version"
require "keg"
require "migrator"
require "linkage_checker"
require "extend/ENV"
require "language/java"
require "language/python"
2018-03-25 12:43:56 +01:00
require "tab"
require "mktemp"
require "find"
require "utils/spdx"
require "extend/on_system"
2021-08-06 02:30:44 -04:00
require "api"
require "extend/api_hashable"
2012-04-05 21:09:24 -05:00
# A formula provides instructions and metadata for Homebrew to install a piece
# of software. Every Homebrew formula is a {Formula}.
# All subclasses of {Formula} (and all Ruby classes) have to be named
# `UpperCase` and `not-use-dashes`.
# A formula specified in `this-formula.rb` should have a class named
# `ThisFormula`. Homebrew does enforce that the name of the file and the class
# correspond.
# Make sure you check with `brew search` that the name is free!
# @abstract
# @see SharedEnvExtension
# @see Pathname
# @see https://www.rubydoc.info/stdlib/fileutils FileUtils
# @see https://docs.brew.sh/Formula-Cookbook Formula Cookbook
2020-11-03 12:39:26 -05:00
# @see https://rubystyle.guide Ruby Style Guide
#
# ### Example
#
# ```ruby
# class Wget < Formula
# homepage "https://www.gnu.org/software/wget/"
# url "https://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz"
# sha256 "52126be8cf1bddd7536886e74c053ad7d0ed2aa89b4b630f76785bac21695fcd"
#
# def install
# system "./configure", "--prefix=#{prefix}"
# system "make", "install"
# end
# end
# ```
class Formula
include FileUtils
2020-03-27 13:04:10 +00:00
include Utils::Shebang
2016-09-05 14:45:35 +10:00
include Utils::Shell
include Context
include OnSystem::MacOSAndLinux
include Homebrew::Livecheck::Constants
extend Forwardable
extend Cachable
extend APIHashable
2024-11-21 18:10:20 -08:00
extend T::Helpers
abstract!
SUPPORTED_NETWORK_ACCESS_PHASES = [:build, :test, :postinstall].freeze
private_constant :SUPPORTED_NETWORK_ACCESS_PHASES
DEFAULT_NETWORK_ACCESS_ALLOWED = true
private_constant :DEFAULT_NETWORK_ACCESS_ALLOWED
# The name of this {Formula}.
# e.g. `this-formula`
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-24 14:01:53 -07:00
sig { returns(String) }
attr_reader :name
# The path to the alias that was used to identify this {Formula}.
# e.g. `/usr/local/Library/Taps/homebrew/homebrew-core/Aliases/another-name-for-this-formula`
sig { returns(T.nilable(Pathname)) }
attr_reader :alias_path
# The name of the alias that was used to identify this {Formula}.
# e.g. `another-name-for-this-formula`
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(String)) }
attr_reader :alias_name
2015-05-27 20:30:43 +08:00
# The fully-qualified name of this {Formula}.
# For core formulae it's the same as {#name}.
2015-05-27 20:30:43 +08:00
# e.g. `homebrew/tap-name/this-formula`
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-24 14:01:53 -07:00
sig { returns(String) }
2015-05-27 20:30:43 +08:00
attr_reader :full_name
# The fully-qualified alias referring to this {Formula}.
# For core formulae it's the same as {#alias_name}.
# e.g. `homebrew/tap-name/another-name-for-this-formula`
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(String)) }
attr_reader :full_alias_name
# The full path to this {Formula}.
# e.g. `/usr/local/Library/Taps/homebrew/homebrew-core/Formula/t/this-formula.rb`
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-24 14:01:53 -07:00
sig { returns(Pathname) }
attr_reader :path
2015-12-06 22:16:08 +08:00
# The {Tap} instance associated with this {Formula}.
# If it's `nil`, then this formula is loaded from a path or URL.
2024-04-22 21:05:48 +02:00
#
# @api internal
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(Tap)) }
2015-12-06 22:16:08 +08:00
attr_reader :tap
# The stable (and default) {SoftwareSpec} for this {Formula}.
2014-12-26 16:07:32 -05:00
# This contains all the attributes (e.g. URL, checksum) that apply to the
# stable version of this formula.
2024-04-22 21:05:48 +02:00
#
# @api internal
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(SoftwareSpec)) }
attr_reader :stable
# The HEAD {SoftwareSpec} for this {Formula}.
# Installed when using `brew install --HEAD`.
# This is always installed with the version `HEAD` and taken from the latest
# commit in the version control system.
# `nil` if there is no HEAD version.
2024-04-22 21:05:48 +02:00
#
# @see #stable
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(SoftwareSpec)) }
attr_reader :head
# The currently active {SoftwareSpec}.
2015-01-04 15:33:25 -05:00
# @see #determine_active_spec
2020-11-24 15:46:47 +01:00
sig { returns(SoftwareSpec) }
attr_reader :active_spec
2015-01-04 15:33:25 -05:00
protected :active_spec
2015-07-28 15:10:40 +08:00
# A symbol to indicate currently active {SoftwareSpec}.
# It's either :stable or :head
2015-07-28 15:10:40 +08:00
# @see #active_spec
2023-07-24 14:01:53 -07:00
sig { returns(Symbol) }
2015-07-28 15:10:40 +08:00
attr_reader :active_spec_sym
# most recent modified time for source files
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(Time)) }
attr_reader :source_modified_time
# Used for creating new Homebrew versions of software without new upstream
# versions.
# @see .revision=
2023-07-24 14:01:53 -07:00
sig { returns(Integer) }
attr_reader :revision
# Used to change version schemes for packages.
2020-11-05 15:19:56 -05:00
# @see .version_scheme=
2023-07-24 14:01:53 -07:00
sig { returns(Integer) }
2016-08-11 09:54:47 +02:00
attr_reader :version_scheme
# The current working directory during builds.
# Will only be non-`nil` inside {#install}.
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(Pathname)) }
attr_reader :buildpath
# The current working directory during tests.
# Will only be non-`nil` inside {.test}.
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(Pathname)) }
attr_reader :testpath
# When installing a bottle (binary package) from a local path this will be
# set to the full path to the bottle tarball. If not, it will be `nil`.
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(Pathname)) }
attr_accessor :local_bottle_path
2016-05-27 01:53:08 -04:00
# When performing a build, test, or other loggable action, indicates which
# log file location to use.
2023-07-24 14:01:53 -07:00
sig { returns(T.nilable(String)) }
2016-05-27 01:53:08 -04:00
attr_reader :active_log_type
2023-07-24 14:01:53 -07:00
# The {BuildOptions} or {Tab} for this {Formula}. Lists the arguments passed
# and any {.option}s in the {Formula}. Note that these may differ at
# different times during the installation of a {Formula}. This is annoying
# but the result of state that we're trying to eliminate.
sig { returns(T.any(BuildOptions, Tab)) }
attr_reader :build
# Whether this formula should be considered outdated
# if the target of the alias it was installed with has since changed.
# Defaults to true.
2023-07-24 14:01:53 -07:00
sig { returns(T::Boolean) }
attr_accessor :follow_installed_alias
2016-09-23 18:13:48 +02:00
alias follow_installed_alias? follow_installed_alias
# Whether or not to force the use of a bottle.
2023-07-24 14:01:53 -07:00
sig { returns(T::Boolean) }
attr_accessor :force_bottle
2023-07-24 14:12:47 -07:00
sig {
params(name: String, path: Pathname, spec: Symbol, alias_path: T.nilable(Pathname),
2023-07-24 14:12:47 -07:00
tap: T.nilable(Tap), force_bottle: T::Boolean).void
}
def initialize(name, path, spec, alias_path: nil, tap: nil, force_bottle: false)
# Only allow instances of subclasses. The base class does not hold any spec information (URLs etc).
raise "Do not call `Formula.new' directly without a subclass." unless self.class < Formula
# Stop any subsequent modification of a formula's definition.
# Changes do not propagate to existing instances of formulae.
# Now that we have an instance, it's too late to make any changes to the class-level definition.
self.class.freeze
2025-02-16 22:20:37 -08:00
@name = T.let(name, String)
@unresolved_path = T.let(path, Pathname)
@path = T.let(path.resolved_path, Pathname)
@alias_path = T.let(alias_path, T.nilable(Pathname))
@alias_name = T.let((File.basename(alias_path) if alias_path), T.nilable(String))
@revision = T.let(self.class.revision || 0, Integer)
@version_scheme = T.let(self.class.version_scheme || 0, Integer)
@head = T.let(nil, T.nilable(SoftwareSpec))
@stable = T.let(nil, T.nilable(SoftwareSpec))
2025-02-16 22:20:37 -08:00
@force_bottle = T.let(force_bottle, T::Boolean)
2025-02-16 22:20:37 -08:00
@tap = T.let(tap, T.nilable(Tap))
@tap ||= if path == Formulary.core_path(name)
CoreTap.instance
else
Tap.from_path(path)
2015-05-27 20:30:43 +08:00
end
2025-02-16 22:20:37 -08:00
@full_name = T.let(T.must(full_name_with_optional_tap(name)), String)
@full_alias_name = T.let(full_name_with_optional_tap(@alias_name), T.nilable(String))
self.class.spec_syms.each do |sym|
spec_eval sym
end
2012-04-05 21:09:24 -05:00
2025-02-16 22:20:37 -08:00
@active_spec = T.let(determine_active_spec(spec), SoftwareSpec)
@active_spec_sym = T.let(head? ? :head : :stable, Symbol)
2014-12-26 16:08:12 -05:00
validate_attributes!
2025-02-16 22:20:37 -08:00
@build = T.let(active_spec.build, T.any(BuildOptions, Tab))
@pin = T.let(FormulaPin.new(self), FormulaPin)
@follow_installed_alias = T.let(true, T::Boolean)
@prefix_returns_versioned_prefix = T.let(false, T.nilable(T::Boolean))
@oldname_locks = T.let([], T::Array[FormulaLock])
@on_system_blocks_exist = T.let(false, T::Boolean)
2012-04-05 21:09:24 -05:00
end
2025-02-16 22:20:37 -08:00
sig { params(spec_sym: Symbol).void }
2016-09-24 17:59:14 +02:00
def active_spec=(spec_sym)
2015-07-30 16:25:21 +08:00
spec = send(spec_sym)
raise FormulaSpecificationError, "#{spec_sym} spec is not available for #{full_name}" unless spec
2018-09-17 02:45:00 +02:00
old_spec_sym = @active_spec_sym
2015-07-30 16:25:21 +08:00
@active_spec = spec
@active_spec_sym = spec_sym
validate_attributes!
@build = active_spec.build
return if spec_sym == old_spec_sym
Dependency.clear_cache
Requirement.clear_cache
end
2025-02-16 22:20:37 -08:00
sig { params(build_options: T.any(BuildOptions, Tab)).void }
def build=(build_options)
old_options = @build
@build = build_options
return if old_options.used_options == build_options.used_options &&
old_options.unused_options == build_options.unused_options
Dependency.clear_cache
Requirement.clear_cache
2015-07-30 16:25:21 +08:00
end
private
# Allow full name logic to be re-used between names, aliases and installed aliases.
2025-02-16 22:20:37 -08:00
sig { params(name: T.nilable(String)).returns(T.nilable(String)) }
2016-09-24 17:59:14 +02:00
def full_name_with_optional_tap(name)
if name.nil? || @tap.nil? || @tap.core_tap?
name
else
"#{@tap}/#{name}"
end
end
2025-02-16 22:20:37 -08:00
sig { params(name: T.any(String, Symbol)).void }
2016-09-24 17:59:14 +02:00
def spec_eval(name)
spec = self.class.send(name).dup
2016-09-23 22:02:23 +02:00
return unless spec.url
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
spec.owner = self
add_global_deps_to_spec(spec)
2023-12-18 09:34:01 -08:00
instance_variable_set(:"@#{name}", spec)
end
sig { params(spec: SoftwareSpec).void }
def add_global_deps_to_spec(spec); end
2025-02-16 22:20:37 -08:00
sig { params(requested: T.any(String, Symbol)).returns(SoftwareSpec) }
2014-06-19 21:35:47 -05:00
def determine_active_spec(requested)
spec = send(requested) || stable || head
2024-09-11 10:45:28 -04:00
spec || raise(FormulaSpecificationError, "#{full_name}: formula requires at least a URL")
end
2025-02-16 22:20:37 -08:00
sig { void }
2014-12-26 16:08:12 -05:00
def validate_attributes!
2024-01-01 17:46:48 +00:00
if name.blank? || name.match?(/\s/) || !Utils.safe_filename?(name)
raise FormulaValidationError.new(full_name, :name, name)
end
2014-12-26 16:08:12 -05:00
2014-12-26 16:07:32 -05:00
url = active_spec.url
raise FormulaValidationError.new(full_name, :url, url) if url.blank? || url.match?(/\s/)
2014-12-26 16:08:12 -05:00
val = version.respond_to?(:to_str) ? version.to_str : version
2024-01-01 17:46:48 +00:00
return if val.present? && !val.match?(/\s/) && Utils.safe_filename?(val)
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
raise FormulaValidationError.new(full_name, :version, val)
end
public
# The alias path that was used to install this formula, if it exists.
# Can differ from {#alias_path}, which is the alias used to find the formula,
2016-09-14 23:18:55 +01:00
# and is specified to this instance.
sig { returns(T.nilable(Pathname)) }
2016-09-14 23:18:55 +01:00
def installed_alias_path
2023-07-24 22:08:17 -07:00
build_tab = build
path = build_tab.source["path"] if build_tab.is_a?(Tab)
2020-11-16 22:18:56 +01:00
return unless path&.match?(%r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases}o)
path = Pathname(path)
return unless path.symlink?
2018-09-17 02:45:00 +02:00
path
2016-09-14 23:18:55 +01:00
end
2020-10-20 12:03:48 +02:00
sig { returns(T.nilable(String)) }
2025-02-17 11:37:57 -08:00
def installed_alias_name = installed_alias_path&.basename&.to_s
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(String)) }
2025-02-17 11:37:57 -08:00
def full_installed_alias_name = full_name_with_optional_tap(installed_alias_name)
2016-09-14 23:18:55 +01:00
# The path that was specified to find this formula.
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(Pathname)) }
2016-09-05 22:13:55 +01:00
def specified_path
return Homebrew::API::Formula.cached_json_file_path if loaded_from_api?
return alias_path if alias_path&.exist?
return @unresolved_path if @unresolved_path.exist?
return local_bottle_path if local_bottle_path.presence&.exist?
alias_path || @unresolved_path
2016-09-05 22:13:55 +01:00
end
# The name specified to find this formula.
2025-02-16 22:20:37 -08:00
sig { returns(String) }
def specified_name
alias_name || name
end
# The name (including tap) specified to find this formula.
2025-02-16 22:20:37 -08:00
sig { returns(String) }
def full_specified_name
full_alias_name || full_name
end
# The name specified to install this formula.
2025-02-16 22:20:37 -08:00
sig { returns(String) }
def installed_specified_name
installed_alias_name || name
end
# The name (including tap) specified to install this formula.
2025-02-16 22:20:37 -08:00
sig { returns(String) }
def full_installed_specified_name
full_installed_alias_name || full_name
end
# Is the currently active {SoftwareSpec} a {#stable} build?
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def stable?
active_spec == stable
end
# Is the currently active {SoftwareSpec} a {#head} build?
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def head?
active_spec == head
end
# Is this formula HEAD-only?
sig { returns(T::Boolean) }
def head_only?
!!head && !stable
end
# Stop RuboCop from erroneously indenting hash target
2019-11-28 15:10:50 +00:00
delegate [ # rubocop:disable Layout/HashAlignment
:bottle_defined?,
:bottle_tag?,
:bottled?,
:bottle_specification,
2018-10-01 10:19:55 +02:00
:downloader,
2018-11-02 17:27:24 +00:00
] => :active_spec
# The Bottle object for the currently active {SoftwareSpec}.
2020-10-20 12:03:48 +02:00
sig { returns(T.nilable(Bottle)) }
def bottle
2025-02-16 22:20:37 -08:00
@bottle ||= T.let(Bottle.new(self, bottle_specification), T.nilable(Bottle)) if bottled?
end
2021-07-10 21:17:33 +02:00
# The Bottle object for given tag.
sig { params(tag: T.nilable(Utils::Bottles::Tag)).returns(T.nilable(Bottle)) }
2021-07-10 21:17:33 +02:00
def bottle_for_tag(tag = nil)
Bottle.new(self, bottle_specification, tag) if bottled?(tag)
2021-07-10 21:17:33 +02:00
end
2015-05-19 13:06:06 -04:00
# The description of the software.
2020-11-05 15:19:56 -05:00
# @!method desc
2025-02-24 09:56:33 -08:00
# @see .desc
delegate desc: :"self.class"
2015-05-19 13:06:06 -04:00
2020-06-10 12:29:25 -04:00
# The SPDX ID of the software license.
2020-11-05 15:19:56 -05:00
# @!method license
2025-02-24 09:56:33 -08:00
# @see .license
2020-06-10 12:29:25 -04:00
delegate license: :"self.class"
# The homepage for the software.
2020-11-05 15:19:56 -05:00
# @!method homepage
2025-02-24 09:56:33 -08:00
# @see .homepage
delegate homepage: :"self.class"
2020-03-16 01:37:49 +05:30
# The livecheck specification for the software.
2020-11-05 15:19:56 -05:00
# @!method livecheck
2025-02-24 09:56:33 -08:00
# @see .livecheck
2020-03-16 01:37:49 +05:30
delegate livecheck: :"self.class"
# Is a livecheck specification defined for the software?
# @!method livecheck_defined?
# @see .livecheck_defined?
delegate livecheck_defined?: :"self.class"
2020-03-16 01:37:49 +05:30
# Is a livecheck specification defined for the software?
2020-11-05 15:19:56 -05:00
# @!method livecheckable?
2020-03-16 01:37:49 +05:30
# @see .livecheckable?
delegate livecheckable?: :"self.class"
2020-12-11 23:14:50 +01:00
# Is a service specification defined for the software?
# @!method service?
# @see .service?
delegate service?: :"self.class"
# The version for the currently active {SoftwareSpec}.
# The version is autodetected from the URL and/or tag so only needs to be
# declared if it cannot be autodetected correctly.
2020-11-05 15:19:56 -05:00
# @!method version
# @see .version
delegate version: :active_spec
# Stop RuboCop from erroneously indenting hash target
delegate [ # rubocop:disable Layout/HashAlignment
:allow_network_access!,
:deny_network_access!,
:network_access_allowed?,
] => :"self.class"
2023-05-15 13:58:33 +02:00
# Whether this formula was loaded using the formulae.brew.sh API
# @!method loaded_from_api?
# @see .loaded_from_api?
delegate loaded_from_api?: :"self.class"
sig { void }
2016-07-13 10:11:59 +03:00
def update_head_version
return unless head?
2023-07-24 14:01:53 -07:00
head_spec = T.must(head)
return unless head_spec.downloader.is_a?(VCSDownloadStrategy)
return unless head_spec.downloader.cached_location.exist?
2016-07-13 10:11:59 +03:00
path = if ENV["HOMEBREW_ENV"]
ENV.fetch("PATH")
else
PATH.new(ORIGINAL_PATHS)
end
with_env(PATH: path) do
2023-07-24 14:01:53 -07:00
head_spec.version.update_commit(head_spec.downloader.last_commit)
end
2016-07-13 10:11:59 +03:00
end
# The {PkgVersion} for this formula with {version} and {#revision} information.
2020-10-20 12:03:48 +02:00
sig { returns(PkgVersion) }
2025-02-17 11:37:57 -08:00
def pkg_version = PkgVersion.new(version, revision)
# If this is a `@`-versioned formula.
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def versioned_formula? = name.include?("@")
# Returns any other `@`-versioned formulae names for any formula (including versioned formulae).
sig { returns(T::Array[String]) }
def versioned_formulae_names
versioned_names = if tap
name_prefix = name.gsub(/(@[\d.]+)?$/, "")
T.must(tap).prefix_to_versioned_formulae_names.fetch(name_prefix, [])
elsif path.exist?
Pathname.glob(path.to_s.gsub(/(@[\d.]+)?\.rb$/, "@*.rb"))
.map { |path| path.basename(".rb").to_s }
.sort
2023-07-24 14:01:53 -07:00
else
raise "Either tap or path is required to list versioned formulae"
end
versioned_names.reject do |versioned_name|
versioned_name == name
end
end
# Returns any `@`-versioned Formula objects for any Formula (including versioned formulae).
sig { returns(T::Array[Formula]) }
def versioned_formulae
versioned_formulae_names.filter_map do |name|
Formula[name]
rescue FormulaUnavailableError
nil
end.sort_by(&:version).reverse
end
# Whether this {Formula} is version-synced with other formulae.
sig { returns(T::Boolean) }
def synced_with_other_formulae?
return false if @tap.nil?
@tap.synced_versions_formulae.any? { |synced_formulae| synced_formulae.include?(name) }
end
# A named {Resource} for the currently active {SoftwareSpec}.
# Additional downloads can be defined as {#resource}s.
# {Resource#stage} will create a temporary directory and yield to a block.
#
# ### Example
#
# ```ruby
# resource("additional_files").stage { bin.install "my/extra/tool" }
# ```
2024-05-03 21:47:49 +02:00
#
# FIXME: This should not actually take a block. All resources should be defined
# at the top-level using {Formula.resource} instead
# (see https://github.com/Homebrew/brew/issues/17203#issuecomment-2093654431).
#
# @api public
sig {
params(name: String, klass: T.class_of(Resource), block: T.nilable(T.proc.bind(Resource).void))
.returns(T.nilable(Resource))
}
2024-09-05 17:19:07 +02:00
def resource(name = T.unsafe(nil), klass = T.unsafe(nil), &block)
if klass.nil?
active_spec.resource(*name, &block)
else
2024-09-05 17:20:20 +02:00
active_spec.resource(name, klass, &block)
2024-09-05 17:19:07 +02:00
end
end
2023-04-27 04:09:28 +01:00
# Old names for the formula.
2024-04-22 21:05:48 +02:00
#
# @api internal
sig { returns(T::Array[String]) }
2023-04-27 04:09:28 +01:00
def oldnames
2025-02-16 22:20:37 -08:00
@oldnames ||= T.let(
if (tap = self.tap)
Tap.tap_migration_oldnames(tap, name) + tap.formula_reverse_renames.fetch(name, [])
else
[]
end, T.nilable(T::Array[String])
)
end
# All aliases for the formula.
2024-04-22 21:05:48 +02:00
#
# @api internal
sig { returns(T::Array[String]) }
def aliases
2025-02-16 22:20:37 -08:00
@aliases ||= T.let(
if (tap = self.tap)
tap.alias_reverse_table.fetch(full_name, []).map { _1.split("/").fetch(-1) }
else
[]
end, T.nilable(T::Array[String])
)
end
# The {Resource}s for the currently active {SoftwareSpec}.
2020-11-05 15:19:56 -05:00
# @!method resources
def_delegator :"active_spec.resources", :values, :resources
# The {Dependency}s for the currently active {SoftwareSpec}.
2024-04-22 21:05:48 +02:00
#
# @api internal
delegate deps: :active_spec
2013-09-21 19:27:24 -05:00
# The declared {Dependency}s for the currently active {SoftwareSpec} (i.e. including those provided by macOS)
delegate declared_deps: :active_spec
# The {Requirement}s for the currently active {SoftwareSpec}.
delegate requirements: :active_spec
2013-09-21 19:27:24 -05:00
2014-12-26 16:07:32 -05:00
# The cached download for the currently active {SoftwareSpec}.
delegate cached_download: :active_spec
2014-02-21 00:41:07 -05:00
2014-12-26 16:07:32 -05:00
# Deletes the download for the currently active {SoftwareSpec}.
delegate clear_cache: :active_spec
2014-02-21 00:41:07 -05:00
# The list of patches for the currently active {SoftwareSpec}.
def_delegator :active_spec, :patches, :patchlist
2014-03-13 19:51:23 -05:00
# The options for the currently active {SoftwareSpec}.
delegate options: :active_spec
# The deprecated options for the currently active {SoftwareSpec}.
delegate deprecated_options: :active_spec
# The deprecated option flags for the currently active {SoftwareSpec}.
delegate deprecated_flags: :active_spec
2014-12-27 14:26:56 -05:00
# If a named option is defined for the currently active {SoftwareSpec}.
2020-11-05 15:19:56 -05:00
# @!method option_defined?
delegate option_defined?: :active_spec
2014-07-31 19:37:39 -05:00
# All the {.fails_with} for the currently active {SoftwareSpec}.
delegate compiler_failures: :active_spec
# If this {Formula} is installed.
# This is actually just a check for if the {#latest_installed_prefix} directory
# exists and is not empty.
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def latest_version_installed?
(dir = latest_installed_prefix).directory? && !dir.children.empty?
end
# If at least one version of {Formula} is installed.
#
# @api public
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def any_version_installed?
2024-06-22 13:31:50 -04:00
installed_prefixes.any? { |keg| (keg/AbstractTab::FILENAME).file? }
end
# The link status symlink directory for this {Formula}.
2015-01-04 14:25:59 -05:00
# You probably want {#opt_prefix} instead.
2024-04-22 21:05:48 +02:00
#
# @api internal
2025-02-16 22:20:37 -08:00
sig { returns(Pathname) }
def linked_keg
linked_keg = possible_names.map { |name| HOMEBREW_LINKED_KEGS/name }
.find(&:directory?)
return linked_keg if linked_keg.present?
HOMEBREW_LINKED_KEGS/name
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(PkgVersion)) }
def latest_head_version
head_versions = installed_prefixes.filter_map do |pn|
2016-07-13 10:11:59 +03:00
pn_pkgversion = PkgVersion.parse(pn.basename.to_s)
pn_pkgversion if pn_pkgversion.head?
end
2016-07-13 10:11:59 +03:00
head_versions.max_by do |pn_pkgversion|
[Keg.new(prefix(pn_pkgversion)).tab.source_modified_time, pn_pkgversion.revision]
2016-07-13 10:11:59 +03:00
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(Pathname)) }
def latest_head_prefix
head_version = latest_head_version
prefix(head_version) if head_version
end
2025-02-16 22:20:37 -08:00
sig { params(version: PkgVersion, fetch_head: T::Boolean).returns(T::Boolean) }
def head_version_outdated?(version, fetch_head: false)
tab = Tab.for_keg(prefix(version))
2016-08-11 10:00:39 +02:00
return true if tab.version_scheme < version_scheme
2023-07-24 14:01:53 -07:00
return true if stable && tab.stable_version && tab.stable_version < T.must(stable).version
return false unless fetch_head
return false unless head&.downloader.is_a?(VCSDownloadStrategy)
2023-07-24 14:01:53 -07:00
downloader = T.must(head).downloader
with_context quiet: true do
downloader.commit_outdated?(version.version.commit)
end
2016-07-13 10:11:59 +03:00
end
# The latest prefix for this formula. Checks for {#head} and then {#stable}'s {#prefix}
2025-02-16 22:20:37 -08:00
sig { returns(Pathname) }
def latest_installed_prefix
if head && (head_version = latest_head_version) && !head_version_outdated?(head_version)
2025-02-16 22:20:37 -08:00
T.must(latest_head_prefix)
2023-07-24 14:01:53 -07:00
elsif stable && (stable_prefix = prefix(PkgVersion.new(T.must(stable).version, revision))).directory?
stable_prefix
else
prefix
end
end
2014-03-05 20:51:37 -08:00
# The directory in the cellar that the formula is installed to.
# This directory points to {#opt_prefix} if it exists and if {#prefix} is not
# called from within the same formula's {#install} or {#post_install} methods.
# Otherwise, return the full path to the formula's versioned cellar.
sig { params(version: T.any(String, PkgVersion)).returns(Pathname) }
def prefix(version = pkg_version)
versioned_prefix = versioned_prefix(version)
version = PkgVersion.parse(version) if version.is_a?(String)
if !@prefix_returns_versioned_prefix && version == pkg_version &&
versioned_prefix.directory? && Keg.new(versioned_prefix).optlinked?
opt_prefix
else
versioned_prefix
end
end
# Is the formula linked?
2024-04-22 21:05:48 +02:00
#
# @api internal
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def linked? = linked_keg.symlink?
# Is the formula linked to `opt`?
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def optlinked? = opt_prefix.symlink?
# If a formula's linked keg points to the prefix.
2025-02-16 22:20:37 -08:00
sig { params(version: T.any(String, PkgVersion)).returns(T::Boolean) }
def prefix_linked?(version = pkg_version)
return false unless linked?
2018-09-17 02:45:00 +02:00
linked_keg.resolved_path == versioned_prefix(version)
end
# {PkgVersion} of the linked keg for the formula.
sig { returns(T.nilable(PkgVersion)) }
def linked_version
return unless linked?
2018-09-17 02:45:00 +02:00
Keg.for(linked_keg).version
end
2014-03-05 20:51:37 -08:00
# The parent of the prefix; the named directory in the cellar containing all
# installed versions of this software.
2020-10-20 12:03:48 +02:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def rack = HOMEBREW_CELLAR/name
2016-09-14 23:18:55 +01:00
# All currently installed prefix directories.
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Pathname]) }
def installed_prefixes
possible_names.map { |name| HOMEBREW_CELLAR/name }
.select(&:directory?)
.flat_map(&:subdirs)
.sort_by(&:basename)
end
2016-09-14 23:18:55 +01:00
# All currently installed kegs.
sig { returns(T::Array[Keg]) }
def installed_kegs
installed_prefixes.map { |dir| Keg.new(dir) }
end
# The directory where the formula's binaries should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Examples
#
# Need to install into the {.bin} but the makefile doesn't `mkdir -p prefix/bin`?
#
# ```ruby
# bin.mkpath
# ```
#
# No `make install` available?
#
# ```ruby
# bin.install "binary1"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def bin = prefix/"bin"
# The directory where the formula's documentation should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def doc = share/"doc"/name
# The directory where the formula's headers should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Example
#
# No `make install` available?
#
# ```ruby
# include.install "example.h"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def include = prefix/"include"
# The directory where the formula's info files should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def info = share/"info"
# The directory where the formula's libraries should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Example
#
# No `make install` available?
#
# ```ruby
# lib.install "example.dylib"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def lib = prefix/"lib"
# The directory where the formula's binaries should be installed.
# This is not symlinked into `HOMEBREW_PREFIX`.
# It is commonly used to install files that we do not wish to be
# symlinked into `HOMEBREW_PREFIX` from one of the other directories and
# instead manually create symlinks or wrapper scripts into e.g. {#bin}.
#
# ### Example
#
# ```ruby
# libexec.install "foo.jar"
# bin.write_jar_script libexec/"foo.jar", "foo"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def libexec = prefix/"libexec"
# The root directory where the formula's manual pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
# Often one of the more specific `man` functions should be used instead,
# e.g. {#man1}.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man = share/"man"
# The directory where the formula's man1 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Example
#
# No `make install` available?
#
# ```ruby
# man1.install "example.1"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man1 = man/"man1"
# The directory where the formula's man2 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man2 = man/"man2"
# The directory where the formula's man3 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Example
#
# No `make install` available?
#
# ```ruby
# man3.install "man.3"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man3 = man/"man3"
# The directory where the formula's man4 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man4 = man/"man4"
# The directory where the formula's man5 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man5 = man/"man5"
# The directory where the formula's man6 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man6 = man/"man6"
# The directory where the formula's man7 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man7 = man/"man7"
# The directory where the formula's man8 pages should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def man8 = man/"man8"
# The directory where the formula's `sbin` binaries should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
# Generally we try to migrate these to {#bin} instead.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def sbin = prefix/"sbin"
# The directory where the formula's shared files should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Examples
#
# Need a custom directory?
#
# ```ruby
# (share/"concept").mkpath
# ```
#
# Installing something into another custom directory?
#
# ```ruby
# (share/"concept2").install "ducks.txt"
# ```
#
# Install `./example_code/simple/ones` to `share/demos`:
#
# ```ruby
# (share/"demos").install "example_code/simple/ones"
# ```
#
# Install `./example_code/simple/ones` to `share/demos/examples`:
#
# ```ruby
# (share/"demos").install "example_code/simple/ones" => "examples"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def share = prefix/"share"
# The directory where the formula's shared files should be installed,
# with the name of the formula appended to avoid linking conflicts.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
#
# ### Example
#
# No `make install` available?
#
# ```ruby
# pkgshare.install "examples"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def pkgshare = prefix/"share"/name
# The directory where Emacs Lisp files should be installed, with the
# formula name appended to avoid linking conflicts.
#
# ### Example
#
# To install an Emacs mode included with a software package:
#
# ```ruby
# elisp.install "contrib/emacs/example-mode.el"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def elisp = prefix/"share/emacs/site-lisp"/name
# The directory where the formula's Frameworks should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
# This is not symlinked into `HOMEBREW_PREFIX`.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def frameworks = prefix/"Frameworks"
# The directory where the formula's kernel extensions should be installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
# This is not symlinked into `HOMEBREW_PREFIX`.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def kext_prefix = prefix/"Library/Extensions"
# The directory where the formula's configuration files should be installed.
# Anything using `etc.install` will not overwrite other files on e.g. upgrades
# but will write a new file named `*.default`.
# This directory is not inside the `HOMEBREW_CELLAR` so it persists
# across upgrades.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def etc = (HOMEBREW_PREFIX/"etc").extend(InstallRenamed)
# A subdirectory of `etc` with the formula name suffixed.
# e.g. `$HOMEBREW_PREFIX/etc/openssl@1.1`
# Anything using `pkgetc.install` will not overwrite other files on
# e.g. upgrades but will write a new file named `*.default`.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def pkgetc = (HOMEBREW_PREFIX/"etc"/name).extend(InstallRenamed)
# The directory where the formula's variable files should be installed.
# This directory is not inside the `HOMEBREW_CELLAR` so it persists
# across upgrades.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def var = HOMEBREW_PREFIX/"var"
# The directory where the formula's zsh function files should be
# installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def zsh_function = share/"zsh/site-functions"
# The directory where the formula's fish function files should be
# installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def fish_function = share/"fish/vendor_functions.d"
# The directory where the formula's Bash completion files should be
# installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def bash_completion = prefix/"etc/bash_completion.d"
# The directory where the formula's zsh completion files should be
# installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def zsh_completion = share/"zsh/site-functions"
# The directory where the formula's fish completion files should be
# installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
2024-04-22 21:05:48 +02:00
#
# @api public
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def fish_completion = share/"fish/vendor_completions.d"
# The directory where formula's powershell completion files should be
# installed.
# This is symlinked into `HOMEBREW_PREFIX` after installation or with
# `brew link` for formulae that are not keg-only.
sig { returns(Pathname) }
def pwsh_completion = share/"pwsh/completions"
# The directory used for as the prefix for {#etc} and {#var} files on
# installation so, despite not being in `HOMEBREW_CELLAR`, they are installed
# there after pouring a bottle.
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def bottle_prefix = prefix/".bottle"
2013-10-05 20:29:19 +01:00
2016-05-27 01:53:08 -04:00
# The directory where the formula's installation or test logs will be written.
2023-07-23 18:42:06 -07:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def logs = HOMEBREW_LOGS + name
2015-04-25 22:07:06 -04:00
# The prefix, if any, to use in filenames for logging current activity.
2020-10-20 12:03:48 +02:00
sig { returns(String) }
2016-05-27 01:53:08 -04:00
def active_log_prefix
if active_log_type
"#{active_log_type}."
else
""
end
end
# Runs a block with the given log type in effect for its duration.
2025-02-16 22:20:37 -08:00
sig { params(log_type: String, _block: T.proc.void).void }
def with_logging(log_type, &_block)
2016-05-27 01:53:08 -04:00
old_log_type = @active_log_type
2025-02-16 22:20:37 -08:00
@active_log_type = T.let(log_type, T.nilable(String))
2016-05-27 01:53:08 -04:00
yield
ensure
@active_log_type = old_log_type
end
# This method can be overridden to provide a plist.
#
# ### Example
#
# ```ruby
# def plist; <<~EOS
2020-11-03 16:36:48 -05:00
# <?xml version="1.0" encoding="UTF-8"?>
# <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
# <plist version="1.0">
# <dict>
# <key>Label</key>
# <string>#{plist_name}</string>
# <key>ProgramArguments</key>
# <array>
# <string>#{opt_bin}/example</string>
# <string>--do-this</string>
# </array>
# <key>RunAtLoad</key>
# <true/>
# <key>KeepAlive</key>
# <true/>
# <key>StandardErrorPath</key>
# <string>/dev/null</string>
# <key>StandardOutPath</key>
# <string>/dev/null</string>
# </dict>
# </plist>
# EOS
# end
# ```
#
# @see https://www.unix.com/man-page/all/5/plist/ <code>plist(5)</code> man page
2025-02-16 22:20:37 -08:00
sig { returns(NilClass) }
def plist
odisabled "`Formula#plist`", "`Homebrew::Service`"
nil
end
# The generated launchd {.plist} service name.
2020-10-20 12:03:48 +02:00
sig { returns(String) }
2025-02-17 11:37:57 -08:00
def plist_name = service.plist_name
2021-05-04 16:22:28 +02:00
# The generated service name.
sig { returns(String) }
2025-02-17 11:37:57 -08:00
def service_name = service.service_name
2021-05-04 16:22:28 +02:00
2021-10-23 21:32:38 -04:00
# The generated launchd {.service} file path.
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def launchd_service_path = opt_prefix/"#{plist_name}.plist"
2021-05-04 16:22:28 +02:00
# The generated systemd {.service} file path.
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def systemd_service_path = opt_prefix/"#{service_name}.service"
2021-05-04 16:22:28 +02:00
2021-11-20 15:14:50 +01:00
# The generated systemd {.timer} file path.
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def systemd_timer_path = opt_prefix/"#{service_name}.timer"
2021-11-20 15:14:50 +01:00
# The service specification of the software.
2025-02-16 22:20:37 -08:00
sig { returns(Homebrew::Service) }
def service
2025-02-16 22:20:37 -08:00
@service ||= T.let(Homebrew::Service.new(self, &self.class.service), T.nilable(Homebrew::Service))
end
2014-03-05 20:51:37 -08:00
# A stable path for this formula, when installed. Contains the formula name
# but no version number. Only the active version will be linked here if
# multiple versions are installed.
#
# This is the preferred way to refer to a formula in plists or from another
2014-03-05 20:51:37 -08:00
# formula, as the path is stable even when the software is updated.
#
# ### Example
#
# ```ruby
# args << "--with-readline=#{Formula["readline"].opt_prefix}" if build.with? "readline"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2020-10-20 12:03:48 +02:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_prefix = HOMEBREW_PREFIX/"opt"/name
2024-04-22 21:05:48 +02:00
# Same as {#bin}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_bin = opt_prefix/"bin"
2024-04-22 21:05:48 +02:00
# Same as {#include}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_include = opt_prefix/"include"
2024-04-22 21:05:48 +02:00
# Same as {#lib}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_lib = opt_prefix/"lib"
2024-04-22 21:05:48 +02:00
# Same as {#libexec}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_libexec = opt_prefix/"libexec"
2024-04-22 21:05:48 +02:00
# Same as {#sbin}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_sbin = opt_prefix/"sbin"
2024-04-22 21:05:48 +02:00
# Same as {#share}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_share = opt_prefix/"share"
2024-04-22 21:05:48 +02:00
# Same as {#pkgshare}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_pkgshare = opt_prefix/"share"/name
2024-04-22 21:05:48 +02:00
# Same as {#elisp}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_elisp = opt_prefix/"share/emacs/site-lisp"/name
2024-04-22 21:05:48 +02:00
# Same as {#frameworks}, but relative to {#opt_prefix} instead of {#prefix}.
#
# @api public
2020-11-24 15:46:47 +01:00
sig { returns(Pathname) }
2025-02-17 11:37:57 -08:00
def opt_frameworks = opt_prefix/"Frameworks"
2014-03-05 21:01:24 -08:00
# Indicates that this formula supports bottles. (Not necessarily that one
# should be used in the current installation run.)
# Can be overridden to selectively disable bottles from formulae.
# Defaults to true so overridden version does not have to check if bottles
# are supported.
# Replaced by {.pour_bottle?}'s `satisfy` method if it is specified.
2020-10-20 12:03:48 +02:00
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def pour_bottle? = true
delegate pour_bottle_check_unsatisfied_reason: :"self.class"
# Can be overridden to run commands on both source and bottle installation.
2020-11-24 15:46:47 +01:00
sig { overridable.void }
def post_install; end
sig { returns(T::Boolean) }
def post_install_defined?
method(:post_install).owner != Formula
end
sig { void }
def install_etc_var
etc_var_dirs = [bottle_prefix/"etc", bottle_prefix/"var"]
Find.find(*etc_var_dirs.select(&:directory?)) do |path|
path = Pathname.new(path)
path.extend(InstallRenamed)
path.cp_path_sub(bottle_prefix, HOMEBREW_PREFIX)
end
end
2020-11-24 15:46:47 +01:00
sig { void }
2016-05-27 01:53:08 -04:00
def run_post_install
2025-02-16 22:20:37 -08:00
@prefix_returns_versioned_prefix = T.let(true, T.nilable(T::Boolean))
2016-05-27 01:53:08 -04:00
build = self.build
2020-11-24 15:46:47 +01:00
begin
self.build = Tab.for_formula(self)
2017-07-15 17:26:51 -07:00
2020-11-24 15:46:47 +01:00
new_env = {
TMPDIR: HOMEBREW_TEMP,
TEMP: HOMEBREW_TEMP,
TMP: HOMEBREW_TEMP,
_JAVA_OPTIONS: "-Djava.io.tmpdir=#{HOMEBREW_TEMP}",
HOMEBREW_PATH: nil,
PATH: PATH.new(ORIGINAL_PATHS),
2020-11-24 15:46:47 +01:00
}
2017-07-15 17:26:51 -07:00
2020-11-24 15:46:47 +01:00
with_env(new_env) do
ENV.clear_sensitive_environment!
ENV.activate_extensions!
2017-07-15 17:26:51 -07:00
2020-11-24 15:46:47 +01:00
with_logging("post_install") do
post_install
end
2017-07-15 17:26:51 -07:00
end
2020-11-24 15:46:47 +01:00
ensure
self.build = build
2025-02-16 22:20:37 -08:00
@prefix_returns_versioned_prefix = T.let(false, T.nilable(T::Boolean))
2016-05-27 01:53:08 -04:00
end
end
# Warn the user about any Homebrew-specific issues or quirks for this package.
2019-06-20 11:08:02 +01:00
# These should not contain setup instructions that would apply to installation
# through a different package manager on a different OS.
#
# ### Example
#
# ```ruby
# def caveats
2017-10-15 02:28:32 +02:00
# <<~EOS
2019-06-20 11:08:02 +01:00
# Are optional. Something the user must be warned about?
# EOS
# end
# ```
#
# ```ruby
# def caveats
2017-10-15 02:28:32 +02:00
# s = <<~EOS
# Print some important notice to the user when `brew info [formula]` is
# called or when brewing a formula.
# This is optional. You can use all the vars like #{version} here.
# EOS
# s += "Some issue only on older systems" if MacOS.version < :el_capitan
# s
# end
# ```
2020-11-24 15:46:47 +01:00
sig { overridable.returns(T.nilable(String)) }
2025-02-17 11:37:57 -08:00
def caveats = nil
# Rarely, you don't want your library symlinked into the main prefix.
# See `gettext.rb` for an example.
2020-11-05 15:19:56 -05:00
# @see .keg_only
2024-04-22 21:05:48 +02:00
#
# @api internal
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2010-07-18 10:38:45 -07:00
def keg_only?
2017-07-07 09:23:31 +01:00
return false unless keg_only_reason
2018-09-17 02:45:00 +02:00
keg_only_reason.applicable?
end
delegate keg_only_reason: :"self.class"
# @see .skip_clean
2020-11-24 15:46:47 +01:00
sig { params(path: Pathname).returns(T::Boolean) }
def skip_clean?(path)
2025-02-16 22:20:37 -08:00
return true if path.extname == ".la" && T.must(self.class.skip_clean_paths).include?(:la)
2018-09-17 02:45:00 +02:00
to_check = path.relative_path_from(prefix).to_s
2025-02-16 22:20:37 -08:00
T.must(self.class.skip_clean_paths).include? to_check
end
# @see .link_overwrite
2025-02-16 22:20:37 -08:00
sig { params(path: Pathname).returns(T::Boolean) }
def link_overwrite?(path)
# Don't overwrite files not created by Homebrew.
return false if path.stat.uid != HOMEBREW_ORIGINAL_BREW_FILE.stat.uid
2018-09-17 02:45:00 +02:00
# Don't overwrite files belong to other keg except when that
# keg's formula is deleted.
begin
keg = Keg.for(path)
rescue NotAKegError, Errno::ENOENT
# file doesn't belong to any keg.
else
tab_tap = keg.tab.tap
# this keg doesn't below to any core/tap formula, most likely coming from a DIY install.
return false if tab_tap.nil?
2018-09-17 02:45:00 +02:00
begin
f = Formulary.factory(keg.name)
rescue FormulaUnavailableError
# formula for this keg is deleted, so defer to allowlist
rescue TapFormulaAmbiguityError
return false # this keg belongs to another formula
else
# this keg belongs to another unrelated formula
return false unless f.possible_names.include?(keg.name)
end
end
to_check = path.relative_path_from(HOMEBREW_PREFIX).to_s
2025-02-16 22:20:37 -08:00
T.must(self.class.link_overwrite_paths).any? do |p|
p.to_s == to_check ||
to_check.start_with?("#{p.to_s.chomp("/")}/") ||
/^#{Regexp.escape(p.to_s).gsub('\*', ".*?")}$/.match?(to_check)
end
end
2020-04-01 13:42:52 +01:00
# Whether this {Formula} is deprecated (i.e. warns on installation).
# Defaults to false.
2020-11-05 15:19:56 -05:00
# @!method deprecated?
2020-04-01 13:42:52 +01:00
# @return [Boolean]
# @see .deprecate!
delegate deprecated?: :"self.class"
2020-04-01 13:42:52 +01:00
# The date that this {Formula} was or becomes deprecated.
# Returns `nil` if no date is specified.
# @!method deprecation_date
# @return Date
# @see .deprecate!
delegate deprecation_date: :"self.class"
2020-08-27 10:42:57 -04:00
# The reason this {Formula} is deprecated.
# Returns `nil` if no reason is specified or the formula is not deprecated.
2020-11-05 15:19:56 -05:00
# @!method deprecation_reason
# @return [String, Symbol]
# @see .deprecate!
2020-08-27 10:42:57 -04:00
delegate deprecation_reason: :"self.class"
# The replacement for this deprecated {Formula}.
# Returns `nil` if no replacement is specified or the formula is not deprecated.
# @!method deprecation_replacement
# @return [String]
# @see .deprecate!
delegate deprecation_replacement: :"self.class"
2020-04-01 13:42:52 +01:00
# Whether this {Formula} is disabled (i.e. cannot be installed).
# Defaults to false.
2020-11-05 15:19:56 -05:00
# @!method disabled?
2020-04-01 13:42:52 +01:00
# @return [Boolean]
# @see .disable!
delegate disabled?: :"self.class"
2020-04-01 13:42:52 +01:00
# The date that this {Formula} was or becomes disabled.
# Returns `nil` if no date is specified.
# @!method disable_date
# @return Date
# @see .disable!
delegate disable_date: :"self.class"
2020-08-27 10:42:57 -04:00
# The reason this {Formula} is disabled.
# Returns `nil` if no reason is specified or the formula is not disabled.
2020-11-05 15:19:56 -05:00
# @!method disable_reason
# @return [String, Symbol]
# @see .disable!
2020-08-27 10:42:57 -04:00
delegate disable_reason: :"self.class"
# The replacement for this disabled {Formula}.
# Returns `nil` if no replacement is specified or the formula is not disabled.
# @!method disable_replacement
# @return [String]
# @see .disable!
delegate disable_replacement: :"self.class"
2020-10-20 12:03:48 +02:00
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def skip_cxxstdlib_check? = false
2020-10-20 12:03:48 +02:00
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def require_universal_deps? = false
2025-02-16 22:20:37 -08:00
sig { void }
def patch
2016-09-23 22:02:23 +02:00
return if patchlist.empty?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
ohai "Patching"
patchlist.each(&:apply)
end
formula: don't include DATA patches in initial Git repo Currently, existing DATA patches are subsumed into the initial Git repo created by `brew install --git`, which makes creating a new DATA patch after more fixes a tedious and error-prone process. This PR delays DATA patch processing till after the Git repo is created, so a `git diff` at the end creates a correct and consolidated DATA patch block ready for insertion/replacement, or even migration to a proper remote patch URL. The difference is clearly seen in `gromgit/fuse/dislocker-mac`, which has both remote and DATA patches. Before: ``` % brew install -sig dislocker-mac ==> Fetching gromgit/fuse/dislocker-mac ==> Downloading https://github.com/Aorimn/dislocker/commit/2cfbba2c8cc07e529622ba134d0a6982815d2b30.patch?full_index=1 Already downloaded: /Volumes/aho/Library/Caches/Homebrew/downloads/37276859cbebc1711941278db00cd8b25b98d69e15e31e33915a98d01a13febc--2cfbba2c8cc07e529622ba134d0a6982815d2b30.patch ==> Downloading https://github.com/Aorimn/dislocker/archive/refs/tags/v0.7.3.tar.gz Already downloaded: /Volumes/aho/Library/Caches/Homebrew/downloads/b1ba1098c95535574936051eca45cc472955a5a024b81cc72e1c3b006e1950b3--dislocker-0.7.3.tar.gz ==> Installing dislocker-mac from gromgit/fuse ==> Patching ==> Applying 2cfbba2c8cc07e529622ba134d0a6982815d2b30.patch Initialized empty Git repository in /private/tmp/dislocker-mac-20250215-35534-8qlxtp/dislocker-0.7.3/.git/ ==> Entering interactive mode... Type `exit` to return and finalize the installation. Install to this prefix: /opt/homebrew/Cellar/dislocker-mac/0.7.3_2 This directory is now a Git repository. Make your changes and then use: git diff | pbcopy to copy the diff to the clipboard. % git diff ``` After: ``` % brew install -sig dislocker-mac ==> Fetching gromgit/fuse/dislocker-mac ==> Downloading https://github.com/Aorimn/dislocker/commit/2cfbba2c8cc07e529622ba134d0a6982815d2b30.patch?full_index=1 Already downloaded: /Volumes/aho/Library/Caches/Homebrew/downloads/37276859cbebc1711941278db00cd8b25b98d69e15e31e33915a98d01a13febc--2cfbba2c8cc07e529622ba134d0a6982815d2b30.patch ==> Downloading https://github.com/Aorimn/dislocker/archive/refs/tags/v0.7.3.tar.gz Already downloaded: /Volumes/aho/Library/Caches/Homebrew/downloads/b1ba1098c95535574936051eca45cc472955a5a024b81cc72e1c3b006e1950b3--dislocker-0.7.3.tar.gz ==> Installing dislocker-mac from gromgit/fuse ==> Applying non-DATA patches ==> Applying 2cfbba2c8cc07e529622ba134d0a6982815d2b30.patch Initialized empty Git repository in /private/tmp/dislocker-mac-20250215-32462-zh1akh/dislocker-0.7.3/.git/ ==> Applying DATA patches ==> Entering interactive mode... Type `exit` to return and finalize the installation. Install to this prefix: /opt/homebrew/Cellar/dislocker-mac/0.7.3_2 This directory is now a Git repository. Make your changes and then use: git diff | pbcopy to copy the diff to the clipboard. % git diff diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bd854d2..9ab137d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,7 +92,7 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") # Don't use `-read_only_relocs' here as it seems to only work for 32 bits # binaries set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-bind_at_load") - set (FUSE_LIB osxfuse_i64) + set (FUSE_LIB fuse) else() # Useless warnings when used within Darwin set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wconversion") diff --git a/src/dislocker-fuse.c b/src/dislocker-fuse.c index f93523f..3dd106c 100644 --- a/src/dislocker-fuse.c +++ b/src/dislocker-fuse.c @@ -33,11 +33,7 @@ -#ifdef __DARWIN -# include <osxfuse/fuse.h> -#else -# include <fuse.h> -#endif /* __DARWIN */ +#include <fuse.h> /** NTFS virtual partition's name */ ```
2025-02-15 20:15:53 +08:00
sig { params(is_data: T::Boolean).void }
def selective_patch(is_data: false)
patches = patchlist.select { |p| p.is_a?(DATAPatch) == is_data }
return if patches.empty?
patchtype = if is_data
"DATA"
else
"non-DATA"
end
ohai "Applying #{patchtype} patches"
patches.each(&:apply)
end
# Yields |self,staging| with current working directory set to the uncompressed tarball
# where staging is a {Mktemp} staging context.
2024-11-21 18:10:20 -08:00
sig(:final) {
params(fetch: T::Boolean, keep_tmp: T::Boolean, debug_symbols: T::Boolean, interactive: T::Boolean,
_blk: T.proc.params(arg0: Formula, arg1: Mktemp).void).void
}
def brew(fetch: true, keep_tmp: false, debug_symbols: false, interactive: false, &_blk)
2025-02-16 22:20:37 -08:00
@prefix_returns_versioned_prefix = T.let(true, T.nilable(T::Boolean))
active_spec.fetch if fetch
2024-03-07 16:20:20 +00:00
stage(interactive:, debug_symbols:) do |staging|
staging.retain! if keep_tmp || debug_symbols
prepare_patches
fetch_patches if fetch
begin
yield self, staging
rescue
staging.retain! if interactive || debug?
raise
ensure
%w[
config.log
CMakeCache.txt
CMakeConfigureLog.yaml
meson-log.txt
].each do |logfile|
Dir["**/#{logfile}"].each do |logpath|
destdir = logs/File.dirname(logpath)
mkdir_p destdir
cp logpath, destdir
end
end
end
end
ensure
2025-02-16 22:20:37 -08:00
@prefix_returns_versioned_prefix = T.let(false, T.nilable(T::Boolean))
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def lock
2025-02-16 22:20:37 -08:00
@lock = T.let(FormulaLock.new(name), T.nilable(FormulaLock))
T.must(@lock).lock
2018-09-17 02:45:00 +02:00
2023-04-27 04:09:28 +01:00
oldnames.each do |oldname|
next unless (oldname_rack = HOMEBREW_CELLAR/oldname).exist?
next if oldname_rack.resolved_path != rack
oldname_lock = FormulaLock.new(oldname)
oldname_lock.lock
@oldname_locks << oldname_lock
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[FormulaLock]) }
def unlock
2017-09-24 19:24:46 +01:00
@lock&.unlock
2023-04-27 04:09:28 +01:00
@oldname_locks.each(&:unlock)
end
sig { returns(T::Array[String]) }
2023-04-27 04:09:28 +01:00
def oldnames_to_migrate
oldnames.select do |oldname|
old_rack = HOMEBREW_CELLAR/oldname
next false unless old_rack.directory?
next false if old_rack.subdirs.empty?
2016-07-01 18:02:31 +03:00
2023-04-27 04:09:28 +01:00
tap == Tab.for_keg(old_rack.subdirs.min).tap
end
end
2016-07-01 18:02:31 +03:00
sig { returns(T::Boolean) }
2023-04-27 04:09:28 +01:00
def migration_needed?
!oldnames_to_migrate.empty? && !rack.exist?
end
2025-02-16 22:20:37 -08:00
sig { params(fetch_head: T::Boolean).returns(T::Array[Keg]) }
def outdated_kegs(fetch_head: false)
2023-04-27 04:09:28 +01:00
raise Migrator::MigrationNeededError.new(oldnames_to_migrate.first, name) if migration_needed?
2020-08-02 13:43:32 +01:00
cache_key = "#{full_name}-#{fetch_head}"
Formula.cache[:outdated_kegs] ||= {}
Formula.cache[:outdated_kegs][cache_key] ||= begin
all_kegs = []
2020-11-24 15:46:47 +01:00
current_version = T.let(false, T::Boolean)
installed_kegs.each do |keg|
all_kegs << keg
version = keg.version
next if version.head?
2024-03-29 23:07:38 +00:00
next if version_scheme > keg.version_scheme && pkg_version != version
next if version_scheme == keg.version_scheme && pkg_version > version
# don't consider this keg current if there's a newer formula available
next if follow_installed_alias? && new_formula_available?
# this keg is the current version of the formula, so it's not outdated
current_version = true
break
end
2020-11-13 10:07:02 -05:00
if current_version ||
2024-03-07 16:20:20 +00:00
((head_version = latest_head_version) && !head_version_outdated?(head_version, fetch_head:))
[]
else
all_kegs += old_installed_formulae.flat_map(&:installed_kegs)
all_kegs.sort_by(&:scheme_and_version)
end
end
end
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def new_formula_available?
installed_alias_target_changed? && !latest_formula.latest_version_installed?
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(Formula)) }
def current_installed_alias_target
Formulary.factory(T.must(installed_alias_name)) if installed_alias_path
end
# Has the target of the alias used to install this formula changed?
# Returns false if the formula wasn't installed with an alias.
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def installed_alias_target_changed?
target = current_installed_alias_target
2017-09-24 20:12:58 +01:00
return false unless target
2018-09-17 02:45:00 +02:00
2017-09-24 20:12:58 +01:00
target.name != name
end
# Is this formula the target of an alias used to install an old formula?
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2025-02-17 11:37:57 -08:00
def supersedes_an_installed_formula? = old_installed_formulae.any?
# Has the alias used to install the formula changed, or are different
# formulae already installed with this alias?
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def alias_changed?
installed_alias_target_changed? || supersedes_an_installed_formula?
end
# If the alias has changed value, return the new formula.
# Otherwise, return self.
2025-02-16 22:20:37 -08:00
sig { returns(Formula) }
def latest_formula
2025-02-16 22:20:37 -08:00
installed_alias_target_changed? ? T.must(current_installed_alias_target) : self
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Formula]) }
def old_installed_formulae
# If this formula isn't the current target of the alias,
# it doesn't make sense to say that other formulae are older versions of it
# because we don't know which came first.
return [] if alias_path.nil? || installed_alias_target_changed?
2018-09-17 02:45:00 +02:00
self.class.installed_with_alias_path(alias_path).reject { |f| f.name == name }
end
2024-04-22 21:05:48 +02:00
# Check whether the installed formula is outdated.
#
# @api internal
2023-07-23 21:05:02 -07:00
sig { params(fetch_head: T::Boolean).returns(T::Boolean) }
def outdated?(fetch_head: false)
2024-03-07 16:20:20 +00:00
!outdated_kegs(fetch_head:).empty?
rescue Migrator::MigrationNeededError
true
end
2025-02-17 11:37:57 -08:00
def_delegators :@pin, :pinnable?, :pinned_version, :pin, :unpin
# !attr[r] pinned?
2024-04-22 21:05:48 +02:00
# @api internal
delegate pinned?: :@pin
2025-02-16 22:20:37 -08:00
sig { params(other: T.untyped).returns(T::Boolean) }
def ==(other)
self.class == other.class &&
name == other.name &&
active_spec_sym == other.active_spec_sym
end
2016-09-23 18:13:48 +02:00
alias eql? ==
2025-02-16 22:20:37 -08:00
sig { returns(Integer) }
2025-02-17 11:37:57 -08:00
def hash = name.hash
2025-02-16 22:20:37 -08:00
sig { params(other: BasicObject).returns(T.nilable(Integer)) }
def <=>(other)
2025-02-16 22:20:37 -08:00
case other
when Formula then name <=> other.name
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def possible_names
2023-04-27 04:09:28 +01:00
[name, *oldnames, *aliases].compact
end
2024-04-26 14:04:55 +02:00
# @api public
sig { returns(String) }
def to_s = name
2014-07-01 15:13:29 -05:00
2020-10-20 12:03:48 +02:00
sig { returns(String) }
def inspect
2015-07-28 15:10:40 +08:00
"#<Formula #{name} (#{active_spec_sym}) #{path}>"
end
# Standard parameters for cabal-v2 builds.
#
# @api public
sig { returns(T::Array[String]) }
def std_cabal_v2_args
# cabal-install's dependency-resolution backtracking strategy can
# easily need more than the default 2,000 maximum number of
# "backjumps," since Hackage is a fast-moving, rolling-release
# target. The highest known needed value by a formula was 43,478
# for git-annex, so 100,000 should be enough to avoid most
# gratuitous backjumps build failures.
["--jobs=#{ENV.make_jobs}", "--max-backjumps=100000", "--install-method=copy", "--installdir=#{bin}"]
end
2020-06-22 13:24:41 +02:00
# Standard parameters for cargo builds.
#
# @api public
sig {
params(root: T.any(String, Pathname), path: T.any(String, Pathname)).returns(T::Array[String])
}
def std_cargo_args(root: prefix, path: ".")
["--jobs", ENV.make_jobs.to_s, "--locked", "--root=#{root}", "--path=#{path}"]
2020-06-22 13:24:41 +02:00
end
2010-09-22 08:05:58 -07:00
# Standard parameters for CMake builds.
#
# Setting `CMAKE_FIND_FRAMEWORK` to "LAST" tells CMake to search for our
# libraries before trying to utilize Frameworks, many of which will be from
# 3rd party installs.
#
# @api public
sig {
params(
install_prefix: T.any(String, Pathname),
install_libdir: T.any(String, Pathname),
find_framework: String,
).returns(T::Array[String])
}
def std_cmake_args(install_prefix: prefix, install_libdir: "lib", find_framework: "LAST")
2023-11-15 19:52:21 +00:00
%W[
-DCMAKE_INSTALL_PREFIX=#{install_prefix}
-DCMAKE_INSTALL_LIBDIR=#{install_libdir}
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_FIND_FRAMEWORK=#{find_framework}
-DCMAKE_VERBOSE_MAKEFILE=ON
-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=#{HOMEBREW_LIBRARY_PATH}/cmake/trap_fetchcontent_provider.cmake
-Wno-dev
-DBUILD_TESTING=OFF
]
end
# Standard parameters for configure builds.
#
# @api public
sig {
params(
prefix: T.any(String, Pathname),
libdir: T.any(String, Pathname),
).returns(T::Array[String])
}
def std_configure_args(prefix: self.prefix, libdir: "lib")
libdir = Pathname(libdir).expand_path(prefix)
["--disable-debug", "--disable-dependency-tracking", "--prefix=#{prefix}", "--libdir=#{libdir}"]
end
2020-01-15 12:33:55 +01:00
# Standard parameters for Go builds.
#
# @api public
sig {
2024-10-23 19:15:41 +02:00
params(
output: T.any(String, Pathname),
ldflags: T.nilable(T.any(String, T::Array[String])),
gcflags: T.nilable(T.any(String, T::Array[String])),
).returns(T::Array[String])
}
2024-10-23 19:15:41 +02:00
def std_go_args(output: bin/name, ldflags: nil, gcflags: nil)
args = ["-trimpath", "-o=#{output}"]
args += ["-ldflags=#{Array(ldflags).join(" ")}"] if ldflags
2024-10-23 19:15:41 +02:00
args += ["-gcflags=#{Array(gcflags).join(" ")}"] if gcflags
args
2020-01-15 12:33:55 +01:00
end
# Standard parameters for meson builds.
#
# @api public
2020-10-20 12:03:48 +02:00
sig { returns(T::Array[String]) }
def std_meson_args
["--prefix=#{prefix}", "--libdir=#{lib}", "--buildtype=release", "--wrap-mode=nofallback"]
end
2024-07-15 23:36:52 -07:00
# Standard parameters for npm builds.
#
# @api public
sig { params(prefix: T.any(String, Pathname, FalseClass)).returns(T::Array[String]) }
2024-07-15 23:36:52 -07:00
def std_npm_args(prefix: libexec)
require "language/node"
return Language::Node.std_npm_install_args(Pathname(prefix)) if prefix
Language::Node.local_npm_install_args
end
2023-07-17 20:48:40 -07:00
# Standard parameters for pip builds.
#
# @api public
2023-07-17 20:48:40 -07:00
sig {
2025-02-16 22:20:37 -08:00
params(prefix: T.any(FalseClass, String, Pathname),
2023-07-17 20:48:40 -07:00
build_isolation: T::Boolean).returns(T::Array[String])
}
def std_pip_args(prefix: self.prefix, build_isolation: false)
2023-08-25 17:23:30 -07:00
args = ["--verbose", "--no-deps", "--no-binary=:all:", "--ignore-installed", "--no-compile"]
args << "--prefix=#{prefix}" if prefix
2023-07-17 20:48:40 -07:00
args << "--no-build-isolation" unless build_isolation
args
end
2025-02-21 14:53:34 +01:00
# Standard parameters for zig builds.
#
# @api public
sig {
params(prefix: T.any(String, Pathname),
release_mode: Symbol).returns(T::Array[String])
2025-02-21 14:53:34 +01:00
}
def std_zig_args(prefix: self.prefix, release_mode: :fast)
raise ArgumentError, "Invalid Zig release mode: #{release_mode}" if [:safe, :fast, :small].exclude?(release_mode)
release_mode_downcased = release_mode.to_s.downcase
release_mode_capitalized = release_mode.to_s.capitalize
2025-02-21 16:49:11 +01:00
[
"--prefix", prefix.to_s,
"--release=#{release_mode_downcased}",
"-Doptimize=Release#{release_mode_capitalized}",
"--summary", "all"
2025-02-21 16:49:11 +01:00
]
2025-02-21 14:53:34 +01:00
end
# Shared library names according to platform conventions.
#
# Optionally specify a `version` to restrict the shared library to a specific
# version. The special string "*" matches any version.
#
# If `name` is specified as "*", match any shared library of any version.
#
# ### Example
#
# ```ruby
# shared_library("foo") #=> foo.dylib
# shared_library("foo", 1) #=> foo.1.dylib
# shared_library("foo", "*") #=> foo.2.dylib, foo.1.dylib, foo.dylib
# shared_library("*") #=> foo.dylib, bar.dylib
# ```
#
# @api public
sig { params(name: String, version: T.nilable(T.any(String, Integer))).returns(String) }
def shared_library(name, version = nil)
return "*.dylib" if name == "*" && (version.blank? || version == "*")
infix = if version == "*"
"{,.*}"
elsif version.present?
".#{version}"
end
"#{name}#{infix}.dylib"
end
# Executable/Library RPATH according to platform conventions.
#
# Optionally specify a `source` or `target` depending on the location
# of the file containing the RPATH command and where its target is located.
#
# ### Example
#
# ```ruby
# rpath #=> "@loader_path/../lib"
# rpath(target: frameworks) #=> "@loader_path/../Frameworks"
# rpath(source: libexec/"bin") #=> "@loader_path/../../lib"
# ```
#
# @api public
sig { params(source: Pathname, target: Pathname).returns(String) }
def rpath(source: bin, target: lib)
unless target.to_s.start_with?(HOMEBREW_PREFIX)
raise "rpath `target` should only be used for paths inside HOMEBREW_PREFIX!"
end
"#{loader_path}/#{target.relative_path_from(source)}"
end
# Linker variable for the directory containing the program or shared object.
#
# @api public
sig { returns(String) }
2025-02-17 11:37:57 -08:00
def loader_path = "@loader_path"
# Creates a new `Time` object for use in the formula as the build time.
#
# @see https://www.rubydoc.info/stdlib/time/Time Time
sig { returns(Time) }
def time
if ENV["SOURCE_DATE_EPOCH"].present?
Time.at(ENV["SOURCE_DATE_EPOCH"].to_i).utc
else
Time.now.utc
end
end
2021-07-24 19:39:47 +08:00
# Replaces a universal binary with its native slice.
#
# If called with no parameters, does this with all compatible
# universal binaries in a {Formula}'s {Keg}.
#
# Raises an error if no universal binaries are found to deuniversalize.
#
# @api public
sig { params(targets: T.nilable(T.any(Pathname, String))).void }
def deuniversalize_machos(*targets)
if targets.none?
targets = any_installed_keg&.mach_o_files&.select do |file|
file.arch == :universal && file.archs.include?(Hardware::CPU.arch)
end
end
raise "No universal binaries found to deuniversalize" if targets.blank?
targets&.each do |target|
extract_macho_slice_from(Pathname(target), Hardware::CPU.arch)
end
2021-07-24 19:39:47 +08:00
end
sig { params(file: Pathname, arch: T.nilable(Symbol)).void }
def extract_macho_slice_from(file, arch = Hardware::CPU.arch)
2021-07-24 19:39:47 +08:00
odebug "Extracting #{arch} slice from #{file}"
file.ensure_writable do
macho = MachO::FatFile.new(file)
native_slice = macho.extract(Hardware::CPU.arch)
2021-07-24 19:39:47 +08:00
native_slice.write file
2021-07-25 23:21:26 +08:00
MachO.codesign! file if Hardware::CPU.arm?
2021-07-24 19:39:47 +08:00
rescue MachO::MachOBinaryError
onoe "#{file} is not a universal binary"
raise
rescue NoMethodError
onoe "#{file} does not contain an #{arch} slice"
raise
end
end
private :extract_macho_slice_from
2025-03-02 18:44:40 -08:00
# Generate shell completions for a formula for `bash`, `zsh`, `fish`, and `pwsh`,
# using the formula's executable.
#
# ### Examples
#
# Using default values for optional arguments.
#
# ```ruby
# generate_completions_from_executable(bin/"foo", "completions")
#
# # translates to
# (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions", "bash")
# (zsh_completion/"_foo").write Utils.safe_popen_read({ "SHELL" => "zsh" }, bin/"foo", "completions", "zsh")
# (fish_completion/"foo.fish").write Utils.safe_popen_read({ "SHELL" => "fish" }, bin/"foo",
# "completions", "fish")
# (pwsh_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "pwsh" }, bin/"foo",
# "completions", "powershell")
# ```
#
# Selecting shells and using a different `base_name`.
#
# ```ruby
# generate_completions_from_executable(bin/"foo", "completions", shells: [:bash, :zsh], base_name: "bar")
#
# # translates to
# (bash_completion/"bar").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions", "bash")
# (zsh_completion/"_bar").write Utils.safe_popen_read({ "SHELL" => "zsh" }, bin/"foo", "completions", "zsh")
# ```
2022-07-11 15:35:48 +02:00
#
# Using predefined `shell_parameter_format :flag`.
2022-08-10 15:40:54 +02:00
#
# ```ruby
# generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: :flag, shells: [:bash])
2022-08-10 15:40:54 +02:00
#
# # translates to
# (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions", "--bash")
# ```
2022-08-10 15:40:54 +02:00
#
# Using predefined `shell_parameter_format :arg`.
2022-08-10 15:40:54 +02:00
#
# ```ruby
# generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: :arg, shells: [:bash])
2022-08-10 15:40:54 +02:00
#
# # translates to
# (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo",
# "completions", "--shell=bash")
# ```
2022-08-10 15:40:54 +02:00
#
# Using predefined `shell_parameter_format :none`.
2022-08-10 15:40:54 +02:00
#
# ```ruby
# generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: :none, shells: [:bash])
2022-08-10 15:40:54 +02:00
#
# # translates to
# (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo", "completions")
# ```
2022-08-10 15:40:54 +02:00
#
# Using predefined `shell_parameter_format :click`.
2022-08-10 15:40:54 +02:00
#
# ```ruby
# generate_completions_from_executable(bin/"foo", shell_parameter_format: :click, shells: [:zsh])
2022-08-10 15:40:54 +02:00
#
# # translates to
# (zsh_completion/"_foo").write Utils.safe_popen_read({ "SHELL" => "zsh", "_FOO_COMPLETE" => "zsh_source" },
# bin/"foo")
# ```
#
2024-12-17 07:52:45 +09:00
# Using predefined `shell_parameter_format :clap`.
#
# ```ruby
# generate_completions_from_executable(bin/"foo", shell_parameter_format: :clap, shells: [:zsh])
#
# # translates to
# (zsh_completion/"_foo").write Utils.safe_popen_read({ "SHELL" => "zsh", "COMPLETE" => "zsh" }, bin/"foo")
# ```
#
# Using custom `shell_parameter_format`.
#
# ```ruby
# generate_completions_from_executable(bin/"foo", "completions", shell_parameter_format: "--selected-shell=",
# shells: [:bash])
2022-08-10 15:40:54 +02:00
#
# # translates to
# (bash_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "bash" }, bin/"foo",
# "completions", "--selected-shell=bash")
# ```
#
# @api public
# @param commands
# the path to the executable and any passed subcommand(s) to use for generating the completion scripts.
# @param base_name
# the base name of the generated completion script. Defaults to the name of the executable if installed
# within formula's bin or sbin. Otherwise falls back to the formula name.
# @param shells
# the shells to generate completion scripts for. Defaults to `[:bash, :zsh, :fish]`.
# @param shell_parameter_format
# specify how `shells` should each be passed to the `executable`. Takes either a String representing a
2024-12-17 07:52:45 +09:00
# prefix, or one of `[:flag, :arg, :none, :click, :clap]`. Defaults to plainly passing the shell.
2022-07-11 15:21:57 +02:00
sig {
params(
commands: T.any(Pathname, String),
base_name: T.nilable(String),
shells: T::Array[Symbol],
shell_parameter_format: T.nilable(T.any(Symbol, String)),
).void
2022-07-11 15:21:57 +02:00
}
def generate_completions_from_executable(*commands,
base_name: nil,
shells: [:bash, :zsh, :fish],
2022-07-24 23:03:28 +02:00
shell_parameter_format: nil)
executable = commands.first.to_s
base_name ||= File.basename(executable) if executable.start_with?(bin.to_s, sbin.to_s)
base_name ||= name
2022-07-10 15:24:14 +02:00
completion_script_path_map = {
2022-07-10 15:45:07 +02:00
bash: bash_completion/base_name,
zsh: zsh_completion/"_#{base_name}",
fish: fish_completion/"#{base_name}.fish",
pwsh: pwsh_completion/"#{base_name}.ps1",
2022-07-10 15:24:14 +02:00
}
shells.each do |shell|
popen_read_env = { "SHELL" => shell.to_s }
2022-07-10 15:24:14 +02:00
script_path = completion_script_path_map[shell]
# Go's cobra and Rust's clap accept "powershell".
2025-03-02 18:44:40 -08:00
shell_argument = (shell == :pwsh) ? "powershell" : shell.to_s
2022-07-24 23:03:28 +02:00
shell_parameter = if shell_parameter_format.nil?
shell_argument.to_s
2022-07-24 23:03:28 +02:00
elsif shell_parameter_format == :flag
"--#{shell_argument}"
2022-07-24 23:03:28 +02:00
elsif shell_parameter_format == :arg
"--shell=#{shell_argument}"
2022-07-24 23:03:28 +02:00
elsif shell_parameter_format == :none
nil
elsif shell_parameter_format == :click
prog_name = File.basename(executable).upcase.tr("-", "_")
popen_read_env["_#{prog_name}_COMPLETE"] = "#{shell_argument}_source"
nil
2024-12-17 07:52:45 +09:00
elsif shell_parameter_format == :clap
popen_read_env["COMPLETE"] = shell_argument.to_s
2024-12-17 07:52:45 +09:00
nil
else
"#{shell_parameter_format}#{shell_argument}"
end
popen_read_args = %w[]
popen_read_args << commands
2022-09-02 22:26:58 +02:00
popen_read_args << shell_parameter if shell_parameter.present?
popen_read_args.flatten!
popen_read_options = {}
popen_read_options[:err] = :err unless ENV["HOMEBREW_STDERR"]
2022-07-10 15:24:14 +02:00
script_path.dirname.mkpath
script_path.write Utils.safe_popen_read(popen_read_env, *popen_read_args, **popen_read_options)
2022-07-10 15:24:14 +02:00
end
end
# an array of all core {Formula} names
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.core_names
CoreTap.instance.formula_names
end
# an array of all tap {Formula} names
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.tap_names
2025-02-16 22:20:37 -08:00
@tap_names ||= T.let(Tap.reject(&:core_tap?).flat_map(&:formula_names).sort, T.nilable(T::Array[String]))
end
# an array of all tap {Formula} files
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Pathname]) }
def self.tap_files
2025-02-16 22:20:37 -08:00
@tap_files ||= T.let(Tap.reject(&:core_tap?).flat_map(&:formula_files), T.nilable(T::Array[Pathname]))
end
# an array of all {Formula} names
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.names
2025-02-16 22:20:37 -08:00
@names ||= T.let((core_names + tap_names.map do |name|
name.split("/").fetch(-1)
end).uniq.sort, T.nilable(T::Array[String]))
end
# an array of all {Formula} names, which the tap formulae have the fully-qualified name
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.full_names
2025-02-16 22:20:37 -08:00
@full_names ||= T.let(core_names + tap_names, T.nilable(T::Array[String]))
end
# an array of all {Formula}
# this should only be used when users specify `--all` to a command
2025-02-16 22:20:37 -08:00
sig { params(eval_all: T::Boolean).returns(T::Array[Formula]) }
2023-09-02 19:35:22 +09:00
def self.all(eval_all: false)
if !eval_all && !Homebrew::EnvConfig.eval_all?
2024-02-04 15:19:29 +01:00
raise ArgumentError, "Formula#all without `--eval-all` or HOMEBREW_EVAL_ALL"
end
(core_names + tap_files).filter_map do |name_or_file|
Formulary.factory(name_or_file)
rescue FormulaUnavailableError, FormulaUnreadableError => e
# Don't let one broken formula break commands. But do complain.
onoe "Failed to import: #{name_or_file}"
$stderr.puts e
nil
end
end
2012-08-10 16:05:30 -04:00
# An array of all racks currently installed.
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Pathname]) }
def self.racks
Formula.cache[:racks] ||= if HOMEBREW_CELLAR.directory?
HOMEBREW_CELLAR.subdirs.reject do |rack|
rack.symlink? || rack.basename.to_s.start_with?(".") || rack.subdirs.empty?
end
else
[]
end
2012-08-10 16:05:30 -04:00
end
# An array of all currently installed formula names.
sig { returns(T::Array[String]) }
def self.installed_formula_names
racks.map { |rack| rack.basename.to_s }
end
# An array of all installed {Formula}
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Formula]) }
def self.installed
Formula.cache[:installed] ||= racks.flat_map do |rack|
Formulary.from_rack(rack)
rescue
[]
end.uniq(&:name)
end
2025-02-16 22:20:37 -08:00
sig { params(alias_path: T.nilable(Pathname)).returns(T::Array[Formula]) }
def self.installed_with_alias_path(alias_path)
return [] if alias_path.nil?
2018-09-17 02:45:00 +02:00
installed.select { |f| f.installed_alias_path == alias_path }
end
2015-09-27 15:57:37 +08:00
# an array of all alias files of core {Formula}
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Pathname]) }
2015-09-27 15:57:37 +08:00
def self.core_alias_files
CoreTap.instance.alias_files
2015-09-27 15:57:37 +08:00
end
# an array of all core aliases
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.core_aliases
CoreTap.instance.aliases
end
# an array of all tap aliases
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.tap_aliases
2025-02-16 22:20:37 -08:00
@tap_aliases ||= T.let(Tap.reject(&:core_tap?).flat_map(&:aliases).sort, T.nilable(T::Array[String]))
end
# an array of all aliases
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def self.aliases
2025-02-16 22:20:37 -08:00
@aliases ||= T.let((core_aliases + tap_aliases.map do |name|
name.split("/").fetch(-1)
end).uniq.sort, T.nilable(T::Array[String]))
2015-09-13 17:23:17 +08:00
end
# an array of all aliases as fully-qualified names
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
2015-09-13 17:23:17 +08:00
def self.alias_full_names
2025-02-16 22:20:37 -08:00
@alias_full_names ||= T.let(core_aliases + tap_aliases, T.nilable(T::Array[String]))
end
2021-06-19 00:14:33 +01:00
# Returns a list of approximately matching formula names, but not the complete match
2025-02-16 22:20:37 -08:00
sig { params(name: String).returns(T::Array[String]) }
2021-06-19 00:14:33 +01:00
def self.fuzzy_search(name)
2025-02-16 22:20:37 -08:00
@spell_checker ||= T.let(DidYouMean::SpellChecker.new(dictionary: Set.new(names + full_names).to_a),
T.nilable(DidYouMean::SpellChecker))
T.cast(@spell_checker.correct(name), T::Array[String])
2021-06-19 00:14:33 +01:00
end
2025-02-16 22:20:37 -08:00
sig { params(name: T.any(Pathname, String)).returns(Formula) }
def self.[](name)
Formulary.factory(name)
end
2015-12-06 22:16:08 +08:00
# True if this formula is provided by Homebrew itself
sig { returns(T::Boolean) }
2015-12-06 22:16:08 +08:00
def core_formula?
!!tap&.core_tap?
2013-10-29 15:46:10 -04:00
end
2015-12-06 22:16:08 +08:00
# True if this formula is provided by external Tap
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2015-12-06 22:16:08 +08:00
def tap?
2017-09-24 20:12:58 +01:00
return false unless tap
2018-09-17 02:45:00 +02:00
2023-07-24 14:01:53 -07:00
!T.must(tap).core_tap?
end
# True if this formula can be installed on this platform
# Redefined in extend/os.
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def valid_platform?
requirements.none?(MacOSRequirement) && requirements.none?(LinuxRequirement)
end
2025-02-16 22:20:37 -08:00
sig { params(options: T::Hash[Symbol, String]).void }
def print_tap_action(options = {})
2016-09-23 22:02:23 +02:00
return unless tap?
2018-09-17 02:45:00 +02:00
2016-09-23 22:02:23 +02:00
verb = options[:verb] || "Installing"
ohai "#{verb} #{name} from #{tap}"
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(String)) }
def tap_git_head
tap&.git_head
rescue TapUnavailableError
nil
end
delegate env: :"self.class"
# !attr[r] conflicts
2024-04-22 21:05:48 +02:00
# @api internal
sig { returns(T::Array[FormulaConflict]) }
2025-02-16 22:20:37 -08:00
def conflicts = T.must(self.class.conflicts)
# Returns a list of Dependency objects in an installable order, which
# means if a depends on b then b will be ordered before a in this list
2024-04-22 21:05:48 +02:00
#
# @api internal
2025-02-16 22:20:37 -08:00
sig { params(block: T.nilable(T.proc.params(arg0: Formula, arg1: Dependency).void)).returns(T::Array[Dependency]) }
def recursive_dependencies(&block)
cache_key = "Formula#recursive_dependencies" unless block
2024-03-07 16:20:20 +00:00
Dependency.expand(self, cache_key:, &block)
end
# The full set of Requirements for this formula's dependency tree.
2024-04-22 21:05:48 +02:00
#
# @api internal
2025-02-16 22:20:37 -08:00
sig { params(block: T.nilable(T.proc.params(arg0: Formula, arg1: Requirement).void)).returns(Requirements) }
def recursive_requirements(&block)
cache_key = "Formula#recursive_requirements" unless block
2024-03-07 16:20:20 +00:00
Requirement.expand(self, cache_key:, &block)
end
# Returns a Keg for the opt_prefix or installed_prefix if they exist.
# If not, return `nil`.
sig { returns(T.nilable(Keg)) }
def any_installed_keg
Formula.cache[:any_installed_keg] ||= {}
Formula.cache[:any_installed_keg][full_name] ||= if (installed_prefix = any_installed_prefix)
Keg.new(installed_prefix)
end
end
2024-04-22 21:05:48 +02:00
# Get the path of any installed prefix.
#
# @api internal
sig { returns(T.nilable(Pathname)) }
def any_installed_prefix
if optlinked? && opt_prefix.exist?
opt_prefix
elsif (latest_installed_prefix = installed_prefixes.last)
latest_installed_prefix
end
end
# Returns the {PkgVersion} for this formula if it is installed.
# If not, return `nil`.
sig { returns(T.nilable(PkgVersion)) }
2020-08-31 10:59:27 -07:00
def any_installed_version
any_installed_keg&.version
end
# Returns a list of Dependency objects that are required at runtime.
2024-04-22 21:05:48 +02:00
#
# @api internal
2025-02-16 22:20:37 -08:00
sig { params(read_from_tab: T::Boolean, undeclared: T::Boolean).returns(T::Array[Dependency]) }
def runtime_dependencies(read_from_tab: true, undeclared: true)
deps = if read_from_tab && undeclared &&
(tab_deps = any_installed_keg&.runtime_dependencies)
tab_deps.filter_map do |d|
full_name = d["full_name"]
next unless full_name
2018-09-17 02:45:00 +02:00
Dependency.new full_name
end
end
begin
deps ||= declared_runtime_dependencies unless undeclared
deps ||= (declared_runtime_dependencies | undeclared_runtime_dependencies)
rescue FormulaUnavailableError
onoe "Could not get runtime dependencies from #{path}!"
deps ||= []
end
deps
end
# Returns a list of {Formula} objects that are required at runtime.
2025-02-16 22:20:37 -08:00
sig { params(read_from_tab: T::Boolean, undeclared: T::Boolean).returns(T::Array[Formula]) }
def runtime_formula_dependencies(read_from_tab: true, undeclared: true)
2020-08-02 13:43:32 +01:00
cache_key = "#{full_name}-#{read_from_tab}-#{undeclared}"
Formula.cache[:runtime_formula_dependencies] ||= {}
Formula.cache[:runtime_formula_dependencies][cache_key] ||= runtime_dependencies(
2024-03-07 16:20:20 +00:00
read_from_tab:,
undeclared:,
).filter_map do |d|
d.to_formula
rescue FormulaUnavailableError
nil
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Formula]) }
def runtime_installed_formula_dependents
# `any_installed_keg` and `runtime_dependencies` `select`s ensure
# that we don't end up with something `Formula#runtime_dependencies` can't
# read from a `Tab`.
Formula.cache[:runtime_installed_formula_dependents] ||= {}
2020-08-02 13:43:32 +01:00
Formula.cache[:runtime_installed_formula_dependents][full_name] ||= Formula.installed
.select(&:any_installed_keg)
2020-08-02 13:43:32 +01:00
.select(&:runtime_dependencies)
.select do |f|
f.runtime_formula_dependencies.any? do |dep|
full_name == dep.full_name
rescue
name == dep.name
end
end
end
# Returns a list of formulae depended on by this formula that aren't
# installed.
2025-02-16 22:20:37 -08:00
sig { params(hide: T::Array[String]).returns(T::Array[Formula]) }
def missing_dependencies(hide: [])
runtime_formula_dependencies.select do |f|
hide.include?(f.name) || f.installed_prefixes.empty?
end
# If we're still getting unavailable formulae at this stage the best we can
# do is just return no results.
rescue FormulaUnavailableError
[]
end
sig { returns(T.nilable(String)) }
def ruby_source_path
2023-07-24 14:01:53 -07:00
path.relative_path_from(T.must(tap).path).to_s if tap && path.exist?
end
sig { returns(T.nilable(Checksum)) }
def ruby_source_checksum
Checksum.new(Digest::SHA256.file(path).hexdigest) if path.exist?
end
2025-02-16 22:20:37 -08:00
sig { params(dependables: T::Hash[Symbol, T.untyped]).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def merge_spec_dependables(dependables)
# We have a hash of specs names (stable/head) to dependency lists.
# Merge all of the dependency lists together, removing any duplicates.
all_dependables = [].union(*dependables.values.map(&:to_a))
all_dependables.map do |dependable|
{
2024-03-07 16:20:20 +00:00
dependable:,
# Now find the list of specs each dependency was a part of.
specs: dependables.filter_map { |spec, spec_deps| spec if spec_deps&.include?(dependable) },
}
end
end
private :merge_spec_dependables
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[String, T.untyped]) }
def to_hash
hsh = {
2018-11-02 17:18:07 +00:00
"name" => name,
"full_name" => full_name,
"tap" => tap&.name,
2023-04-27 04:09:28 +01:00
"oldnames" => oldnames,
2018-11-02 17:18:07 +00:00
"aliases" => aliases.sort,
"versioned_formulae" => versioned_formulae.map(&:name),
"desc" => desc,
"license" => SPDX.license_expression_to_string(license),
2018-11-02 17:18:07 +00:00
"homepage" => homepage,
"versions" => {
"stable" => stable&.version&.to_s,
2018-11-02 17:18:07 +00:00
"head" => head&.version&.to_s,
"bottle" => bottle_defined?,
},
"urls" => urls_hash,
2018-11-02 17:18:07 +00:00
"revision" => revision,
"version_scheme" => version_scheme,
"bottle" => {},
"pour_bottle_only_if" => self.class.pour_bottle_only_if&.to_s,
2018-11-02 17:18:07 +00:00
"keg_only" => keg_only?,
"keg_only_reason" => keg_only_reason&.to_hash,
2018-11-02 17:18:07 +00:00
"options" => [],
"build_dependencies" => [],
"dependencies" => [],
"test_dependencies" => [],
"recommended_dependencies" => [],
"optional_dependencies" => [],
"uses_from_macos" => [],
"uses_from_macos_bounds" => [],
"requirements" => serialized_requirements,
2018-11-02 17:18:07 +00:00
"conflicts_with" => conflicts.map(&:name),
"conflicts_with_reasons" => conflicts.map(&:reason),
"link_overwrite" => self.class.link_overwrite_paths.to_a,
"caveats" => caveats_with_placeholders,
2024-03-29 23:07:38 +00:00
"installed" => T.let([], T::Array[T::Hash[String, T.untyped]]),
2018-11-02 17:18:07 +00:00
"linked_keg" => linked_version&.to_s,
"pinned" => pinned?,
"outdated" => outdated?,
"deprecated" => deprecated?,
"deprecation_date" => deprecation_date,
"deprecation_reason" => deprecation_reason,
"deprecation_replacement" => deprecation_replacement,
"disabled" => disabled?,
"disable_date" => disable_date,
"disable_reason" => disable_reason,
"disable_replacement" => disable_replacement,
"post_install_defined" => post_install_defined?,
"service" => (service.to_hash if service?),
"tap_git_head" => tap_git_head,
"ruby_source_path" => ruby_source_path,
2023-02-19 02:03:59 +00:00
"ruby_source_checksum" => {},
}
hsh["bottle"]["stable"] = bottle_hash if stable && bottle_defined?
2023-02-06 13:04:16 +00:00
hsh["options"] = options.map do |opt|
{ "option" => opt.flag, "description" => opt.description }
end
hsh.merge!(dependencies_hash)
hsh["installed"] = installed_kegs.sort_by(&:scheme_and_version).map do |keg|
tab = keg.tab
{
2018-11-02 17:18:07 +00:00
"version" => keg.version.to_s,
"used_options" => tab.used_options.as_flags,
"built_as_bottle" => tab.built_as_bottle,
"poured_from_bottle" => tab.poured_from_bottle,
"time" => tab.time,
2018-11-02 17:18:07 +00:00
"runtime_dependencies" => tab.runtime_dependencies,
"installed_as_dependency" => tab.installed_as_dependency,
2018-11-02 17:18:07 +00:00
"installed_on_request" => tab.installed_on_request,
}
end
if (source_checksum = ruby_source_checksum)
2023-02-19 02:03:59 +00:00
hsh["ruby_source_checksum"] = {
"sha256" => source_checksum.hexdigest,
2023-02-19 02:03:59 +00:00
}
end
hsh
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[String, T.untyped]) }
2025-02-05 23:36:57 -08:00
def to_hash_with_variations
hash = to_hash
# Take from API, merging in local install status.
2023-05-15 13:58:33 +02:00
if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api?
json_formula = Homebrew::API::Formula.all_formulae.fetch(name).dup
2023-05-05 01:26:59 +02:00
return json_formula.merge(
hash.slice("name", "installed", "linked_keg", "pinned", "outdated"),
)
end
variations = {}
2023-09-28 14:05:09 +01:00
if path.exist? && on_system_blocks_exist?
2023-05-13 22:35:08 +02:00
formula_contents = path.read
OnSystem::ALL_OS_ARCH_COMBINATIONS.each do |os, arch|
2024-03-07 16:20:20 +00:00
bottle_tag = Utils::Bottles::Tag.new(system: os, arch:)
2023-05-13 22:35:08 +02:00
next unless bottle_tag.valid_combination?
2024-03-07 16:20:20 +00:00
Homebrew::SimulateSystem.with(os:, arch:) do
2025-02-05 23:36:57 -08:00
variations_namespace = Formulary.class_s("Variations#{bottle_tag.to_sym.capitalize}")
2023-05-13 22:35:08 +02:00
variations_formula_class = Formulary.load_formula(name, path, formula_contents, variations_namespace,
flags: self.class.build_flags, ignore_errors: true)
variations_formula = variations_formula_class.new(name, path, :stable,
2024-03-07 16:20:20 +00:00
alias_path:, force_bottle:)
2023-05-13 22:35:08 +02:00
2025-02-05 23:36:57 -08:00
variations_formula.to_hash.each do |key, value|
2023-05-13 22:35:08 +02:00
next if value.to_s == hash[key].to_s
variations[bottle_tag.to_sym] ||= {}
variations[bottle_tag.to_sym][key] = value
end
end
end
end
2025-02-05 23:36:57 -08:00
hash["variations"] = variations
hash
end
# Returns the bottle information for a formula.
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[String, T.untyped]) }
2025-02-05 23:36:57 -08:00
def bottle_hash
hash = {}
stable_spec = stable
return hash unless stable_spec
return hash unless bottle_defined?
bottle_spec = stable_spec.bottle_specification
2025-02-05 23:36:57 -08:00
hash["rebuild"] = bottle_spec.rebuild
hash["root_url"] = bottle_spec.root_url
hash["files"] = {}
bottle_spec.collector.each_tag do |tag|
tag_spec = bottle_spec.collector.specification_for(tag, no_older_versions: true)
os_cellar = tag_spec.cellar
2021-08-13 13:49:52 +01:00
os_cellar = os_cellar.inspect if os_cellar.is_a?(Symbol)
checksum = tag_spec.checksum.hexdigest
file_hash = {}
file_hash["cellar"] = os_cellar
2025-02-05 23:36:57 -08:00
filename = Bottle::Filename.create(self, tag, bottle_spec.rebuild)
path, = Utils::Bottles.path_resolved_basename(bottle_spec.root_url, name, checksum, filename)
file_hash["url"] = "#{bottle_spec.root_url}/#{path}"
file_hash["sha256"] = checksum
hash["files"][tag.to_sym] = file_hash
end
hash
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) }
def urls_hash
hash = {}
if stable
stable_spec = T.must(stable)
hash["stable"] = {
"url" => stable_spec.url,
"tag" => stable_spec.specs[:tag],
"revision" => stable_spec.specs[:revision],
"using" => (stable_spec.using if stable_spec.using.is_a?(Symbol)),
"checksum" => stable_spec.checksum&.to_s,
}
end
if head
hash["head"] = {
"url" => T.must(head).url,
"branch" => T.must(head).specs[:branch],
"using" => (T.must(head).using if T.must(head).using.is_a?(Symbol)),
}
end
hash
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[T::Hash[String, T.untyped]]) }
def serialized_requirements
requirements = self.class.spec_syms.to_h do |sym|
[sym, send(sym)&.requirements]
end
merge_spec_dependables(requirements).map do |data|
req = data[:dependable]
req_name = req.name.dup
req_name.prepend("maximum_") if req.respond_to?(:comparator) && req.comparator == "<="
req_version = if req.respond_to?(:version)
req.version
elsif req.respond_to?(:arch)
req.arch
end
{
"name" => req_name,
"cask" => req.cask,
"download" => req.download,
"version" => req_version,
"contexts" => req.tags,
"specs" => data[:specs],
}
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(String)) }
def caveats_with_placeholders
caveats&.gsub(HOMEBREW_PREFIX, HOMEBREW_PREFIX_PLACEHOLDER)
&.gsub(HOMEBREW_CELLAR, HOMEBREW_CELLAR_PLACEHOLDER)
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[String, T.untyped]) }
def dependencies_hash
# Create a hash of spec names (stable/head) to the list of dependencies under each
dependencies = self.class.spec_syms.to_h do |sym|
[sym, send(sym)&.declared_deps]
end
# Implicit dependencies are only needed when installing from source
# since they are only used to download and unpack source files.
# @see DependencyCollector
dependencies.transform_values! { |deps| deps&.reject(&:implicit?) }
hash = {}
dependencies.each do |spec_sym, spec_deps|
next if spec_deps.nil?
dep_hash = if spec_sym == :stable
hash
else
next if spec_deps == dependencies[:stable]
hash["#{spec_sym}_dependencies"] ||= {}
end
dep_hash["build_dependencies"] = spec_deps.select(&:build?)
.reject(&:uses_from_macos?)
.map(&:name)
.uniq
dep_hash["dependencies"] = spec_deps.reject(&:optional?)
.reject(&:recommended?)
.reject(&:build?)
.reject(&:test?)
.reject(&:uses_from_macos?)
.map(&:name)
.uniq
dep_hash["test_dependencies"] = spec_deps.select(&:test?)
.reject(&:uses_from_macos?)
.map(&:name)
.uniq
dep_hash["recommended_dependencies"] = spec_deps.select(&:recommended?)
.reject(&:uses_from_macos?)
.map(&:name)
.uniq
dep_hash["optional_dependencies"] = spec_deps.select(&:optional?)
.reject(&:uses_from_macos?)
.map(&:name)
.uniq
uses_from_macos_deps = spec_deps.select(&:uses_from_macos?).uniq
dep_hash["uses_from_macos"] = uses_from_macos_deps.map do |dep|
if dep.tags.length >= 2
{ dep.name => dep.tags }
elsif dep.tags.present?
{ dep.name => dep.tags.first }
else
dep.name
end
end
dep_hash["uses_from_macos_bounds"] = uses_from_macos_deps.map(&:bounds)
end
hash
end
2025-02-16 22:20:37 -08:00
sig { params(spec_symbol: Symbol).returns(T.nilable(T::Hash[String, T.untyped])) }
def internal_dependencies_hash(spec_symbol)
raise ArgumentError, "Unsupported spec: #{spec_symbol}" unless [:stable, :head].include?(spec_symbol)
return unless (spec = public_send(spec_symbol))
spec.declared_deps.each_with_object({}) do |dep, dep_hash|
# Implicit dependencies are only needed when installing from source
# since they are only used to download and unpack source files.
# @see DependencyCollector
next if dep.implicit?
metadata_hash = {}
metadata_hash[:tags] = dep.tags if dep.tags.present?
metadata_hash[:uses_from_macos] = dep.bounds.presence if dep.uses_from_macos?
dep_hash[dep.name] = metadata_hash.presence
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(T::Boolean)) }
2023-09-28 14:05:09 +01:00
def on_system_blocks_exist?
self.class.on_system_blocks_exist? || @on_system_blocks_exist
end
2024-07-14 11:42:22 -04:00
sig {
params(
verify_download_integrity: T::Boolean,
timeout: T.nilable(T.any(Integer, Float)),
quiet: T::Boolean,
).returns(Pathname)
}
def fetch(verify_download_integrity: true, timeout: nil, quiet: false)
odeprecated "Formula#fetch", "Resource#fetch on Formula#resource"
2024-07-14 11:42:22 -04:00
active_spec.fetch(verify_download_integrity:, timeout:, quiet:)
end
2025-02-16 22:20:37 -08:00
sig { params(filename: T.any(Pathname, String)).void }
def verify_download_integrity(filename)
odeprecated "Formula#verify_download_integrity", "Resource#verify_download_integrity on Formula#resource"
active_spec.verify_download_integrity(filename)
end
2025-02-16 22:20:37 -08:00
sig { params(keep_tmp: T::Boolean).returns(T.untyped) }
def run_test(keep_tmp: false)
2025-02-16 22:20:37 -08:00
@prefix_returns_versioned_prefix = T.let(true, T.nilable(T::Boolean))
test_env = {
2018-11-02 17:18:07 +00:00
TMPDIR: HOMEBREW_TEMP,
TEMP: HOMEBREW_TEMP,
TMP: HOMEBREW_TEMP,
TERM: "dumb",
PATH: PATH.new(ENV.fetch("PATH"), HOMEBREW_PREFIX/"bin"),
HOMEBREW_PATH: nil,
}.merge(common_stage_test_env)
2020-07-21 14:59:29 +01:00
test_env[:_JAVA_OPTIONS] += " -Djava.io.tmpdir=#{HOMEBREW_TEMP}"
ENV.clear_sensitive_environment!
2020-08-23 06:32:26 +02:00
Utils::Git.set_name_email!
mktemp("#{name}-test") do |staging|
staging.retain! if keep_tmp
2025-02-16 22:20:37 -08:00
@testpath = T.let(staging.tmpdir, T.nilable(Pathname))
test_env[:HOME] = @testpath
2025-02-16 22:20:37 -08:00
setup_home T.must(@testpath)
begin
2016-05-27 01:53:08 -04:00
with_logging("test") do
with_env(test_env) do
test
end
2016-05-27 01:53:08 -04:00
end
# Handle all possible exceptions running formula tests.
rescue Exception # rubocop:disable Lint/RescueException
staging.retain! if debug?
raise
end
end
2014-09-18 14:16:07 -05:00
ensure
2025-02-16 22:20:37 -08:00
@prefix_returns_versioned_prefix = T.let(false, T.nilable(T::Boolean))
@testpath = T.let(nil, T.nilable(Pathname))
end
2020-10-20 12:03:48 +02:00
sig { returns(T::Boolean) }
def test_defined?
2024-11-22 18:06:49 -08:00
method(:test).owner != Formula
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(T::Boolean)) }
def test; end
2014-09-18 14:16:07 -05:00
2025-02-16 22:20:37 -08:00
sig { params(file: T.any(Pathname, String)).returns(Pathname) }
def test_fixtures(file)
2017-06-01 16:06:51 +02:00
HOMEBREW_LIBRARY_PATH/"test/support/fixtures"/file
end
# This method is overridden in {Formula} subclasses to provide the
# installation instructions. The sources (from {.url}) are downloaded,
# hash-checked and then Homebrew changes into a temporary directory where the
# archive is unpacked or repository cloned.
#
# ### Example
#
# ```ruby
# def install
# system "./configure", "--prefix=#{prefix}"
# system "make", "install"
# end
# ```
2025-02-16 22:20:37 -08:00
sig { void }
def install; end
2022-08-07 15:55:37 +02:00
# Sometimes we have to change a bit before we install. Mostly we
# prefer a patch, but if you need the {Formula#prefix prefix} of
# this formula in the patch you have to resort to `inreplace`,
# because in the patch you don't have access to any variables
# defined by the formula, as only `HOMEBREW_PREFIX` is available
# in the {DATAPatch embedded patch}.
#
# ### Examples
#
2022-08-07 15:55:37 +02:00
# `inreplace` supports regular expressions:
#
# ```ruby
# inreplace "somefile.cfg", /look[for]what?/, "replace by #{bin}/tool"
# ```
2022-08-07 15:55:37 +02:00
#
# `inreplace` supports blocks:
#
# ```ruby
# inreplace "Makefile" do |s|
2022-08-07 15:55:37 +02:00
# s.gsub! "/usr/local", HOMEBREW_PREFIX.to_s
# end
# ```
2022-08-07 15:55:37 +02:00
#
# @see Utils::Inreplace.inreplace
# @api public
sig {
params(
paths: T.any(T::Enumerable[T.any(String, Pathname)], String, Pathname),
before: T.nilable(T.any(Pathname, Regexp, String)),
after: T.nilable(T.any(Pathname, String, Symbol)),
old_audit_result: T.nilable(T::Boolean),
audit_result: T::Boolean,
global: T::Boolean,
block: T.nilable(T.proc.params(s: StringInreplaceExtension).void),
).void
}
def inreplace(paths, before = nil, after = nil, old_audit_result = nil, audit_result: true, global: true, &block)
2024-09-02 16:02:35 +08:00
# NOTE: must check for `#nil?` and not `#blank?`, or else `old_audit_result = false` will not call `odeprecated`.
unless old_audit_result.nil?
odeprecated "inreplace(paths, before, after, #{old_audit_result})",
"inreplace(paths, before, after, audit_result: #{old_audit_result})"
audit_result = old_audit_result
end
Utils::Inreplace.inreplace(paths, before, after, audit_result:, global:, &block)
rescue Utils::Inreplace::Error => e
onoe e.to_s
2023-07-31 11:31:35 -07:00
raise BuildError.new(self, "inreplace", Array(paths), {})
end
protected
sig { params(home: Pathname).void }
def setup_home(home)
# Don't let bazel write to tmp directories we don't control or clean.
(home/".bazelrc").write "startup --output_user_root=#{home}/_bazel"
end
# Returns a list of Dependency objects that are declared in the formula.
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Dependency]) }
def declared_runtime_dependencies
cache_key = "Formula#declared_runtime_dependencies" unless build.any_args_or_options?
2024-03-07 16:20:20 +00:00
Dependency.expand(self, cache_key:) do |_, dependency|
Dependency.prune if dependency.build?
next if dependency.required?
2018-09-17 02:45:00 +02:00
if build.any_args_or_options?
Dependency.prune if build.without?(dependency)
elsif !dependency.recommended?
Dependency.prune
end
end
end
# Returns a list of Dependency objects that are not declared in the formula
# but the formula links to.
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Dependency]) }
def undeclared_runtime_dependencies
keg = any_installed_keg
return [] unless keg
2020-07-07 11:29:33 +01:00
CacheStoreDatabase.use(:linkage) do |db|
linkage_checker = LinkageChecker.new(keg, self, cache_db: db)
linkage_checker.undeclared_deps.map { |n| Dependency.new(n) }
end
end
public
# To call out to the system, we use the `system` method and we prefer
# you give the args separately as in the line below, otherwise a subshell
# has to be opened first.
#
# ### Examples
#
# ```ruby
# system "./bootstrap.sh", "--arg1", "--prefix=#{prefix}"
# ```
#
# For CMake and other build systems we have some necessary defaults in e.g.
# {#std_cmake_args}:
#
# ```ruby
# system "cmake", ".", *std_cmake_args
# ```
#
# If the arguments given to `configure` (or `make` or `cmake`) are depending
# on options defined above, we usually make a list first and then
# use the `args << if <condition>` to append each:
#
# ```ruby
# args = ["--with-option1", "--with-option2"]
2020-11-03 16:36:48 -05:00
# args << "--without-gcc" if ENV.compiler == :clang
# ```
#
# Most software still uses `configure` and `make`.
# Check with `./configure --help` for what our options are.
#
# ```ruby
# system "./configure", "--disable-debug", "--disable-dependency-tracking",
# "--disable-silent-rules", "--prefix=#{prefix}",
# *args # our custom arg list (needs `*` to unpack)
# ```
#
# If there is a "make install" available, please use it!
#
# ```ruby
# system "make", "install"
# ```
#
# @api public
sig { params(cmd: T.any(String, Pathname), args: T.any(String, Integer, Pathname, Symbol)).void }
def system(cmd, *args)
2020-04-05 15:44:50 +01:00
verbose_using_dots = Homebrew::EnvConfig.verbose_using_dots?
2015-09-23 15:40:27 +08:00
# remove "boring" arguments so that the important ones are more likely to
# be shown considering that we trim long ohai lines to the terminal width
pretty_args = args.dup
unless verbose?
case cmd
when "./configure"
pretty_args -= std_configure_args
when "cabal"
pretty_args -= std_cabal_v2_args
2020-06-22 13:24:41 +02:00
when "cargo"
pretty_args -= std_cargo_args
when "cmake"
pretty_args -= std_cmake_args
when "go"
pretty_args -= std_go_args
when "meson"
pretty_args -= std_meson_args
2025-02-21 14:53:34 +01:00
when "zig"
pretty_args -= std_zig_args
when %r{(^|/)(pip|python)(?:[23](?:\.\d{1,2})?)?$}
pretty_args -= std_pip_args
end
end
pretty_args.each_index do |i|
pretty_args[i] = "import setuptools..." if pretty_args[i].to_s.start_with? "import setuptools"
end
2017-06-01 16:06:51 +02:00
ohai "#{cmd} #{pretty_args * " "}".strip
2025-02-16 22:20:37 -08:00
@exec_count ||= T.let(0, T.nilable(Integer))
@exec_count += 1
2019-10-03 08:50:45 +02:00
logfn = format("#{logs}/#{active_log_prefix}%02<exec_count>d.%<cmd_base>s",
exec_count: @exec_count,
2020-11-30 16:27:49 +01:00
cmd_base: File.basename(cmd).split.first)
2015-04-25 22:07:06 -04:00
logs.mkpath
2014-12-30 23:52:07 -05:00
File.open(logfn, "w") do |log|
2014-09-05 15:36:08 -05:00
log.puts Time.now, "", cmd, args, ""
log.flush
if verbose?
2014-09-05 15:36:08 -05:00
rd, wr = IO.pipe
begin
pid = fork do
rd.close
log.close
exec_cmd(cmd, args, wr, logfn)
end
wr.close
2015-09-23 15:40:27 +08:00
if verbose_using_dots
last_dot = Time.at(0)
while (buf = rd.gets)
2015-09-23 15:40:27 +08:00
log.puts buf
# make sure dots printed with interval of at least 1 min.
2023-04-18 15:06:50 -07:00
next if (Time.now - last_dot) <= 60
2018-09-17 02:45:00 +02:00
print "."
$stdout.flush
last_dot = Time.now
2015-09-23 15:40:27 +08:00
end
puts
else
while (buf = rd.gets)
2015-09-23 15:40:27 +08:00
log.puts buf
puts buf
end
2014-09-05 15:36:08 -05:00
end
ensure
rd.close
end
2014-09-05 15:36:08 -05:00
else
2020-11-24 15:46:47 +01:00
pid = fork do
exec_cmd(cmd, args, log, logfn)
end
end
2025-02-16 22:20:37 -08:00
Process.wait(pid)
2014-09-05 15:36:08 -05:00
$stdout.flush
unless $CHILD_STATUS.success?
2023-03-13 00:59:08 +01:00
log_lines = Homebrew::EnvConfig.fail_log_lines
2014-09-05 15:36:08 -05:00
log.flush
if !verbose? || verbose_using_dots
puts "Last #{log_lines} lines from #{logfn}:"
2023-03-13 00:59:08 +01:00
Kernel.system "/usr/bin/tail", "-n", log_lines.to_s, logfn
end
2014-09-05 15:36:08 -05:00
log.puts
2014-12-30 23:47:07 -05:00
require "system_config"
require "build_environment"
2014-12-30 23:47:07 -05:00
env = ENV.to_hash
SystemConfig.dump_verbose_config(log)
2014-12-30 23:47:07 -05:00
log.puts
BuildEnvironment.dump env, log
2014-12-30 23:47:07 -05:00
raise BuildError.new(self, cmd, args, env)
2014-09-05 15:36:08 -05:00
end
end
end
sig { params(quiet: T::Boolean).returns(T::Array[Keg]) }
def eligible_kegs_for_cleanup(quiet: false)
eligible_for_cleanup = []
if latest_version_installed?
eligible_kegs = if head? && (head_prefix = latest_head_prefix)
head, stable = installed_kegs.partition { |keg| keg.version.head? }
# Remove newest head and stable kegs.
head - [Keg.new(head_prefix)] + T.must(stable.sort_by(&:scheme_and_version).slice(0...-1))
else
installed_kegs.select do |keg|
tab = keg.tab
if version_scheme > tab.version_scheme
true
elsif version_scheme == tab.version_scheme
pkg_version > keg.version
else
false
end
end
end
unless eligible_kegs.empty?
eligible_kegs.each do |keg|
if keg.linked?
opoo "Skipping (old) #{keg} due to it being linked" unless quiet
elsif pinned? && keg == Keg.new(@pin.path.resolved_path)
opoo "Skipping (old) #{keg} due to it being pinned" unless quiet
elsif (keepme = keg/".keepme") && keepme.exist? && keepme.readable? &&
(keepme_refs = keepme.readlines.map(&:strip).select { |ref| Pathname(ref).exist? }.presence)
opoo "Skipping #{keg} as it needed by #{keepme_refs.join(", ")}" unless quiet
else
eligible_for_cleanup << keg
end
end
end
elsif !installed_prefixes.empty? && !pinned?
# If the cellar only has one version installed, don't complain
# that we can't tell which one to keep. Don't complain at all if the
# only installed version is a pinned formula.
opoo "Skipping #{full_name}: most recent version #{pkg_version} not installed" unless quiet
end
eligible_for_cleanup
end
# Create a temporary directory then yield. When the block returns,
# recursively delete the temporary directory. Passing `opts[:retain]`
# or calling `do |staging| ... staging.retain!` in the block will skip
# the deletion and retain the temporary directory's contents.
sig {
params(
prefix: String,
retain: T::Boolean,
retain_in_cache: T::Boolean,
block: T.proc.params(arg0: Mktemp).void,
).void
}
def mktemp(prefix = name, retain: false, retain_in_cache: false, &block)
Mktemp.new(prefix, retain:, retain_in_cache:).run(&block)
end
# A version of `FileUtils.mkdir` that also changes to that folder in
# a block.
2025-02-16 22:20:37 -08:00
sig { params(name: T.any(String, Pathname), block: T.nilable(T.proc.void)).returns(T.untyped) }
2020-08-19 17:12:32 +01:00
def mkdir(name, &block)
result = FileUtils.mkdir_p(name)
2020-11-16 22:18:56 +01:00
return result unless block
2018-09-17 02:45:00 +02:00
2020-08-19 17:12:32 +01:00
FileUtils.chdir(name, &block)
end
# Runs `xcodebuild` without Homebrew's compiler environment variables set.
sig { params(args: T.any(String, Integer, Pathname, Symbol)).void }
def xcodebuild(*args)
removed = ENV.remove_cc_etc
2020-11-24 15:46:47 +01:00
begin
2024-08-18 16:28:17 -07:00
self.system("xcodebuild", *args)
2020-11-24 15:46:47 +01:00
ensure
ENV.update(removed)
end
end
2025-02-16 22:20:37 -08:00
sig { void }
def fetch_patches
patchlist.select(&:external?).each(&:fetch)
end
sig { void }
def fetch_bottle_tab
return unless bottled?
T.must(bottle).fetch_tab
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[String, T.untyped]) }
def bottle_tab_attributes
return {} unless bottled?
T.must(bottle).tab_attributes
end
private
2025-02-16 22:20:37 -08:00
sig { void }
def prepare_patches
patchlist.grep(DATAPatch) { |p| p.path = path }
end
# Returns the prefix for a given formula version number.
2025-02-16 22:20:37 -08:00
sig { params(version: T.any(String, Pathname, PkgVersion)).returns(Pathname) }
2025-02-17 14:17:08 -08:00
def versioned_prefix(version) = rack/version.to_s
2025-02-16 22:20:37 -08:00
sig {
params(
cmd: T.any(String, Pathname),
args: T::Array[T.any(String, Integer, Pathname, Symbol)],
out: IO,
logfn: T.nilable(String),
).void
}
def exec_cmd(cmd, args, out, logfn)
ENV["HOMEBREW_CC_LOG_PATH"] = logfn
2016-09-23 11:01:40 +02:00
ENV.remove_cc_etc if cmd.to_s.start_with? "xcodebuild"
# Turn on argument filtering in the superenv compiler wrapper.
# We should probably have a better mechanism for this than adding
# special cases to this method.
if cmd == "python"
setup_py_in_args = %w[setup.py build.py].include?(args.first)
setuptools_shim_in_args = args.any? { |a| a.to_s.start_with? "import setuptools" }
2024-05-31 15:49:12 -07:00
ENV.refurbish_args if setup_py_in_args || setuptools_shim_in_args
end
$stdout.reopen(out)
$stderr.reopen(out)
out.close
args.map!(&:to_s)
begin
2023-04-03 17:34:39 -07:00
Kernel.exec(cmd, *args)
rescue
nil
end
puts "Failed to execute: #{cmd}"
exit! 1 # never gets here unless exec threw or failed
end
# Common environment variables used at both build and test time.
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[Symbol, String]) }
def common_stage_test_env
{
_JAVA_OPTIONS: "-Duser.home=#{HOMEBREW_CACHE}/java_cache",
GOCACHE: "#{HOMEBREW_CACHE}/go_cache",
GOPATH: "#{HOMEBREW_CACHE}/go_mod_cache",
CARGO_HOME: "#{HOMEBREW_CACHE}/cargo_cache",
PIP_CACHE_DIR: "#{HOMEBREW_CACHE}/pip_cache",
CURL_HOME: ENV.fetch("CURL_HOME") { Dir.home },
PYTHONDONTWRITEBYTECODE: "1",
}
end
2025-02-16 22:20:37 -08:00
sig { params(interactive: T::Boolean, debug_symbols: T::Boolean, _block: T.proc.params(arg0: Mktemp).void).void }
def stage(interactive: false, debug_symbols: false, &_block)
2024-03-07 16:20:20 +00:00
active_spec.stage(debug_symbols:) do |staging|
2025-02-16 22:20:37 -08:00
@source_modified_time = T.let(active_spec.source_modified_time, T.nilable(Time))
@buildpath = T.let(Pathname.pwd, T.nilable(Pathname))
2023-07-24 14:01:53 -07:00
env_home = T.must(buildpath)/".brew_home"
mkdir_p env_home
stage_env = {
HOMEBREW_PATH: nil,
}
unless interactive
stage_env[:HOME] = env_home
stage_env.merge!(common_stage_test_env)
end
setup_home env_home
# Don't dirty the git tree for git clones.
(env_home/".gitignore").write "*"
ENV.clear_sensitive_environment!
begin
with_env(stage_env) do
yield staging
end
ensure
2025-02-16 22:20:37 -08:00
@buildpath = T.let(nil, T.nilable(Pathname))
end
end
end
# The methods below define the formula DSL.
class << self
include BuildEnvironment::DSL
include OnSystem::MacOSAndLinux
# Initialise instance variables for each subclass. These need to be initialised before the class is frozen,
# and some DSL may never be called so it can't be done lazily.
2025-02-16 22:20:37 -08:00
sig { params(child: T::Class[Formula]).void }
def inherited(child)
super
child.instance_eval do
# Ensure this is synced with `freeze`
2025-02-16 22:20:37 -08:00
@stable = T.let(SoftwareSpec.new(flags: build_flags), T.nilable(SoftwareSpec))
@head = T.let(HeadSoftwareSpec.new(flags: build_flags), T.nilable(HeadSoftwareSpec))
@livecheck = T.let(Livecheck.new(self), T.nilable(Livecheck))
@conflicts = T.let([], T.nilable(T::Array[FormulaConflict]))
@skip_clean_paths = T.let(Set.new, T.nilable(T::Set[T.any(String, Symbol)]))
@link_overwrite_paths = T.let(Set.new, T.nilable(T::Set[String]))
@loaded_from_api = T.let(false, T.nilable(T::Boolean))
2025-02-24 14:49:33 -08:00
@on_system_blocks_exist = T.let(false, T.nilable(T::Boolean))
2025-02-16 22:20:37 -08:00
@network_access_allowed = T.let(SUPPORTED_NETWORK_ACCESS_PHASES.to_h do |phase|
[phase, DEFAULT_NETWORK_ACCESS_ALLOWED]
2025-02-16 22:20:37 -08:00
end, T.nilable(T::Hash[Symbol, T::Boolean]))
end
end
2025-02-16 22:20:37 -08:00
sig { returns(T.self_type) }
def freeze
specs.each(&:freeze)
@livecheck.freeze
@conflicts.freeze
@skip_clean_paths.freeze
@link_overwrite_paths.freeze
super
end
2025-02-16 22:20:37 -08:00
sig { returns(T::Hash[Symbol, T::Boolean]) }
def network_access_allowed = T.must(@network_access_allowed)
2022-09-10 20:28:21 -04:00
# Whether this formula was loaded using the formulae.brew.sh API
2025-02-24 14:49:33 -08:00
sig { returns(T::Boolean) }
def loaded_from_api? = !!@loaded_from_api
2022-09-10 20:28:21 -04:00
2022-07-21 15:32:51 +02:00
# Whether this formula contains OS/arch-specific blocks
# (e.g. `on_macos`, `on_arm`, `on_monterey :or_older`, `on_system :linux, macos: :big_sur_or_newer`).
2025-02-24 14:49:33 -08:00
sig { returns(T::Boolean) }
def on_system_blocks_exist? = !!@on_system_blocks_exist
2022-07-21 15:32:51 +02:00
# The reason for why this software is not linked (by default) to {::HOMEBREW_PREFIX}.
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(KegOnlyReason)) }
attr_reader :keg_only_reason
2015-05-19 13:06:06 -04:00
# A one-line description of the software. Used by users to get an overview
# of the software and Homebrew maintainers.
# Shows when running `brew info`.
#
# ### Example
#
# ```ruby
# desc "Example formula"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
sig { params(val: String).returns(T.nilable(String)) }
def desc(val = T.unsafe(nil))
2025-02-23 13:18:49 -08:00
val.nil? ? @desc : @desc = T.let(val, T.nilable(String))
end
2015-05-19 13:06:06 -04:00
2020-06-10 12:29:25 -04:00
# The SPDX ID of the open-source license that the formula uses.
# Shows when running `brew info`.
#
2020-08-24 01:54:28 -04:00
# Use `:any_of`, `:all_of` or `:with` to describe complex license expressions.
# `:any_of` should be used when the user can choose which license to use.
# `:all_of` should be used when the user must use all licenses.
2020-08-18 10:58:32 -04:00
# `:with` should be used to specify a valid SPDX exception.
#
2020-08-18 10:58:32 -04:00
# Add `+` to an identifier to indicate that the formulae can be
# licensed under later versions of the same license.
#
# ### Examples
#
# ```ruby
# license "BSD-2-Clause"
# ```
#
# ```ruby
# license "EPL-1.0+"
# ```
#
# ```ruby
# license any_of: ["MIT", "GPL-2.0-only"]
# ```
#
# ```ruby
# license all_of: ["MIT", "GPL-2.0-only"]
# ```
#
# ```ruby
# license "GPL-2.0-only" => { with: "LLVM-exception" }
# ```
#
# ```ruby
# license :public_domain
# ```
#
# ```ruby
# license any_of: [
2020-08-24 01:54:28 -04:00
# "MIT",
# :public_domain,
# all_of: ["0BSD", "Zlib", "Artistic-1.0+"],
# "Apache-2.0" => { with: "LLVM-exception" },
# ]
# ```
2024-04-22 21:05:48 +02:00
#
# @see https://docs.brew.sh/License-Guidelines Homebrew License Guidelines
# @see https://spdx.github.io/spdx-spec/latest/annexes/spdx-license-expressions/ SPDX license expression guide
2024-04-22 21:05:48 +02:00
# @api public
2025-02-16 22:20:37 -08:00
sig {
params(args: T.any(NilClass, String, Symbol, T::Hash[T.any(String, Symbol), T.anything]))
.returns(T.any(NilClass, String, Symbol, T::Hash[T.any(String, Symbol), T.anything]))
}
2020-08-20 10:26:37 -04:00
def license(args = nil)
if args.nil?
@licenses
else
2025-02-16 22:20:37 -08:00
@licenses = T.let(args, T.any(NilClass, String, Symbol, T::Hash[T.any(String, Symbol), T.anything]))
2020-08-20 10:26:37 -04:00
end
end
# The phases for which network access is allowed. By default, network
# access is allowed for all phases. Valid phases are `:build`, `:test`,
# and `:postinstall`. When no argument is passed, network access will be
# allowed for all phases.
#
# ### Examples
#
# ```ruby
# allow_network_access!
# ```
#
# ```ruby
# allow_network_access! :build
# ```
#
# ```ruby
# allow_network_access! [:build, :test]
# ```
sig { params(phases: T.any(Symbol, T::Array[Symbol])).void }
def allow_network_access!(phases = [])
phases_array = Array(phases)
if phases_array.empty?
2025-02-16 22:20:37 -08:00
network_access_allowed.each_key { |phase| network_access_allowed[phase] = true }
else
phases_array.each do |phase|
raise ArgumentError, "Unknown phase: #{phase}" unless SUPPORTED_NETWORK_ACCESS_PHASES.include?(phase)
2025-02-16 22:20:37 -08:00
network_access_allowed[phase] = true
end
end
end
# The phases for which network access is denied. By default, network
# access is allowed for all phases. Valid phases are `:build`, `:test`,
# and `:postinstall`. When no argument is passed, network access will be
# denied for all phases.
#
# ### Examples
#
# ```ruby
# deny_network_access!
# ```
#
# ```ruby
# deny_network_access! :build
# ```
#
# ```ruby
# deny_network_access! [:build, :test]
# ```
sig { params(phases: T.any(Symbol, T::Array[Symbol])).void }
def deny_network_access!(phases = [])
phases_array = Array(phases)
if phases_array.empty?
2025-02-16 22:20:37 -08:00
network_access_allowed.each_key { |phase| network_access_allowed[phase] = false }
else
phases_array.each do |phase|
raise ArgumentError, "Unknown phase: #{phase}" unless SUPPORTED_NETWORK_ACCESS_PHASES.include?(phase)
2025-02-16 22:20:37 -08:00
network_access_allowed[phase] = false
end
end
end
# Whether the specified phase should be forced offline.
sig { params(phase: Symbol).returns(T::Boolean) }
def network_access_allowed?(phase)
raise ArgumentError, "Unknown phase: #{phase}" unless SUPPORTED_NETWORK_ACCESS_PHASES.include?(phase)
env_var = Homebrew::EnvConfig.send(:"formula_#{phase}_network")
2025-02-16 22:20:37 -08:00
env_var.nil? ? network_access_allowed[phase] : env_var == "allow"
end
# The homepage for the software. Used by users to get more information
# about the software and Homebrew maintainers as a point of contact for
# e.g. submitting patches.
# Can be opened with running `brew home`.
#
# ### Example
#
# ```ruby
# homepage "https://www.example.com"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
sig { params(val: String).returns(T.nilable(String)) }
def homepage(val = T.unsafe(nil))
2025-02-23 13:18:49 -08:00
val.nil? ? @homepage : @homepage = T.let(val, T.nilable(String))
end
# Checks whether a `livecheck` specification is defined or not.
#
# It returns `true` when a `livecheck` block is present in the {Formula}
# and `false` otherwise.
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
def livecheck_defined?
@livecheck_defined == true
end
# Checks whether a `livecheck` specification is defined or not. This is a
# legacy alias for `#livecheck_defined?`.
#
# It returns `true` when a `livecheck` block is present in the {Formula}
# and `false` otherwise.
sig { returns(T::Boolean) }
2020-03-16 01:37:49 +05:30
def livecheckable?
# odeprecated "`livecheckable?`", "`livecheck_defined?`"
@livecheck_defined == true
2020-03-16 01:37:49 +05:30
end
# Checks whether a service specification is defined or not.
#
# It returns `true` when a service block is present in the {Formula}
# and `false` otherwise.
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2020-12-11 23:14:50 +01:00
def service?
@service_block.present?
2020-12-11 23:14:50 +01:00
end
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(T::Array[FormulaConflict])) }
attr_reader :conflicts
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(T::Set[T.any(String, Symbol)])) }
attr_reader :skip_clean_paths
sig { returns(T.nilable(T::Set[String])) }
attr_reader :link_overwrite_paths
sig { returns(T.nilable(Symbol)) }
attr_reader :pour_bottle_only_if
# If `pour_bottle?` returns `false` the user-visible reason to display for
# why they cannot use the bottle.
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(String)) }
attr_accessor :pour_bottle_check_unsatisfied_reason
# Used for creating new Homebrew versions of software without new upstream
# versions. For example, if we bump the major version of a library that this
# {Formula} {.depends_on} then we may need to update the `revision` of this
# {Formula} to install a new version linked against the new library version.
# `0` if unset.
#
# ### Example
#
# ```ruby
# revision 1
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
sig { params(val: Integer).returns(T.nilable(Integer)) }
def revision(val = T.unsafe(nil))
2025-02-23 13:18:49 -08:00
val.nil? ? @revision : @revision = T.let(val, T.nilable(Integer))
end
# Used for creating new Homebrew version schemes. For example, if we want
2016-08-11 09:54:47 +02:00
# to change version scheme from one to another, then we may need to update
# `version_scheme` of this {Formula} to be able to use new version scheme,
# e.g. to move from 20151020 scheme to 1.0.0 we need to increment
2016-08-11 10:00:39 +02:00
# `version_scheme`. Without this, the prior scheme will always equate to a
# higher version.
2016-08-11 09:54:47 +02:00
# `0` if unset.
#
# ### Example
#
# ```ruby
# version_scheme 1
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
sig { params(val: Integer).returns(T.nilable(Integer)) }
def version_scheme(val = T.unsafe(nil))
2025-02-23 13:18:49 -08:00
val.nil? ? @version_scheme : @version_scheme = T.let(val, T.nilable(Integer))
end
2016-08-11 09:54:47 +02:00
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[Symbol]) }
2025-02-17 11:37:57 -08:00
def spec_syms = [:stable, :head].freeze
# A list of the {.stable} and {.head} {SoftwareSpec}s.
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[SoftwareSpec]) }
2013-09-17 21:25:39 -05:00
def specs
spec_syms.map do |sym|
send(sym)
end.freeze
2013-09-17 21:25:39 -05:00
end
# The URL used to download the source for the {.stable} version of the formula.
# We prefer `https` for security and proxy reasons.
2022-11-06 22:50:07 -05:00
# If not inferable, specify the download strategy with `using: ...`.
#
# - `:git`, `:hg`, `:svn`, `:bzr`, `:fossil`, `:cvs`,
2020-11-03 16:36:48 -05:00
# - `:curl` (normal file download, will also extract)
# - `:nounzip` (without extracting)
# - `:post` (download via an HTTP POST)
#
# ### Examples
#
# ```ruby
# url "https://packed.sources.and.we.prefer.https.example.com/archive-1.2.3.tar.bz2"
# ```
#
# ```ruby
# url "https://some.dont.provide.archives.example.com",
2020-11-03 16:36:48 -05:00
# using: :git,
# tag: "1.2.3",
# revision: "db8e4de5b2d6653f66aea53094624468caad15d2"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(val: String, specs: T::Hash[Symbol, T.any(String, Symbol)]).void }
2025-02-17 11:37:57 -08:00
def url(val, specs = {}) = stable.url(val, specs)
# The version string for the {.stable} version of the formula.
# The version is autodetected from the URL and/or tag so only needs to be
# declared if it cannot be autodetected correctly.
#
# ### Example
#
# ```ruby
# version "1.2-final"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-17 14:17:08 -08:00
sig { params(val: T.nilable(String)).returns(T.nilable(Version)) }
2025-02-17 11:37:57 -08:00
def version(val = nil) = stable.version(val)
# Additional URLs for the {.stable} version of the formula.
# These are only used if the {.url} fails to download. It's optional and
# there can be more than one. Generally we add them when the main {.url}
# is unreliable. If {.url} is really unreliable then we may swap the
# {.mirror} and {.url}.
#
# ### Example
#
# ```ruby
# mirror "https://in.case.the.host.is.down.example.com"
# mirror "https://in.case.the.mirror.is.down.example.com
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(val: String).void }
2025-02-17 11:37:57 -08:00
def mirror(val) = stable.mirror(val)
# @scope class
# To verify the cached download's integrity and security we verify the
# SHA-256 hash matches what we've declared in the {Formula}. To quickly fill
# this value you can leave it blank and run `brew fetch --force` and it'll
# tell you the currently valid value.
#
# ### Example
#
# ```ruby
# sha256 "2a2ba417eebaadcb4418ee7b12fe2998f26d6e6f7fda7983412ff66a741ab6f7"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(val: String).void }
2025-02-17 11:37:57 -08:00
def sha256(val) = stable.sha256(val)
# Adds a {.bottle} {SoftwareSpec}.
# This provides a pre-built binary package built by the Homebrew maintainers for you.
# It will be installed automatically if there is a binary package for your platform
# and you haven't passed or previously used any options on this formula.
#
# If you maintain your own repository, you can add your own bottle links.
2020-11-03 12:39:26 -05:00
# @see https://docs.brew.sh/Bottles Bottles
# You can ignore this block entirely if submitting to Homebrew/homebrew-core.
# It'll be handled for you by the Brew Test Bot.
#
# ```ruby
# bottle do
# root_url "https://example.com" # Optional root to calculate bottle URLs.
# rebuild 1 # Marks the old bottle as outdated without bumping the version/revision of the formula.
# # Optionally specify the HOMEBREW_CELLAR in which the bottles were built.
# sha256 cellar: "/brew/Cellar", catalina: "ef65c759c5097a36323fa9c77756468649e8d1980a3a4e05695c05e39568967c"
# sha256 cellar: :any, mojave: "28f4090610946a4eb207df102d841de23ced0d06ba31cb79e040d883906dcd4f"
# sha256 high_sierra: "91dd0caca9bd3f38c439d5a7b6f68440c4274945615fae035ff0a369264b8a2f"
# end
# ```
2015-09-14 19:51:04 +08:00
#
# Homebrew maintainers aim to bottle all formulae.
2024-04-22 21:05:48 +02:00
#
# @api public
2022-08-31 19:47:19 +01:00
sig { params(block: T.proc.bind(BottleSpecification).void).void }
2025-02-17 11:37:57 -08:00
def bottle(&block) = stable.bottle(&block)
2025-02-25 08:59:53 -08:00
sig { returns(BuildOptions) }
2025-02-17 11:37:57 -08:00
def build = stable.build
2020-07-30 10:10:42 +02:00
# Get the `BUILD_FLAGS` from the formula's namespace set in `Formulary::load_formula`.
2025-02-16 22:20:37 -08:00
sig { returns(T::Array[String]) }
def build_flags
2020-11-24 15:46:47 +01:00
namespace = T.must(to_s.split("::")[0..-2]).join("::")
2020-07-30 10:10:42 +02:00
return [] if namespace.empty?
2020-07-30 10:10:42 +02:00
mod = const_get(namespace)
mod.const_get(:BUILD_FLAGS)
end
# Allows adding {.depends_on} and {Patch}es just to the {.stable} {SoftwareSpec}.
# This is required instead of using a conditional.
2022-11-06 22:50:07 -05:00
# It is preferable to also pull the {url} and {sha256= sha256} into the block if one is added.
#
# ### Example
#
# ```ruby
# stable do
# url "https://example.com/foo-1.0.tar.gz"
# sha256 "2a2ba417eebaadcb4418ee7b12fe2998f26d6e6f7fda7983412ff66a741ab6f7"
#
# depends_on "libxml2"
# depends_on "libffi"
# end
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(block: T.nilable(T.proc.returns(SoftwareSpec))).returns(T.untyped) }
def stable(&block)
2025-02-16 22:20:37 -08:00
return T.must(@stable) unless block
2018-09-17 02:45:00 +02:00
2025-02-16 22:20:37 -08:00
T.must(@stable).instance_eval(&block)
end
# Adds a {.head} {SoftwareSpec}.
# This can be installed by passing the `--HEAD` option to allow
# installing software directly from a branch of a version-control repository.
# If called as a method this provides just the {url} for the {SoftwareSpec}.
# If a block is provided you can also add {.depends_on} and {Patch}es just to the {.head} {SoftwareSpec}.
# The download strategies (e.g. `:using =>`) are the same as for {url}.
2020-11-03 16:36:48 -05:00
# `master` is the default branch and doesn't need stating with a `branch:` parameter.
#
# ### Example
#
# ```ruby
# head "https://we.prefer.https.over.git.example.com/.git"
# ```
#
# ```ruby
# head "https://example.com/.git", branch: "name_of_branch"
# ```
#
# or (if autodetect fails):
#
# ```ruby
# head "https://hg.is.awesome.but.git.has.won.example.com/", using: :hg
# ```
2025-02-16 22:20:37 -08:00
sig {
params(val: T.nilable(String), specs: T::Hash[Symbol, T.untyped], block: T.nilable(T.proc.void))
.returns(T.untyped)
}
def head(val = nil, specs = {}, &block)
2020-11-16 22:18:56 +01:00
if block
2025-02-16 22:20:37 -08:00
T.must(@head).instance_eval(&block)
2013-09-13 11:13:12 -05:00
elsif val
2025-02-16 22:20:37 -08:00
T.must(@head).url(val, specs)
2013-09-13 11:13:12 -05:00
else
@head
end
2012-04-05 21:09:24 -05:00
end
# Additional downloads can be defined as {resource}s and accessed in the
# install method. Resources can also be defined inside a {.stable} or
# {.head} block. This mechanism replaces ad-hoc "subformula" classes.
#
# ### Example
#
# ```ruby
# resource "additional_files" do
# url "https://example.com/additional-stuff.tar.gz"
# sha256 "c6bc3f48ce8e797854c4b865f6a8ff969867bbcaebd648ae6fd825683e59fef2"
# end
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2024-05-03 21:47:49 +02:00
sig { params(name: String, klass: T.class_of(Resource), block: T.nilable(T.proc.bind(Resource).void)).void }
def resource(name, klass = Resource, &block)
specs.each do |spec|
spec.resource(name, klass, &block) unless spec.resource_defined?(name)
end
end
# Specify a Go resource.
#
2024-04-22 21:05:48 +02:00
# @api public
2025-02-16 22:20:37 -08:00
sig { params(name: String, block: T.nilable(T.proc.void)).void }
def go_resource(name, &block)
odisabled "`Formula.go_resource`", "Go modules"
specs.each { |spec| spec.go_resource(name, &block) }
end
# The dependencies for this formula. Use strings for the names of other
# formulae. Homebrew provides some `:special` {Requirement}s for stuff
# that needs extra handling (often changing some ENV vars or
# deciding whether to use the system provided version).
#
# ### Examples
#
# `:build` means this dependency is only needed during build.
#
# ```ruby
# depends_on "cmake" => :build
# ```
#
# `:test` means this dependency is only needed during testing.
#
# ```ruby
# depends_on "node" => :test
# ```
#
# `:recommended` dependencies are built by default.
# But a `--without-...` option is generated to opt-out.
#
# ```ruby
# depends_on "readline" => :recommended
# ```
#
# `:optional` dependencies are NOT built by default unless the
# auto-generated `--with-...` option is passed.
#
# ```ruby
# depends_on "glib" => :optional
# ```
#
# If you need to specify that another formula has to be built with/out
# certain options (note, no `--` needed before the option):
#
# ```ruby
# depends_on "zeromq" => "with-pgm"
# depends_on "qt" => ["with-qtdbus", "developer"] # Multiple options.
# ```
#
2023-02-10 23:15:40 -05:00
# Optional and enforce that "boost" is built using `--with-c++11`.
#
# ```ruby
# depends_on "boost" => [:optional, "with-c++11"]
# ```
#
# If a dependency is only needed in certain cases:
#
# ```ruby
# depends_on "sqlite" if MacOS.version >= :catalina
2020-11-03 16:36:48 -05:00
# depends_on xcode: :build # If the formula really needs full Xcode to compile.
# depends_on macos: :mojave # Needs at least macOS Mojave (10.14) to run.
# ```
#
# It is possible to only depend on something if
# `build.with?` or `build.without? "another_formula"`:
#
# ```ruby
# depends_on "postgresql" if build.without? "sqlite"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-17 14:17:08 -08:00
sig { params(dep: T.any(String, Symbol, T::Hash[String, T.untyped], T::Class[Requirement])).void }
def depends_on(dep)
2013-09-21 19:27:24 -05:00
specs.each { |spec| spec.depends_on(dep) }
end
# Indicates use of dependencies provided by macOS.
2021-10-23 21:32:38 -04:00
# On macOS this is a no-op (as we use the provided system libraries) unless
# `:since` specifies a minimum macOS version.
# On Linux this will act as {.depends_on}.
2024-04-22 21:05:48 +02:00
#
# @api public
sig {
params(
2024-02-13 00:42:54 +01:00
dep: T.any(String, T::Hash[T.any(String, Symbol), T.any(Symbol, T::Array[Symbol])]),
bounds: T::Hash[Symbol, Symbol],
).void
}
def uses_from_macos(dep, bounds = {})
specs.each { |spec| spec.uses_from_macos(dep, bounds) }
end
# Options can be used as arguments to `brew install`.
# To switch features on/off: `"with-something"` or `"with-otherthing"`.
# To use other software: `"with-other-software"` or `"without-foo"`.
# Note that for {.depends_on} that are `:optional` or `:recommended`, options
# are generated automatically.
#
# There are also some special options:
#
# - `:universal`: build a universal binary/library (e.g. on newer Intel Macs
# this means a combined x86_64/x86 binary/library).
#
# ### Examples
#
# ```ruby
# option "with-spam", "The description goes here without a dot at the end"
# ```
#
# ```ruby
# option "with-qt", "Text here overwrites what's autogenerated by 'depends_on "qt" => :optional'"
# ```
#
# ```ruby
# option :universal
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(name: String, description: String).void }
def option(name, description = "")
2013-09-21 19:27:24 -05:00
specs.each { |spec| spec.option(name, description) }
end
# Deprecated options are used to rename options and migrate users who used
# them to newer ones. They are mostly used for migrating non-`with` options
# (e.g. `enable-debug`) to `with` options (e.g. `with-debug`).
#
# ### Example
#
# ```ruby
# deprecated_option "enable-debug" => "with-debug"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(hash: T::Hash[String, String]).void }
def deprecated_option(hash)
specs.each { |spec| spec.deprecated_option(hash) }
end
# External patches can be declared using resource-style blocks.
#
# ### Examples
#
# ```ruby
# patch do
# url "https://example.com/example_patch.diff"
# sha256 "c6bc3f48ce8e797854c4b865f6a8ff969867bbcaebd648ae6fd825683e59fef2"
# end
# ```
#
# A strip level of `-p1` is assumed. It can be overridden using a symbol
# argument:
#
# ```ruby
# patch :p0 do
# url "https://example.com/example_patch.diff"
# sha256 "c6bc3f48ce8e797854c4b865f6a8ff969867bbcaebd648ae6fd825683e59fef2"
# end
# ```
#
# Patches can be declared in stable and head blocks. This form is
# preferred over using conditionals.
#
# ```ruby
# stable do
# patch do
# url "https://example.com/example_patch.diff"
# sha256 "c6bc3f48ce8e797854c4b865f6a8ff969867bbcaebd648ae6fd825683e59fef2"
# end
# end
# ```
#
# Embedded (`__END__`) patches are declared like so:
#
# ```ruby
# patch :DATA
# patch :p0, :DATA
# ```
#
# Patches can also be embedded by passing a string. This makes it possible
# to provide multiple embedded patches while making only some of them
# conditional.
2024-04-22 21:05:48 +02:00
#
# ```ruby
# patch :p0, "..."
# ```
#
# @see https://docs.brew.sh/Formula-Cookbook#patches Patches
2024-04-22 21:05:48 +02:00
# @api public
2025-02-16 22:20:37 -08:00
sig {
params(strip: T.any(String, Symbol), src: T.any(NilClass, String, Symbol), block: T.nilable(T.proc.void)).void
}
def patch(strip = :p1, src = nil, &block)
specs.each { |spec| spec.patch(strip, src, &block) }
end
2020-11-03 16:36:48 -05:00
# One or more formulae that conflict with this one and why.
#
# ### Example
#
# ```ruby
# conflicts_with "imagemagick", because: "both install `convert` binaries"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(names: T.untyped).void }
def conflicts_with(*names)
2025-02-16 22:20:37 -08:00
opts = T.let(names.last.is_a?(Hash) ? names.pop : {}, T::Hash[Symbol, T.untyped])
names.each { |name| T.must(conflicts) << FormulaConflict.new(name, opts[:because]) }
end
# Skip cleaning paths in a formula.
#
# Sometimes the formula {Cleaner cleaner} breaks things.
#
# ### Examples
#
# Preserve cleaned paths with:
#
# ```ruby
# skip_clean "bin/foo", "lib/bar"
# ```
#
# Keep .la files with:
#
# ```ruby
# skip_clean :la
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(paths: T.any(String, Symbol)).returns(T::Set[T.any(String, Symbol)]) }
def skip_clean(*paths)
2013-06-20 16:18:01 -05:00
paths.flatten!
2014-02-23 12:09:28 -08:00
# Specifying :all is deprecated and will become an error
2025-02-16 22:20:37 -08:00
T.must(skip_clean_paths).merge(paths)
end
# Software that will not be symlinked into the `brew --prefix` and will
# only live in its Cellar. Other formulae can depend on it and Homebrew
# will add the necessary includes, libraries and other paths while
# building that other formula.
#
# Keg-only formulae are not in your PATH and are not seen by compilers
# if you build your own software outside of Homebrew. This way, we
# don't shadow software provided by macOS.
#
# ### Examples
#
# ```ruby
# keg_only :provided_by_macos
# ```
#
# ```ruby
# keg_only :versioned_formulae
# ```
#
# ```ruby
# keg_only "because I want it so"
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(reason: T.any(String, Symbol), explanation: String).void }
def keg_only(reason, explanation = "")
2025-02-16 22:20:37 -08:00
@keg_only_reason = T.let(KegOnlyReason.new(reason, explanation), T.nilable(KegOnlyReason))
2010-07-18 10:38:45 -07:00
end
# Pass `:skip` to this method to disable post-install stdlib checking.
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(check_type: Symbol).void }
def cxxstdlib_check(check_type)
define_method(:skip_cxxstdlib_check?) { true } if check_type == :skip
end
# Marks the {Formula} as failing with a particular compiler so it will fall back to others.
#
# ### Examples
#
# For Apple compilers, this should be in the format:
#
# ```ruby
# fails_with :clang do
# build 600
# cause "multiple configure and compile errors"
# end
# ```
#
# The block may be omitted and if present, the build may be omitted;
# if so, then the compiler will not be allowed for *all* versions.
#
# `major_version` should be the major release number only, for instance
# '7' for the GCC 7 series (7.0, 7.1, etc.).
# If `version` or the block is omitted, then the compiler will
# not be allowed for all compilers in that series.
#
# For example, if a bug is only triggered on GCC 7.1 but is not
# encountered on 7.2:
2014-01-04 12:49:48 +00:00
#
# ```ruby
# fails_with :gcc => '7' do
# version '7.1'
# end
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-17 14:17:08 -08:00
sig { params(compiler: T.any(Symbol, T::Hash[Symbol, String]), block: T.nilable(T.proc.void)).void }
def fails_with(compiler, &block)
specs.each { |spec| spec.fails_with(compiler, &block) }
end
# Marks the {Formula} as needing a certain standard, so Homebrew
# will fall back to other compilers if the default compiler
# does not implement that standard.
#
# We generally prefer to {.depends_on} a desired compiler and to
# explicitly use that compiler in a formula's {#install} block,
# rather than implicitly finding a suitable compiler with `needs`.
#
# @see .fails_with
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(standards: String).void }
def needs(*standards)
specs.each { |spec| spec.needs(*standards) }
end
# A test is required for new formulae and makes us happy.
#
# The block will create, run in and delete a temporary directory.
#
# We want tests that don't require any user input
# and test the basic functionality of the application.
# For example, `foo build-foo input.foo` is a good test
# and `foo --version` or `foo --help` are bad tests.
# However, a bad test is better than no test at all.
#
# ### Examples
#
# ```ruby
# (testpath/"test.file").write <<~EOS
# writing some test file, if you need to
# EOS
# assert_equal "OK", shell_output("test_command test.file").strip
# ```
#
# Need complete control over stdin, stdout?
#
# ```ruby
# require "open3"
# Open3.popen3("#{bin}/example", "argument") do |stdin, stdout, _|
# stdin.write("some text")
# stdin.close
# assert_equal "result", stdout.read
# end
# ```
#
# The test will fail if it returns false, or if an exception is raised.
# Failed assertions and failed `system` commands will raise exceptions.
2024-04-22 21:05:48 +02:00
#
# @see https://docs.brew.sh/Formula-Cookbook#add-a-test-to-the-formula Tests
# @return [Boolean]
2024-04-22 21:05:48 +02:00
# @api public
2025-02-16 22:20:37 -08:00
sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }
2025-02-17 11:37:57 -08:00
def test(&block) = define_method(:test, &block)
# {Livecheck} can be used to check for newer versions of the software.
# This method evaluates the DSL specified in the `livecheck` block of the
# {Formula} (if it exists) and sets the instance variables of a {Livecheck}
# object accordingly. This is used by `brew livecheck` to check for newer
2020-03-16 01:37:49 +05:30
# versions of the software.
#
# ### Example
#
# ```ruby
# livecheck do
2020-03-16 01:37:49 +05:30
# skip "Not maintained"
# url "https://example.com/foo/releases"
# regex /foo-(\d+(?:\.\d+)+)\.tar/
# end
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig { params(block: T.nilable(T.proc.bind(Livecheck).returns(T.untyped))).returns(T.untyped) }
2020-03-16 01:37:49 +05:30
def livecheck(&block)
2020-11-16 22:18:56 +01:00
return @livecheck unless block
2020-03-16 01:37:49 +05:30
2025-02-16 22:20:37 -08:00
@livecheck_defined = T.let(true, T.nilable(T::Boolean))
2020-03-16 01:37:49 +05:30
@livecheck.instance_eval(&block)
end
2020-12-11 23:14:50 +01:00
# Service can be used to define services.
# This method evaluates the DSL specified in the service block of the
# {Formula} (if it exists) and sets the instance variables of a Service
# object accordingly. This is used by `brew install` to generate a service file.
2020-12-11 23:14:50 +01:00
#
# ### Example
#
# ```ruby
# service do
2020-12-11 23:14:50 +01:00
# run [opt_bin/"foo"]
# end
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-17 11:37:57 -08:00
sig { params(block: T.nilable(T.proc.returns(T.untyped))).returns(T.nilable(T.proc.returns(T.untyped))) }
2020-12-11 23:14:50 +01:00
def service(&block)
return @service_block unless block
2020-12-11 23:14:50 +01:00
2025-02-16 22:20:37 -08:00
@service_block = T.let(block, T.nilable(T.proc.returns(T.untyped)))
2020-12-11 23:14:50 +01:00
end
# Defines whether the {Formula}'s bottle can be used on the given Homebrew
# installation.
#
# ### Examples
#
# If the bottle requires the Xcode CLT to be installed a
# {Formula} would declare:
#
# ```ruby
# pour_bottle? do
# reason "The bottle needs the Xcode CLT to be installed."
# satisfy { MacOS::CLT.installed? }
# end
# ```
#
# If `satisfy` returns `false` then a bottle will not be used and instead
# the {Formula} will be built from source and `reason` will be printed.
#
# Alternatively, a preset reason can be passed as a symbol:
#
# ```ruby
# pour_bottle? only_if: :clt_installed
# ```
2024-04-22 21:05:48 +02:00
#
# @api public
2025-02-16 22:20:37 -08:00
sig {
params(
only_if: T.nilable(Symbol),
block: T.nilable(T.proc.params(arg0: T.untyped).returns(T.any(T::Boolean, Symbol))),
2025-02-17 14:17:08 -08:00
).void
2025-02-16 22:20:37 -08:00
}
def pour_bottle?(only_if: nil, &block)
2025-02-16 22:20:37 -08:00
@pour_bottle_check = T.let(PourBottleCheck.new(self), T.nilable(PourBottleCheck))
@pour_bottle_only_if = T.let(only_if, T.nilable(Symbol))
if only_if.present? && block.present?
raise ArgumentError, "Do not pass both a preset condition and a block to `pour_bottle?`"
end
block ||= case only_if
when :clt_installed
lambda do |_|
on_macos do
2023-07-24 22:08:17 -07:00
T.bind(self, PourBottleCheck)
reason(+<<~EOS)
The bottle needs the Xcode Command Line Tools to be installed at /Library/Developer/CommandLineTools.
Development tools provided by Xcode.app are not sufficient.
You can install the Xcode Command Line Tools, if desired, with:
xcode-select --install
EOS
2023-07-24 22:08:17 -07:00
satisfy { MacOS::CLT.installed? }
end
end
when :default_prefix
lambda do |_|
2023-07-24 22:08:17 -07:00
T.bind(self, PourBottleCheck)
reason(<<~EOS)
The bottle (and many others) needs to be installed into #{Homebrew::DEFAULT_PREFIX}.
2021-07-20 09:07:09 +08:00
EOS
2023-07-24 22:08:17 -07:00
satisfy { HOMEBREW_PREFIX.to_s == Homebrew::DEFAULT_PREFIX }
end
else
raise ArgumentError, "Invalid preset `pour_bottle?` condition" if only_if.present?
end
2025-02-16 22:20:37 -08:00
@pour_bottle_check.instance_eval(&T.unsafe(block))
end
# Deprecates a {Formula} (on the given date) so a warning is
# shown on each installation. If the date has not yet passed the formula
# will not be deprecated.
#
# ### Examples
#
# ```ruby
# deprecate! date: "2020-08-27", because: :unmaintained
# ```
#
# ```ruby
# deprecate! date: "2020-08-27", because: "has been replaced by foo"
# ```
#
# ```ruby
# deprecate! date: "2020-08-27", because: "has been replaced by foo", replacement: "foo"
# ```
#
# @see https://docs.brew.sh/Deprecating-Disabling-and-Removing-Formulae
# @see DeprecateDisable::FORMULA_DEPRECATE_DISABLE_REASONS
2024-04-22 21:05:48 +02:00
# @api public
2025-02-16 22:20:37 -08:00
sig { params(date: String, because: T.any(NilClass, String, Symbol), replacement: T.nilable(String)).void }
def deprecate!(date:, because:, replacement: nil)
2025-02-16 22:20:37 -08:00
@deprecation_date = T.let(Date.parse(date), T.nilable(Date))
return if T.must(@deprecation_date) > Date.today
2020-08-27 16:40:12 -04:00
2025-02-16 22:20:37 -08:00
@deprecation_reason = T.let(because, T.any(NilClass, String, Symbol))
@deprecation_replacement = T.let(replacement, T.nilable(String))
T.must(@deprecated = T.let(true, T.nilable(T::Boolean)))
2020-04-01 13:42:52 +01:00
end
# Whether this {Formula} is deprecated (i.e. warns on installation).
# Defaults to false.
# @see .deprecate!
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2020-04-01 13:42:52 +01:00
def deprecated?
@deprecated == true
end
# The date that this {Formula} was or becomes deprecated.
# Returns `nil` if no date is specified.
#
# @see .deprecate!
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(Date)) }
attr_reader :deprecation_date
2020-08-27 10:42:57 -04:00
# The reason for deprecation of a {Formula}.
#
2020-11-05 15:19:56 -05:00
# @return [nil] if no reason was provided or the formula is not deprecated.
# @see .deprecate!
2025-02-16 22:20:37 -08:00
sig { returns(T.any(NilClass, String, Symbol)) }
2020-08-27 10:42:57 -04:00
attr_reader :deprecation_reason
# The replacement for a deprecated {Formula}.
#
# @return [nil] if no replacement was provided or the formula is not deprecated.
# @see .deprecate!
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(String)) }
attr_reader :deprecation_replacement
# Disables a {Formula} (on the given date) so it cannot be
# installed. If the date has not yet passed the formula
# will be deprecated instead of disabled.
#
# ### Examples
#
# ```ruby
# disable! date: "2020-08-27", because: :does_not_build
# ```
#
# ```ruby
# disable! date: "2020-08-27", because: "has been replaced by foo"
# ```
#
# ```ruby
# disable! date: "2020-08-27", because: "has been replaced by foo", replacement: "foo"
# ```
#
# @see https://docs.brew.sh/Deprecating-Disabling-and-Removing-Formulae
# @see DeprecateDisable::FORMULA_DEPRECATE_DISABLE_REASONS
2024-04-22 21:05:48 +02:00
# @api public
2025-02-16 22:20:37 -08:00
sig { params(date: String, because: T.any(NilClass, String, Symbol), replacement: T.nilable(String)).void }
def disable!(date:, because:, replacement: nil)
2025-02-16 22:20:37 -08:00
@disable_date = T.let(Date.parse(date), T.nilable(Date))
2025-02-16 22:20:37 -08:00
if T.must(@disable_date) > Date.today
@deprecation_reason = T.let(because, T.any(NilClass, String, Symbol))
@deprecation_replacement = T.let(replacement, T.nilable(String))
@deprecated = T.let(true, T.nilable(T::Boolean))
return
end
2020-04-01 13:42:52 +01:00
2025-02-16 22:20:37 -08:00
@disable_reason = T.let(because, T.nilable(T.any(String, Symbol)))
@disable_replacement = T.let(replacement, T.nilable(String))
@disabled = T.let(true, T.nilable(T::Boolean))
2020-04-01 13:42:52 +01:00
end
# Whether this {Formula} is disabled (i.e. cannot be installed).
# Defaults to false.
#
# @see .disable!
2023-07-23 21:05:02 -07:00
sig { returns(T::Boolean) }
2020-04-01 13:42:52 +01:00
def disabled?
@disabled == true
end
# The date that this {Formula} was or becomes disabled.
# Returns `nil` if no date is specified.
#
# @see .disable!
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(Date)) }
attr_reader :disable_date
# The reason this {Formula} is disabled.
2020-08-27 10:42:57 -04:00
# Returns `nil` if no reason was provided or the formula is not disabled.
#
# @see .disable!
2025-02-16 22:20:37 -08:00
sig { returns(T.any(NilClass, String, Symbol)) }
2020-08-27 10:42:57 -04:00
attr_reader :disable_reason
# The replacement for a disabled {Formula}.
# Returns `nil` if no reason was provided or the formula is not disabled.
#
# @see .disable!
2025-02-16 22:20:37 -08:00
sig { returns(T.nilable(String)) }
attr_reader :disable_replacement
# Permit overwriting certain files while linking.
#
# ### Examples
#
# Sometimes we accidentally install files outside the prefix. Once we fix that,
# users will get a link conflict error. Overwrite those files with:
#
# ```ruby
# link_overwrite "bin/foo", "lib/bar"
# ```
#
# ```ruby
# link_overwrite "share/man/man1/baz-*"
# ```
2025-02-16 22:20:37 -08:00
sig { params(paths: String).returns(T::Set[String]) }
def link_overwrite(*paths)
paths.flatten!
2025-02-16 22:20:37 -08:00
T.must(link_overwrite_paths).merge(paths)
end
end
end
require "extend/os/formula"