Add types for ENV extensions.

This commit is contained in:
Markus Reiter 2020-11-20 14:20:38 +01:00
parent 53c59cd5a8
commit 0424940496
9 changed files with 268 additions and 66 deletions

View File

@ -351,6 +351,7 @@ module Homebrew
end
# Needs a custom implementation.
sig { returns(String) }
def make_jobs
jobs = ENV["HOMEBREW_MAKE_JOBS"].to_i
return jobs.to_s if jobs.positive?

View File

@ -1,4 +1,4 @@
# typed: false
# typed: strict
# frozen_string_literal: true
require "hardware"
@ -7,11 +7,22 @@ require "extend/ENV/shared"
require "extend/ENV/std"
require "extend/ENV/super"
module Kernel
extend T::Sig
sig { params(env: T.nilable(String)).returns(T::Boolean) }
def superenv?(env)
env != "std" && Superenv.bin
return false if env == "std"
!Superenv.bin.nil?
end
private :superenv?
end
module EnvActivation
extend T::Sig
sig { params(env: T.nilable(String)).void }
def activate_extensions!(env: nil)
if superenv?(env)
extend(Superenv)
@ -20,25 +31,41 @@ module EnvActivation
end
end
def with_build_environment(env: nil, cc: nil, build_bottle: false, bottle_arch: nil)
sig do
params(
env: T.nilable(String),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(String),
_block: T.proc.returns(T.untyped),
).returns(T.untyped)
end
def with_build_environment(env: nil, cc: nil, build_bottle: false, bottle_arch: nil, &_block)
old_env = to_hash.dup
tmp_env = to_hash.dup.extend(EnvActivation)
tmp_env.activate_extensions!(env: env)
tmp_env.setup_build_environment(cc: cc, build_bottle: build_bottle, bottle_arch: bottle_arch)
T.cast(tmp_env, EnvActivation).activate_extensions!(env: env)
T.cast(tmp_env, T.any(Superenv, Stdenv))
.setup_build_environment(cc: cc, build_bottle: build_bottle, bottle_arch: bottle_arch)
replace(tmp_env)
begin
yield
ensure
replace(old_env)
end
def sensitive?(key)
/(cookie|key|token|password)/i =~ key
end
sig { params(key: T.any(String, Symbol)).returns(T::Boolean) }
def sensitive?(key)
key.match?(/(cookie|key|token|password)/i)
end
sig { returns(T::Hash[String, String]) }
def sensitive_environment
select { |key, _| sensitive?(key) }
end
sig { void }
def clear_sensitive_environment!
each_key { |key| delete key if sensitive?(key) }
end

View File

@ -0,0 +1,49 @@
# typed: strict
module EnvMethods
include Kernel
sig { params(key: String).returns(T::Boolean) }
def key?(key); end
sig { params(key: String).returns(T.nilable(String)) }
def [](key); end
sig { params(key: String).returns(String) }
def fetch(key); end
sig { params(key: String, value: T.nilable(T.any(String, PATH))).returns(T.nilable(String)) }
def []=(key, value); end
sig { params(block: T.proc.params(arg0: [String, String]).returns(T::Boolean)).returns(T::Hash[String, String]) }
def select(&block); end
sig { params(block: T.proc.params(arg0: String).void).void }
def each_key(&block); end
sig { params(key: String).returns(T.nilable(String)) }
def delete(key); end
sig do
params(other: T.any(T::Hash[String, String], Sorbet::Private::Static::ENVClass))
.returns(Sorbet::Private::Static::ENVClass)
end
def replace(other); end
sig { returns(T::Hash[String, String]) }
def to_hash; end
end
module EnvActivation
include EnvMethods
end
class Sorbet
module Private
module Static
class ENVClass
include EnvActivation
end
end
end
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "compilers"
@ -11,13 +11,16 @@ require "development_tools"
# @see Stdenv
# @see https://www.rubydoc.info/stdlib/Env Ruby's ENV API
module SharedEnvExtension
extend T::Sig
include CompilerConstants
# @private
CC_FLAG_VARS = %w[CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS].freeze
# @private
private_constant :CC_FLAG_VARS
FC_FLAG_VARS = %w[FCFLAGS FFLAGS].freeze
# @private
private_constant :FC_FLAG_VARS
SANITIZED_VARS = %w[
CDPATH CLICOLOR_FORCE
CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH OBJC_INCLUDE_PATH
@ -28,8 +31,16 @@ module SharedEnvExtension
GOBIN GOPATH GOROOT PERL_MB_OPT PERL_MM_OPT
LIBRARY_PATH LD_LIBRARY_PATH LD_PRELOAD LD_RUN_PATH
].freeze
private_constant :SANITIZED_VARS
# @private
sig do
params(
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
).void
end
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil)
@formula = formula
@cc = cc
@ -37,57 +48,62 @@ module SharedEnvExtension
@bottle_arch = bottle_arch
reset
end
private :setup_build_environment
# @private
sig { void }
def reset
SANITIZED_VARS.each { |k| delete(k) }
end
private :reset
sig { returns(T::Hash[String, String]) }
def remove_cc_etc
keys = %w[CC CXX OBJC OBJCXX LD CPP CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS LDFLAGS CPPFLAGS]
removed = Hash[*keys.flat_map { |key| [key, self[key]] }]
keys.each do |key|
delete(key)
end
removed
keys.map { |key| [key, delete(key)] }.to_h
end
sig { params(newflags: String).void }
def append_to_cflags(newflags)
append(CC_FLAG_VARS, newflags)
end
sig { params(val: T.any(Regexp, String)).void }
def remove_from_cflags(val)
remove CC_FLAG_VARS, val
end
sig { params(value: String).void }
def append_to_cccfg(value)
append("HOMEBREW_CCCFG", value, "")
end
sig { params(keys: T.any(String, T::Array[String]), value: T.untyped, separator: String).void }
def append(keys, value, separator = " ")
value = value.to_s
Array(keys).each do |key|
old = self[key]
if old.nil? || old.empty?
self[key] = value
old_value = self[key]
self[key] = if old_value.nil? || old_value.empty?
value
else
self[key] += separator + value
old_value + separator + value
end
end
end
sig { params(keys: T.any(String, T::Array[String]), value: T.untyped, separator: String).void }
def prepend(keys, value, separator = " ")
value = value.to_s
Array(keys).each do |key|
old = self[key]
self[key] = if old.nil? || old.empty?
old_value = self[key]
self[key] = if old_value.nil? || old_value.empty?
value
else
value + separator + old
value + separator + old_value
end
end
end
sig { params(key: String, path: String).void }
def append_path(key, path)
self[key] = PATH.new(self[key]).append(path)
end
@ -99,61 +115,78 @@ module SharedEnvExtension
# 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>
sig { params(key: String, path: String).void }
def prepend_path(key, path)
return if %w[/usr/bin /bin /usr/sbin /sbin].include? path.to_s
self[key] = PATH.new(self[key]).prepend(path)
end
sig { params(key: String, path: T.any(String, Pathname)).void }
def prepend_create_path(key, path)
path = Pathname.new(path) unless path.is_a? Pathname
path = Pathname(path)
path.mkpath
prepend_path key, path
end
sig { params(keys: T.any(String, T::Array[String]), value: T.untyped).void }
def remove(keys, value)
return if value.nil?
Array(keys).each do |key|
next unless self[key]
old_value = self[key]
next if old_value.nil?
self[key] = self[key].sub(value, "")
delete(key) if self[key].empty?
new_value = old_value.sub(value, "")
if new_value.empty?
delete(key)
else
self[key] = new_value
end
end
end
sig { returns(T.nilable(String)) }
def cc
self["CC"]
end
sig { returns(T.nilable(String)) }
def cxx
self["CXX"]
end
sig { returns(T.nilable(String)) }
def cflags
self["CFLAGS"]
end
sig { returns(T.nilable(String)) }
def cxxflags
self["CXXFLAGS"]
end
sig { returns(T.nilable(String)) }
def cppflags
self["CPPFLAGS"]
end
sig { returns(T.nilable(String)) }
def ldflags
self["LDFLAGS"]
end
sig { returns(T.nilable(String)) }
def fc
self["FC"]
end
sig { returns(T.nilable(String)) }
def fflags
self["FFLAGS"]
end
sig { returns(T.nilable(String)) }
def fcflags
self["FCFLAGS"]
end
@ -164,8 +197,7 @@ module SharedEnvExtension
# # modify CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS in one go:
# ENV.append_to_cflags "-I ./missing/includes"
# end</pre>
#
# @return [Symbol]
sig { returns(T.any(Symbol, String)) }
def compiler
@compiler ||= if (cc = @cc)
warn_about_non_apple_gcc(cc) if cc.match?(GNU_GCC_REGEXP)
@ -189,27 +221,31 @@ module SharedEnvExtension
end
end
# @private
sig { returns(T.any(String, Pathname)) }
def determine_cc
COMPILER_SYMBOL_MAP.invert.fetch(compiler, compiler)
end
private :determine_cc
COMPILERS.each do |compiler|
define_method(compiler) do
@compiler = compiler
self.cc = determine_cc
self.cxx = determine_cxx
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
# Currently only used by aalib in core.
sig { void }
def ncurses_define
append "CPPFLAGS", "-DNCURSES_OPAQUE=0"
end
# @private
sig { void }
def userpaths!
path = PATH.new(self["PATH"]).select do |p|
# put Superenv.bin and opt path at the first
@ -228,6 +264,7 @@ module SharedEnvExtension
self["PATH"] = path
end
sig { void }
def fortran
# Ignore repeated calls to this function as it will misleadingly warn about
# building with an alternative Fortran compiler without optimization flags,
@ -260,6 +297,7 @@ module SharedEnvExtension
end
# @private
sig { returns(Symbol) }
def effective_arch
if @build_bottle && @bottle_arch
@bottle_arch.to_sym
@ -269,6 +307,7 @@ module SharedEnvExtension
end
# @private
sig { params(name: String).returns(Formula) }
def gcc_version_formula(name)
version = name[GNU_GCC_REGEXP, 1]
gcc_version_name = "gcc@#{version}"
@ -282,6 +321,7 @@ module SharedEnvExtension
end
# @private
sig { params(name: String).void }
def warn_about_non_apple_gcc(name)
begin
gcc_formula = gcc_version_formula(name)
@ -299,30 +339,40 @@ module SharedEnvExtension
EOS
end
sig { void }
def permit_arch_flags; end
# A no-op until we enable this by default again (which we may never do).
sig { void }
def permit_weak_imports; end
# @private
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
private
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)
self["CC"] = self["OBJC"] = val.to_s
end
sig { params(val: T.any(String, Pathname)).returns(String) }
def cxx=(val)
self["CXX"] = self["OBJCXX"] = val.to_s
end
sig { returns(T.nilable(String)) }
def homebrew_cc
self["HOMEBREW_CC"]
end
sig { params(value: String, source: String).returns(Symbol) }
def fetch_compiler(value, source)
COMPILER_SYMBOL_MAP.fetch(value) do |other|
case other
@ -334,10 +384,9 @@ module SharedEnvExtension
end
end
sig { void }
def check_for_compiler_universal_support
return unless homebrew_cc.match?(GNU_GCC_REGEXP)
raise "Non-Apple GCC can't build universal binaries"
raise "Non-Apple GCC can't build universal binaries" if homebrew_cc&.match?(GNU_GCC_REGEXP)
end
end

View File

@ -0,0 +1,15 @@
# typed: strict
module SharedEnvExtension
include EnvMethods
end
class Sorbet
module Private
module Static
class ENVClass
include SharedEnvExtension
end
end
end
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: strict
# frozen_string_literal: true
require "hardware"
@ -14,8 +14,16 @@ module Stdenv
SAFE_CFLAGS_FLAGS = "-w -pipe"
# @private
def setup_build_environment(**options)
super(**options)
sig do
params(
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
).void
end
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil)
super
self["HOMEBREW_ENV"] = "std"
@ -49,13 +57,14 @@ module Stdenv
send(compiler)
return unless cc.match?(GNU_GCC_REGEXP)
return unless cc&.match?(GNU_GCC_REGEXP)
gcc_formula = gcc_version_formula(cc)
append_path "PATH", gcc_formula.opt_bin.to_s
end
alias generic_setup_build_environment setup_build_environment
sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[]
end
@ -73,10 +82,11 @@ module Stdenv
# Removes the MAKEFLAGS environment variable, causing make to use a single job.
# This is useful for makefiles with race conditions.
# When passed a block, MAKEFLAGS is removed only for the duration of the block and is restored after its completion.
def deparallelize
sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }
def deparallelize(&block)
old = self["MAKEFLAGS"]
remove "MAKEFLAGS", /-j\d+/
if block_given?
if block
begin
yield
ensure
@ -89,30 +99,33 @@ module Stdenv
%w[O3 O2 O1 O0 Os].each do |opt|
define_method opt do
remove_from_cflags(/-O./)
append_to_cflags "-#{opt}"
send(:remove_from_cflags, /-O./)
send(:append_to_cflags, "-#{opt}")
end
end
# @private
sig { returns(T.any(String, Pathname)) }
def determine_cc
s = super
DevelopmentTools.locate(s) || Pathname.new(s)
DevelopmentTools.locate(s) || Pathname(s)
end
private :determine_cc
# @private
sig { returns(Pathname) }
def determine_cxx
dir, base = determine_cc.split
dir, base = Pathname(determine_cc).split
dir/base.to_s.sub("gcc", "g++").sub("clang", "clang++")
end
private :determine_cxx
GNU_GCC_VERSIONS.each do |n|
define_method(:"gcc-#{n}") do
super()
set_cpu_cflags
send(:set_cpu_cflags)
end
end
sig { void }
def clang
super()
replace_in_cflags(/-Xarch_#{Hardware::CPU.arch_32_bit} (-march=\S*)/, '\1')
@ -124,16 +137,19 @@ module Stdenv
set_cpu_cflags(map)
end
sig { void }
def m64
append_to_cflags "-m64"
append "LDFLAGS", "-arch #{Hardware::CPU.arch_64_bit}"
end
sig { void }
def m32
append_to_cflags "-m32"
append "LDFLAGS", "-arch #{Hardware::CPU.arch_32_bit}"
end
sig { void }
def universal_binary
check_for_compiler_universal_support
@ -141,33 +157,38 @@ module Stdenv
append "LDFLAGS", Hardware::CPU.universal_archs.as_arch_flags
return if compiler_any_clang?
return unless Hardware.is_32_bit?
return unless Hardware::CPU.is_32_bit?
# Can't mix "-march" for a 32-bit CPU with "-arch x86_64"
replace_in_cflags(/-march=\S*/, "-Xarch_#{Hardware::CPU.arch_32_bit} \\0")
end
sig { void }
def cxx11
append "CXX", "-std=c++11"
libcxx
end
sig { void }
def libcxx
append "CXX", "-stdlib=libc++" if compiler == :clang
end
sig { void }
def libstdcxx
append "CXX", "-stdlib=libstdc++" if compiler == :clang
end
# @private
sig { params(before: Regexp, after: String).void }
def replace_in_cflags(before, after)
CC_FLAG_VARS.each do |key|
self[key] = self[key].sub(before, after) if key?(key)
self[key] = fetch(key).sub(before, after) if key?(key)
end
end
# Convenience method to set all C compiler flags in one shot.
sig { params(val: String).void }
def define_cflags(val)
CC_FLAG_VARS.each { |key| self[key] = val }
end
@ -175,6 +196,7 @@ module Stdenv
# Sets architecture-specific flags for every environment variable
# given in the list `flags`.
# @private
sig { params(flags: T::Array[String], map: T::Hash[Symbol, String]).void }
def set_cpu_flags(flags, map = Hardware::CPU.optimization_flags)
cflags =~ /(-Xarch_#{Hardware::CPU.arch_32_bit} )-march=/
xarch = Regexp.last_match(1).to_s
@ -186,19 +208,23 @@ module Stdenv
append flags, map.fetch(effective_arch)
end
sig { void }
def x11; end
# @private
sig { params(map: T::Hash[Symbol, String]).void }
def set_cpu_cflags(map = Hardware::CPU.optimization_flags) # rubocop:disable Naming/AccessorMethodName
set_cpu_flags(CC_FLAG_VARS, map)
end
sig { returns(Integer) }
def make_jobs
Homebrew::EnvConfig.make_jobs.to_i
end
# This method does nothing in stdenv since there's no arg refurbishment
# @private
sig { void }
def refurbish_args; end
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "extend/ENV/shared"
@ -22,6 +22,7 @@ module Superenv
# @private
attr_accessor :keg_only_deps, :deps, :run_time_deps, :x11
sig { params(base: Superenv).void }
def self.extended(base)
base.keg_only_deps = []
base.deps = []
@ -29,8 +30,10 @@ module Superenv
end
# @private
sig { returns(T.nilable(Pathname)) }
def self.bin; end
sig { void }
def reset
super
# Configure scripts generated by autoconf 2.61 or later export as_nl, which
@ -39,8 +42,16 @@ module Superenv
end
# @private
def setup_build_environment(**options)
super(**options)
sig do
params(
formula: T.nilable(Formula),
cc: T.nilable(String),
build_bottle: T.nilable(T::Boolean),
bottle_arch: T.nilable(T::Boolean),
).void
end
def setup_build_environment(formula: nil, cc: nil, build_bottle: false, bottle_arch: nil)
super
send(compiler)
self["HOMEBREW_ENV"] = "super"
@ -89,18 +100,22 @@ module Superenv
private
sig { params(val: T.any(String, Pathname)).returns(String) }
def cc=(val)
self["HOMEBREW_CC"] = super
end
sig { params(val: T.any(String, Pathname)).returns(String) }
def cxx=(val)
self["HOMEBREW_CXX"] = super
end
sig { returns(String) }
def determine_cxx
determine_cc.to_s.gsub("gcc", "g++").gsub("clang", "clang++")
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_paths
[]
end
@ -115,7 +130,7 @@ module Superenv
path.append("/usr/bin", "/bin", "/usr/sbin", "/sbin")
begin
path.append(gcc_version_formula(homebrew_cc).opt_bin) if homebrew_cc.match?(GNU_GCC_REGEXP)
path.append(gcc_version_formula(T.must(homebrew_cc)).opt_bin) if homebrew_cc&.match?(GNU_GCC_REGEXP)
rescue FormulaUnavailableError
# Don't fail and don't add these formulae to the path if they don't exist.
nil
@ -124,6 +139,7 @@ module Superenv
path.existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_pkg_config_paths
[]
end
@ -143,6 +159,7 @@ module Superenv
).existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_aclocal_paths
[]
end
@ -156,6 +173,7 @@ module Superenv
).existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_isystem_paths
[]
end
@ -173,6 +191,7 @@ module Superenv
PATH.new(keg_only_deps.map(&:opt_include)).existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_library_paths
[]
end
@ -187,6 +206,7 @@ module Superenv
PATH.new(paths).existing
end
sig { returns(String) }
def determine_dependencies
deps.map(&:name).join(",")
end
@ -199,6 +219,7 @@ module Superenv
).existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_cmake_include_paths
[]
end
@ -208,6 +229,7 @@ module Superenv
PATH.new(homebrew_extra_cmake_include_paths).existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_cmake_library_paths
[]
end
@ -217,6 +239,7 @@ module Superenv
PATH.new(homebrew_extra_cmake_library_paths).existing
end
sig { returns(T::Array[Pathname]) }
def homebrew_extra_cmake_frameworks_paths
[]
end
@ -229,14 +252,17 @@ module Superenv
).existing
end
sig { returns(String) }
def determine_make_jobs
Homebrew::EnvConfig.make_jobs
end
sig { returns(String) }
def determine_optflags
Hardware::CPU.optimization_flags.fetch(effective_arch)
end
sig { returns(String) }
def determine_cccfg
""
end
@ -246,9 +272,10 @@ module Superenv
# Removes the MAKEFLAGS environment variable, causing make to use a single job.
# This is useful for makefiles with race conditions.
# When passed a block, MAKEFLAGS is removed only for the duration of the block and is restored after its completion.
def deparallelize
sig { params(block: T.proc.returns(T.untyped)).returns(T.untyped) }
def deparallelize(&block)
old = delete("MAKEFLAGS")
if block_given?
if block
begin
yield
ensure
@ -259,56 +286,64 @@ module Superenv
old
end
sig { returns(Integer) }
def make_jobs
self["MAKEFLAGS"] =~ /-\w*j(\d+)/
[Regexp.last_match(1).to_i, 1].max
end
sig { void }
def universal_binary
check_for_compiler_universal_support
self["HOMEBREW_ARCHFLAGS"] = Hardware::CPU.universal_archs.as_arch_flags
end
sig { void }
def permit_arch_flags
append_to_cccfg "K"
end
sig { void }
def m32
append "HOMEBREW_ARCHFLAGS", "-m32"
end
sig { void }
def m64
append "HOMEBREW_ARCHFLAGS", "-m64"
end
sig { void }
def cxx11
append_to_cccfg "x"
append_to_cccfg "g" if homebrew_cc == "clang"
end
sig { void }
def libcxx
append_to_cccfg "g" if compiler == :clang
end
sig { void }
def libstdcxx
append_to_cccfg "h" if compiler == :clang
end
# @private
sig { void }
def refurbish_args
append_to_cccfg "O"
end
%w[O3 O2 O1 O0 Os].each do |opt|
define_method opt do
self["HOMEBREW_OPTIMIZATION_LEVEL"] = opt
send(:[]=, "HOMEBREW_OPTIMIZATION_LEVEL", opt)
end
end
sig { void }
def set_x11_env_if_installed; end
def set_cpu_flags(_arg0, _arg1 = "", _arg2 = {}); end
end
require "extend/os/extend/ENV/super"

View File

@ -27,7 +27,7 @@ module Superenv
def homebrew_extra_paths
paths = []
paths << MacOS::XQuartz.bin.to_s if x11?
paths << MacOS::XQuartz.bin if x11?
paths
end

View File

@ -34,7 +34,7 @@ module Utils
# @api public
sig do
params(
paths: T.any(T::Array[T.untyped], String),
paths: T.any(T::Array[T.untyped], String, Pathname),
before: T.nilable(T.any(Regexp, String)),
after: T.nilable(T.any(String, Symbol)),
audit_result: T::Boolean,