397 lines
10 KiB
Ruby
Raw Normal View History

2020-11-20 14:20:38 +01:00
# typed: true
# frozen_string_literal: true
require "compilers"
require "development_tools"
2013-11-27 17:29:06 -06:00
# Homebrew extends Ruby's `ENV` to make our code more readable.
# Implemented in {SharedEnvExtension} and either {Superenv} or
# {Stdenv} (depending on the build mode).
# @see Superenv
# @see Stdenv
2018-10-03 21:03:22 +00:00
# @see https://www.rubydoc.info/stdlib/Env Ruby's ENV API
2013-08-19 12:32:59 -05:00
module SharedEnvExtension
2020-11-20 14:20:38 +01:00
extend T::Sig
include CompilerConstants
CC_FLAG_VARS = %w[CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS].freeze
2020-11-20 14:20:38 +01:00
private_constant :CC_FLAG_VARS
FC_FLAG_VARS = %w[FCFLAGS FFLAGS].freeze
2020-11-20 14:20:38 +01:00
private_constant :FC_FLAG_VARS
SANITIZED_VARS = %w[
CDPATH CLICOLOR_FORCE
CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH OBJC_INCLUDE_PATH
CC CXX OBJC OBJCXX CPP MAKE LD LDSHARED
CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS
MACOSX_DEPLOYMENT_TARGET SDKROOT DEVELOPER_DIR
CMAKE_PREFIX_PATH CMAKE_INCLUDE_PATH CMAKE_FRAMEWORK_PATH
GOBIN GOPATH GOROOT PERL_MB_OPT PERL_MM_OPT
LIBRARY_PATH LD_LIBRARY_PATH LD_PRELOAD LD_RUN_PATH
].freeze
2020-11-20 14:20:38 +01:00
private_constant :SANITIZED_VARS
sig {
2020-11-20 14:20:38 +01:00
params(
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
).void
}
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil)
@formula = formula
@cc = cc
@build_bottle = build_bottle
@bottle_arch = bottle_arch
reset
end
2020-11-20 14:20:38 +01:00
private :setup_build_environment
2020-11-20 14:20:38 +01:00
sig { void }
def reset
SANITIZED_VARS.each { |k| delete(k) }
end
2020-11-20 14:20:38 +01:00
private :reset
2020-11-20 14:20:38 +01:00
sig { returns(T::Hash[String, String]) }
2013-08-19 12:32:59 -05:00
def remove_cc_etc
keys = %w[CC CXX OBJC OBJCXX LD CPP CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS]
2020-11-20 14:20:38 +01:00
keys.map { |key| [key, delete(key)] }.to_h
2013-08-19 12:32:59 -05:00
end
2020-11-20 14:20:38 +01:00
sig { params(newflags: String).void }
def append_to_cflags(newflags)
2013-08-19 12:32:59 -05:00
append(CC_FLAG_VARS, newflags)
end
2020-11-20 14:20:38 +01:00
sig { params(val: T.any(Regexp, String)).void }
def remove_from_cflags(val)
2013-08-19 12:32:59 -05:00
remove CC_FLAG_VARS, val
end
2020-11-20 14:20:38 +01:00
sig { params(value: String).void }
def append_to_cccfg(value)
2018-07-12 19:59:06 +01:00
append("HOMEBREW_CCCFG", value, "")
end
2020-11-20 14:20:38 +01:00
sig { params(keys: T.any(String, T::Array[String]), value: T.untyped, separator: String).void }
def append(keys, value, separator = " ")
2013-08-19 12:32:59 -05:00
value = value.to_s
Array(keys).each do |key|
2020-11-20 14:20:38 +01:00
old_value = self[key]
2020-12-01 17:04:59 +00:00
self[key] = if old_value.blank?
2020-11-20 14:20:38 +01:00
value
else
2020-11-20 14:20:38 +01:00
old_value + separator + value
2013-08-19 12:32:59 -05:00
end
end
end
2020-11-20 14:20:38 +01:00
sig { params(keys: T.any(String, T::Array[String]), value: T.untyped, separator: String).void }
def prepend(keys, value, separator = " ")
value = value.to_s
2013-08-19 12:32:59 -05:00
Array(keys).each do |key|
2020-11-20 14:20:38 +01:00
old_value = self[key]
2020-12-01 17:04:59 +00:00
self[key] = if old_value.blank?
2020-03-13 21:15:06 +00:00
value
else
2020-11-20 14:20:38 +01:00
value + separator + old_value
2013-08-19 12:32:59 -05:00
end
end
end
2013-08-19 17:21:13 -05:00
2020-12-06 14:17:57 +11:00
sig { params(key: String, path: T.any(String, Pathname)).void }
def append_path(key, path)
2017-04-27 10:44:44 +02:00
self[key] = PATH.new(self[key]).append(path)
2013-08-19 17:21:13 -05:00
end
# Prepends a directory to `PATH`.
# Is the formula struggling to find the pkgconfig file? Point it to it.
# This is done automatically for keg-only formulae.
# <pre>ENV.prepend_path "PKG_CONFIG_PATH", "#{Formula["glib"].opt_lib}/pkgconfig"</pre>
# Prepending a system path such as /usr/bin is a no-op so that requirements
# don't accidentally override superenv shims or formulae's `bin` directories.
# <pre>ENV.prepend_path "PATH", which("emacs").dirname</pre>
2020-12-06 14:17:57 +11:00
sig { params(key: String, path: T.any(String, Pathname)).void }
def prepend_path(key, path)
return if %w[/usr/bin /bin /usr/sbin /sbin].include? path.to_s
2018-09-17 02:45:00 +02:00
2017-04-27 10:44:44 +02:00
self[key] = PATH.new(self[key]).prepend(path)
2013-08-19 12:32:59 -05:00
end
2013-08-19 17:21:13 -05:00
2020-11-20 14:20:38 +01:00
sig { params(key: String, path: T.any(String, Pathname)).void }
def prepend_create_path(key, path)
2020-11-20 14:20:38 +01:00
path = Pathname(path)
path.mkpath
prepend_path key, path
end
2020-11-20 14:20:38 +01:00
sig { params(keys: T.any(String, T::Array[String]), value: T.untyped).void }
def remove(keys, value)
return if value.nil?
2018-09-17 02:45:00 +02:00
2013-08-19 12:32:59 -05:00
Array(keys).each do |key|
2020-11-20 14:20:38 +01:00
old_value = self[key]
next if old_value.nil?
2018-09-17 02:45:00 +02:00
2020-11-20 14:20:38 +01:00
new_value = old_value.sub(value, "")
if new_value.empty?
delete(key)
else
self[key] = new_value
end
end
2013-08-19 12:32:59 -05:00
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def cc
self["CC"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def cxx
self["CXX"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def cflags
self["CFLAGS"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def cxxflags
self["CXXFLAGS"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def cppflags
self["CPPFLAGS"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def ldflags
self["LDFLAGS"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def fc
self["FC"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def fflags
self["FFLAGS"]
end
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
def fcflags
self["FCFLAGS"]
end
2013-08-19 12:32:59 -05:00
# Outputs the current compiler.
# <pre># Do something only for the system clang
# if ENV.compiler == :clang
# # modify CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS in one go:
# ENV.append_to_cflags "-I ./missing/includes"
# end</pre>
2020-11-20 14:20:38 +01:00
sig { returns(T.any(Symbol, String)) }
def compiler
@compiler ||= if (cc = @cc)
2020-09-18 22:31:23 +02:00
warn_about_non_apple_gcc(cc) if cc.match?(GNU_GCC_REGEXP)
fetch_compiler(cc, "--cc")
elsif (cc = homebrew_cc)
2020-09-18 22:31:23 +02:00
warn_about_non_apple_gcc(cc) if cc.match?(GNU_GCC_REGEXP)
compiler = fetch_compiler(cc, "HOMEBREW_CC")
if @formula
compilers = [compiler] + CompilerSelector.compilers
compiler = CompilerSelector.select_for(@formula, compilers)
end
compiler
elsif @formula
CompilerSelector.select_for(@formula)
else
DevelopmentTools.default_compiler
end
end
2020-11-20 14:20:38 +01:00
sig { returns(T.any(String, Pathname)) }
def determine_cc
COMPILER_SYMBOL_MAP.invert.fetch(compiler, compiler)
end
2020-11-20 14:20:38 +01:00
private :determine_cc
2014-06-22 18:43:01 -05:00
COMPILERS.each do |compiler|
define_method(compiler) do
@compiler = compiler
2020-11-20 14:20:38 +01:00
send(:cc=, send(:determine_cc))
send(:cxx=, send(:determine_cxx))
end
end
# Snow Leopard defines an NCURSES value the opposite of most distros.
# @see https://bugs.python.org/issue6848
2020-11-20 14:20:38 +01:00
sig { void }
2013-08-19 12:32:59 -05:00
def ncurses_define
2021-01-25 09:18:10 +00:00
odeprecated "ENV.ncurses_define"
append "CPPFLAGS", "-DNCURSES_OPAQUE=0"
2013-08-19 12:32:59 -05:00
end
# @private
2020-11-20 14:20:38 +01:00
sig { void }
2013-08-19 12:32:59 -05:00
def userpaths!
2021-01-25 09:18:10 +00:00
odeprecated "ENV.userpaths!"
2017-04-28 15:07:46 +02:00
path = PATH.new(self["PATH"]).select do |p|
# put Superenv.bin and opt path at the first
p.start_with?("#{HOMEBREW_REPOSITORY}/Library/ENV", "#{HOMEBREW_PREFIX}/opt")
end
path.append(HOMEBREW_PREFIX/"bin") # XXX hot fix to prefer brewed stuff (e.g. python) over /usr/bin.
path.append(self["PATH"]) # reset of self["PATH"]
path.append(
# user paths
ORIGINAL_PATHS.map do |p|
p.realpath.to_s
rescue
nil
2017-04-28 15:07:46 +02:00
end - %w[/usr/X11/bin /opt/X11/bin],
)
self["PATH"] = path
2013-08-19 12:32:59 -05:00
end
2020-11-20 14:20:38 +01:00
sig { void }
2013-08-19 12:32:59 -05:00
def fortran
# Ignore repeated calls to this function as it will misleadingly warn about
# building with an alternative Fortran compiler without optimization flags,
# despite it often being the Homebrew-provided one set up in the first call.
return if @fortran_setup_done
2018-09-17 02:45:00 +02:00
@fortran_setup_done = true
flags = []
2013-08-30 19:00:19 -05:00
if fc
ohai "Building with an alternative Fortran compiler", "This is unsupported."
self["F77"] ||= fc
else
if (gfortran = which("gfortran", (HOMEBREW_PREFIX/"bin").to_s))
2021-01-26 15:21:24 -05:00
ohai "Using Homebrew-provided Fortran compiler"
2017-04-27 10:44:44 +02:00
elsif (gfortran = which("gfortran", PATH.new(ORIGINAL_PATHS)))
2021-01-26 15:21:24 -05:00
ohai "Using a Fortran compiler found at #{gfortran}"
end
if gfortran
puts "This may be changed by setting the FC environment variable."
self["FC"] = self["F77"] = gfortran
flags = FC_FLAG_VARS
end
2013-08-19 12:32:59 -05:00
end
flags.each { |key| self[key] = cflags }
set_cpu_flags(flags)
2013-08-19 12:32:59 -05:00
end
# @private
2020-11-20 14:20:38 +01:00
sig { returns(Symbol) }
def effective_arch
if @build_bottle && @bottle_arch
@bottle_arch.to_sym
else
Hardware.oldest_cpu
end
end
# @private
2020-11-20 14:20:38 +01:00
sig { params(name: String).returns(Formula) }
def gcc_version_formula(name)
version = name[GNU_GCC_REGEXP, 1]
gcc_version_name = "gcc@#{version}"
gcc = Formulary.factory("gcc")
if gcc.version_suffix == version
gcc
else
2015-06-21 21:18:24 -04:00
Formulary.factory(gcc_version_name)
end
end
# @private
2020-11-20 14:20:38 +01:00
sig { params(name: String).void }
def warn_about_non_apple_gcc(name)
begin
gcc_formula = gcc_version_formula(name)
2015-06-22 21:08:27 -04:00
rescue FormulaUnavailableError => e
2017-10-15 02:28:32 +02:00
raise <<~EOS
Homebrew GCC requested, but formula #{e.name} not found!
2015-06-21 21:18:23 -04:00
EOS
end
2016-09-23 22:02:23 +02:00
return if gcc_formula.opt_prefix.exist?
2018-09-17 02:45:00 +02:00
2017-10-15 02:28:32 +02:00
raise <<~EOS
The requested Homebrew GCC was not installed. You must:
brew install #{gcc_formula.full_name}
2016-09-23 22:02:23 +02:00
EOS
end
2020-11-20 14:20:38 +01:00
sig { void }
def permit_arch_flags; end
2014-05-18 14:34:31 -05:00
2020-11-20 14:20:38 +01:00
sig { void }
2021-01-25 09:18:10 +00:00
def permit_weak_imports
odeprecated "ENV.permit_weak_imports"
end
# @private
2020-11-20 14:20:38 +01:00
sig { params(cc: T.any(Symbol, String)).returns(T::Boolean) }
def compiler_any_clang?(cc = compiler)
%w[clang llvm_clang].include?(cc.to_s)
end
2014-05-18 14:34:31 -05:00
private
2020-11-20 14:20:38 +01:00
sig { params(_flags: T::Array[String], _map: T::Hash[Symbol, String]).void }
def set_cpu_flags(_flags, _map = {}); end
sig { params(val: T.any(String, Pathname)).returns(String) }
def cc=(val)
2014-05-18 14:34:31 -05:00
self["CC"] = self["OBJC"] = val.to_s
end
2020-11-20 14:20:38 +01:00
sig { params(val: T.any(String, Pathname)).returns(String) }
def cxx=(val)
2014-05-18 14:34:31 -05:00
self["CXX"] = self["OBJCXX"] = val.to_s
end
2014-05-18 14:34:31 -05:00
2020-11-20 14:20:38 +01:00
sig { returns(T.nilable(String)) }
2014-05-18 14:34:31 -05:00
def homebrew_cc
self["HOMEBREW_CC"]
end
2020-11-20 14:20:38 +01:00
sig { params(value: String, source: String).returns(Symbol) }
def fetch_compiler(value, source)
COMPILER_SYMBOL_MAP.fetch(value) do |other|
case other
when GNU_GCC_REGEXP
other
else
raise "Invalid value for #{source}: #{other}"
end
end
end
2020-11-20 14:20:38 +01:00
sig { void }
def check_for_compiler_universal_support
2020-11-20 14:20:38 +01:00
raise "Non-Apple GCC can't build universal binaries" if homebrew_cc&.match?(GNU_GCC_REGEXP)
end
2013-08-19 12:32:59 -05:00
end
2016-08-16 13:47:22 +01:00
require "extend/os/extend/ENV/shared"