
Our parsing of linker flags can be easily confused by, e.g., -Wl,-undefined -Wl,dynamic_lookup,-dead_strip_dylibs The current code that tries to detect these flags will erroneously conclude that they were not passed. This change fixes that.
555 lines
15 KiB
Bash
Executable File
555 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
# Make sure this shim uses the same Ruby interpreter that is used by Homebrew.
|
|
if [[ -z "${HOMEBREW_RUBY_PATH}" ]]
|
|
then
|
|
echo "${0##*/}: The build tool has reset ENV; --env=std required." >&2
|
|
exit 1
|
|
fi
|
|
unset HOMEBREW_LD_LIBRARY_PATH
|
|
if [[ -n "${LD_LIBRARY_PATH+set}" ]]
|
|
then
|
|
export HOMEBREW_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}"
|
|
fi
|
|
unset LD_LIBRARY_PATH
|
|
unset RUBYLIB
|
|
exec "${HOMEBREW_RUBY_PATH}" --enable-frozen-string-literal --disable=gems,did_you_mean,rubyopt -x "$0" "$@"
|
|
#!/usr/bin/env ruby -W0
|
|
|
|
require "pathname"
|
|
require "set"
|
|
require "English"
|
|
|
|
def mac?
|
|
RUBY_PLATFORM[/darwin/]
|
|
end
|
|
|
|
def high_sierra_or_later?
|
|
mac? && ENV["HOMEBREW_MACOS_VERSION_NUMERIC"].to_s.to_i >= 101300
|
|
end
|
|
|
|
def linux?
|
|
RUBY_PLATFORM[/linux/]
|
|
end
|
|
|
|
class Cmd
|
|
CXX_REGEX = /(?:c|g|clang)\+\+/.freeze
|
|
|
|
attr_reader :config, :prefix, :cellar, :opt, :cachedir, :tmpdir, :sysroot, :deps
|
|
attr_reader :archflags, :optflags, :keg_regex, :formula_prefix, :formula_buildpath
|
|
|
|
def initialize(arg0, args)
|
|
@arg0 = arg0
|
|
@args = args.freeze
|
|
@config = ENV.fetch("HOMEBREW_CCCFG", "")
|
|
@prefix = ENV["HOMEBREW_PREFIX"]
|
|
@cellar = ENV["HOMEBREW_CELLAR"]
|
|
@cachedir = ENV["HOMEBREW_CACHE"]
|
|
@opt = ENV["HOMEBREW_OPT"]
|
|
@tmpdir = ENV["HOMEBREW_TEMP"]
|
|
@sysroot = ENV["HOMEBREW_SDKROOT"]
|
|
@archflags = ENV.fetch("HOMEBREW_ARCHFLAGS", "").split
|
|
@optflags = ENV.fetch("HOMEBREW_OPTFLAGS", "").split
|
|
@deps = Set.new(ENV.fetch("HOMEBREW_DEPENDENCIES", "").split(","))
|
|
@formula_prefix = ENV["HOMEBREW_FORMULA_PREFIX"]
|
|
@formula_buildpath = ENV["HOMEBREW_FORMULA_BUILDPATH"]
|
|
# matches opt or cellar prefix and formula name
|
|
@keg_regex = %r{(#{Regexp.escape(opt)}|#{Regexp.escape(cellar)})/([\w+-.@]+)}
|
|
end
|
|
|
|
def mode
|
|
if @arg0 == "cpp"
|
|
:cpp
|
|
elsif ["ld", "ld.gold", "gold"].include? @arg0
|
|
:ld
|
|
elsif @args.include? "-c"
|
|
if CXX_REGEX.match?(@arg0)
|
|
:cxx
|
|
else
|
|
:cc
|
|
end
|
|
elsif @args.include?("-xc++-header") || @args.each_cons(2).include?(["-x", "c++-header"])
|
|
:cxx
|
|
elsif @args.include? "-E"
|
|
:ccE
|
|
elsif CXX_REGEX.match?(@arg0)
|
|
:cxxld
|
|
else
|
|
:ccld
|
|
end
|
|
end
|
|
|
|
def tool
|
|
@tool ||= case @arg0
|
|
when "ld" then "ld"
|
|
when "gold", "ld.gold" then "ld.gold"
|
|
when "cpp" then "cpp"
|
|
when /llvm_(clang(\+\+)?)/
|
|
"#{ENV["HOMEBREW_PREFIX"]}/opt/llvm/bin/#{Regexp.last_match(1)}"
|
|
when /\w\+\+(-\d+(\.\d)?)?$/
|
|
case ENV["HOMEBREW_CC"]
|
|
when "swift_clang"
|
|
"#{ENV["HOMEBREW_PREFIX"]}/opt/swift/libexec/bin/clang++"
|
|
when /clang/
|
|
"clang++"
|
|
when /llvm-gcc/
|
|
"llvm-g++-4.2"
|
|
when /(g)?cc(-\d+(\.\d)?)?$/
|
|
"g++#{Regexp.last_match(2)}"
|
|
end
|
|
else
|
|
# Note that this is a universal fallback, so that we'll always invoke
|
|
# HOMEBREW_CC regardless of what name under which the tool was invoked.
|
|
if ENV["HOMEBREW_CC"] == "llvm_clang"
|
|
"#{ENV["HOMEBREW_PREFIX"]}/opt/llvm/bin/clang"
|
|
elsif ENV["HOMEBREW_CC"] == "swift_clang"
|
|
"#{ENV["HOMEBREW_PREFIX"]}/opt/swift/libexec/bin/clang"
|
|
else
|
|
ENV["HOMEBREW_CC"]
|
|
end
|
|
end
|
|
end
|
|
|
|
def args
|
|
if @args.length == 1 && @args[0] == "-v"
|
|
# Don't add linker arguments if -v passed as sole option. This stops gcc
|
|
# -v with no other arguments from outputting a linker error. Some
|
|
# software uses gcc -v (wrongly) to sniff the GCC version.
|
|
return @args.dup
|
|
end
|
|
|
|
args = if !refurbish_args? || mode == :ld || configure?
|
|
@args.dup
|
|
else
|
|
refurbished_args
|
|
end
|
|
|
|
if sysroot
|
|
if tool == "ld"
|
|
args << "-syslibroot" << sysroot
|
|
else
|
|
args << "-isysroot#{sysroot}" << "--sysroot=#{sysroot}"
|
|
end
|
|
end
|
|
|
|
case mode
|
|
when :ccld
|
|
cflags + args + cppflags + ldflags
|
|
when :cxxld
|
|
cxxflags + args + cppflags + ldflags
|
|
when :cc
|
|
cflags + args + cppflags
|
|
when :cxx
|
|
cxxflags + args + cppflags
|
|
when :ccE
|
|
args + cppflags
|
|
when :cpp
|
|
args + cppflags
|
|
when :ld
|
|
ldflags + args
|
|
end
|
|
end
|
|
|
|
def refurbished_args
|
|
@lset = Set.new(library_paths + system_library_paths)
|
|
@iset = Set.new(isystem_paths + include_paths)
|
|
|
|
args = []
|
|
enum = @args.each
|
|
|
|
loop do
|
|
case arg = enum.next
|
|
when "-arch"
|
|
if permit_arch_flags?
|
|
args << arg << enum.next
|
|
else
|
|
enum.next
|
|
end
|
|
when "-m32", "-m64"
|
|
args << arg if permit_arch_flags?
|
|
when /^-Xarch_/
|
|
refurbished = refurbish_arg(enum.next, enum)
|
|
unless refurbished.empty?
|
|
args << arg
|
|
args += refurbished
|
|
end
|
|
else
|
|
args += refurbish_arg(arg, enum)
|
|
end
|
|
end
|
|
|
|
args
|
|
end
|
|
|
|
def refurbish_arg(arg, enum)
|
|
args = []
|
|
|
|
case arg
|
|
when /^-g\d?$/, /^-gstabs\d+/, "-gstabs+", /^-ggdb\d?/,
|
|
/^-mtune=.+/, /^-mcpu=.+/, /^-O[0-9zs]?$/,
|
|
"-fast", "-no-cpp-precomp", "-pedantic",
|
|
"-pedantic-errors", "-Wno-long-double",
|
|
"-Wno-unused-but-set-variable"
|
|
when /^-march=.+/
|
|
args << arg if runtime_cpu_detection?
|
|
when "-fopenmp", "-lgomp", "-mno-fused-madd", "-fforce-addr", "-fno-defer-pop",
|
|
"-mno-dynamic-no-pic", "-fearly-inlining", /^-f(?:no-)?inline-functions-called-once/,
|
|
/^-finline-limit/, /^-f(?:no-)?check-new/, "-fno-delete-null-pointer-checks",
|
|
"-fcaller-saves", "-fthread-jumps", "-fno-reorder-blocks", "-fcse-skip-blocks",
|
|
"-frerun-cse-after-loop", "-frerun-loop-opt", "-fcse-follow-jumps",
|
|
"-fno-regmove", "-fno-for-scope", "-fno-tree-pre", "-fno-tree-dominator-opts",
|
|
"-fuse-linker-plugin", "-frounding-math"
|
|
# clang doesn't support these flags
|
|
args << arg unless tool =~ /^clang/
|
|
when "-Xpreprocessor", "-Xclang"
|
|
# used for -Xpreprocessor -fopenmp
|
|
args << arg << enum.next
|
|
when /-mmacosx-version-min=(\d+)\.(\d+)/
|
|
if high_sierra_or_later? && Regexp.last_match(1) == "10" && Regexp.last_match(2).to_i < 9
|
|
arg = "-mmacosx-version-min=10.9"
|
|
end
|
|
args << arg
|
|
when "--fast-math"
|
|
arg = "-ffast-math" if /^clang/.match?(tool)
|
|
args << arg
|
|
when "-Wno-deprecated-register"
|
|
# older gccs don't support these flags
|
|
args << arg unless tool =~ /^g..-4.[02]/
|
|
when /^-Wl,-z,defs/
|
|
# -Wl,-undefined,error is already the default
|
|
when /^-W[alp],/, /^-Wno-/, "-Werror=implicit-function-declaration"
|
|
args << arg
|
|
when /^-W.*/
|
|
# prune warnings
|
|
when "-macosx_version_min", "-dylib_install_name"
|
|
args << "-Wl,#{arg},#{enum.next}"
|
|
when "-multiply_definedsuppress"
|
|
args << "-Wl,-multiply_defined,suppress"
|
|
when "-undefineddynamic_lookup"
|
|
args << "-Wl,-undefined,dynamic_lookup"
|
|
when /^-isysroot=/, /^--sysroot=/
|
|
if mac?
|
|
sdk = arg.split("=", 2).last
|
|
# We set the sysroot for macOS SDKs
|
|
args << arg unless sdk.downcase.include? "osx"
|
|
else
|
|
args << arg
|
|
end
|
|
when /^-isysroot(.+)?/, /^--sysroot(.+)?/
|
|
# Support both "-isysrootfoo" and "-isysroot foo" (two arguments)
|
|
sdk = chuzzle(Regexp.last_match(1))
|
|
if mac?
|
|
sdk ||= enum.next
|
|
# We set the sysroot for macOS SDKs
|
|
args << "-isysroot#{sdk}" if !sdk.downcase.include?("osx") && !sdk.empty?
|
|
else
|
|
args << arg
|
|
args << enum.next unless sdk
|
|
end
|
|
when "-dylib"
|
|
args << "-Wl,#{arg}"
|
|
when /^-I(.+)?/
|
|
# Support both "-Ifoo" (one argument) and "-I foo" (two arguments)
|
|
val = chuzzle(Regexp.last_match(1)) || enum.next
|
|
path = canonical_path(val)
|
|
args << "-I#{val}" if keep?(path) && @iset.add?(path)
|
|
when /^-L(.+)?/
|
|
val = chuzzle(Regexp.last_match(1)) || enum.next
|
|
path = canonical_path(val)
|
|
args << "-L#{val}" if keep?(path) && @lset.add?(path)
|
|
else
|
|
args << arg
|
|
end
|
|
|
|
args
|
|
end
|
|
|
|
def keep?(path)
|
|
# Allow references to self
|
|
if formula_prefix && path.start_with?("#{formula_prefix}/")
|
|
true
|
|
# first two paths: reject references to Cellar or opt paths
|
|
# for unspecified dependencies
|
|
elsif path.start_with?(cellar) || path.start_with?(opt)
|
|
dep = path[keg_regex, 2]
|
|
dep && @deps.include?(dep)
|
|
elsif path.start_with?(prefix, cachedir, tmpdir)
|
|
true
|
|
elsif path.start_with?("/opt/local", "/opt/boxen/homebrew", "/opt/X11", "/sw", "/usr/X11")
|
|
# ignore MacPorts, Boxen's Homebrew, X11, fink
|
|
false
|
|
elsif prefix != "/usr/local" && path.start_with?("/usr/local")
|
|
# ignore things in /usr/local if Homebrew is in another prefix;
|
|
# on macOS, this probably means that the user is on ARM and has an Intel
|
|
# Homebrew in /usr/local
|
|
false
|
|
elsif mac?
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def cflags
|
|
args = []
|
|
|
|
return args if !refurbish_args? && !configure?
|
|
|
|
args << "-w" unless configure?
|
|
args << "-#{ENV["HOMEBREW_OPTIMIZATION_LEVEL"]}"
|
|
args.concat(optflags) unless runtime_cpu_detection?
|
|
args.concat(archflags)
|
|
args << "-std=#{@arg0}" if /c[89]9/.match?(@arg0)
|
|
args << "-g" if debug_symbols?
|
|
args
|
|
end
|
|
|
|
def cxxflags
|
|
args = cflags
|
|
args << "-std=c++11" if cxx11?
|
|
args << "-stdlib=libc++" if libcxx?
|
|
args << "-stdlib=libstdc++" if libstdcxx?
|
|
args
|
|
end
|
|
|
|
def cppflags
|
|
args = []
|
|
args += path_flags("-isystem", isystem_paths) + path_flags("-I", include_paths)
|
|
# Add -nostdinc when building against glibc@2.13 to avoid mixing system and brewed glibc headers.
|
|
args << "-nostdinc" if @deps.include?("glibc@2.13")
|
|
# Ideally this would be -ffile-prefix-map, but that requires a minimum of GCC 8, LLVM Clang 10 or Apple Clang 12
|
|
# and detecting the version dynamically based on what `HOMEBREW_CC` may have been rewritten to point to is awkward
|
|
args << "-fdebug-prefix-map=#{formula_buildpath}=." if formula_buildpath
|
|
args
|
|
end
|
|
|
|
def ldflags_mac(args)
|
|
case mode
|
|
when :ld
|
|
args << "-headerpad_max_install_names"
|
|
args << "-no_weak_imports" if no_weak_imports?
|
|
args << "-no_fixup_chains" if no_fixup_chains?
|
|
args << "-oso_prefix" << formula_buildpath if oso_prefix? && formula_buildpath
|
|
args << "-ld_classic" if ld_classic?
|
|
when :ccld, :cxxld
|
|
args << "-Wl,-headerpad_max_install_names"
|
|
args << "-Wl,-no_weak_imports" if no_weak_imports?
|
|
args << "-Wl,-no_fixup_chains" if no_fixup_chains?
|
|
args << "-Wl,-oso_prefix,#{formula_buildpath}" if oso_prefix? && formula_buildpath
|
|
args << "-Wl,-ld_classic" if ld_classic?
|
|
end
|
|
args
|
|
end
|
|
|
|
def ldflags_linux(args)
|
|
unless mode == :ld
|
|
wl = "-Wl,"
|
|
if @deps.include?("glibc@2.13")
|
|
args << "-B#{@opt}/glibc@2.13/lib"
|
|
else
|
|
args << "-B#{@opt}/glibc/lib"
|
|
end
|
|
end
|
|
args += rpath_flags("#{wl}-rpath=", rpath_paths)
|
|
args += ["#{wl}--dynamic-linker=#{dynamic_linker_path}"] if dynamic_linker_path
|
|
# Use -rpath-link to make sure linker uses glibc@2.13 rather than the system glibc for indirect
|
|
# dependencies because -L will only handle direct dependencies.
|
|
args << "#{wl}-rpath-link=#{@opt}/glibc@2.13/lib" if @deps.include?("glibc@2.13")
|
|
args
|
|
end
|
|
|
|
def ldflags
|
|
args = path_flags("-L", library_paths)
|
|
if mac?
|
|
ldflags_mac(args)
|
|
elsif linux?
|
|
ldflags_linux(args)
|
|
else
|
|
args
|
|
end
|
|
end
|
|
|
|
def isystem_paths
|
|
path_split("HOMEBREW_ISYSTEM_PATHS")
|
|
end
|
|
|
|
def include_paths
|
|
path_split("HOMEBREW_INCLUDE_PATHS")
|
|
end
|
|
|
|
def library_paths
|
|
path_split("HOMEBREW_LIBRARY_PATHS")
|
|
end
|
|
|
|
def rpath_paths
|
|
path_split("HOMEBREW_RPATH_PATHS")
|
|
end
|
|
|
|
def dynamic_linker_path
|
|
chuzzle(ENV["HOMEBREW_DYNAMIC_LINKER"])
|
|
end
|
|
|
|
def system_library_paths
|
|
paths = ["#{sysroot}/usr/lib"]
|
|
paths << "/usr/local/lib" if !sysroot && !ENV["SDKROOT"]
|
|
paths
|
|
end
|
|
|
|
def configure?
|
|
# configure scripts generated with autoconf 2.61 or later export as_nl
|
|
ENV.key? "as_nl"
|
|
end
|
|
|
|
def refurbish_args?
|
|
config.include?("O")
|
|
end
|
|
|
|
def cxx11?
|
|
config.include?("x")
|
|
end
|
|
|
|
def libcxx?
|
|
config.include?("g")
|
|
end
|
|
|
|
def libstdcxx?
|
|
config.include?("h")
|
|
end
|
|
|
|
def permit_arch_flags?
|
|
config.include?("K")
|
|
end
|
|
|
|
def runtime_cpu_detection?
|
|
config.include?("d")
|
|
end
|
|
|
|
def no_weak_imports?
|
|
config.include?("w")
|
|
end
|
|
|
|
def debug_symbols?
|
|
config.include?("D")
|
|
end
|
|
|
|
def linker_flags
|
|
@args.select { |arg| arg.start_with?("-Wl,") }
|
|
.flat_map { |arg| arg.delete_prefix("-Wl,").split(",") }
|
|
end
|
|
|
|
def no_fixup_chains?
|
|
return false unless config.include?("f")
|
|
return false unless calls_ld?
|
|
return true if @args.each_cons(2).include?(["-undefined", "dynamic_lookup"])
|
|
return true if linker_flags.each_cons(2).include?(["-undefined", "dynamic_lookup"])
|
|
|
|
# The next flag would produce an error, but we fix it in `refurbish_arg`.
|
|
@args.include?("-undefineddynamic_lookup")
|
|
end
|
|
|
|
def oso_prefix?
|
|
config.include?("o") && !configure?
|
|
end
|
|
|
|
def ld_classic?
|
|
return false unless config.include?("c")
|
|
return false unless calls_ld?
|
|
|
|
@args.include?("-dead_strip_dylibs") || linker_flags.include?("-dead_strip_dylibs")
|
|
end
|
|
|
|
def calls_ld?
|
|
return true if mode == :ld
|
|
return false unless [:ccld, :cxxld].include?(mode)
|
|
|
|
fuse_ld_flags = @args.find_all { |arg| arg.match?(/^-fuse-ld=/) }
|
|
return true if fuse_ld_flags.empty?
|
|
|
|
fuse_ld_flag = fuse_ld_flags.last.strip
|
|
fuse_ld_arg = fuse_ld_flag.split("=", 2).last
|
|
|
|
(fuse_ld_arg == "ld") || fuse_ld_arg.end_with?("/usr/bin/ld")
|
|
end
|
|
|
|
def canonical_path(path)
|
|
path = Pathname.new(path)
|
|
path = path.realpath if path.exist?
|
|
path.to_s
|
|
end
|
|
|
|
def path_flags(prefix, paths)
|
|
paths = paths.uniq.select { |path| File.directory?(path) }
|
|
paths.map! { |path| prefix + path }
|
|
end
|
|
|
|
# Unlike {path_flags}, do not prune non-existent directories.
|
|
# `formula.lib` for example does not yet exist, but should not be pruned.
|
|
def rpath_flags(prefix, paths)
|
|
paths.uniq.map { |path| prefix + path }
|
|
end
|
|
|
|
def path_split(key)
|
|
ENV.fetch(key, "").split(File::PATH_SEPARATOR)
|
|
end
|
|
|
|
def chuzzle(val)
|
|
return val if val.nil?
|
|
|
|
val = val.chomp
|
|
return val unless val.empty?
|
|
end
|
|
end
|
|
|
|
def log(basename, argv, tool, args)
|
|
return unless ENV.key?("HOMEBREW_CC_LOG_PATH")
|
|
|
|
adds = args - argv
|
|
dels = argv - args
|
|
|
|
s = +""
|
|
s << "#{basename} called with: #{argv.join(" ")}\n"
|
|
s << "superenv removed: #{dels.join(" ")}\n" unless dels.empty?
|
|
s << "superenv added: #{adds.join(" ")}\n" unless adds.empty?
|
|
s << "superenv executed: #{tool} #{args.join(" ")}\n\n"
|
|
File.open("#{ENV["HOMEBREW_CC_LOG_PATH"]}.cc", "a+") { |f| f.write(s) }
|
|
end
|
|
|
|
def remove_superbin_from_path(paths)
|
|
superbin = Pathname.new(__FILE__).dirname.realpath
|
|
paths.reject do |x|
|
|
path = Pathname.new(x)
|
|
path.directory? && path.realpath == superbin
|
|
end
|
|
end
|
|
|
|
if __FILE__ == $PROGRAM_NAME
|
|
##################################################################### sanity
|
|
|
|
if (cc = ENV["HOMEBREW_CC"]).nil? || cc.empty? || cc == "cc"
|
|
# those values are not allowed
|
|
ENV["HOMEBREW_CC"] = "clang"
|
|
end
|
|
|
|
ENV["LD_LIBRARY_PATH"] = ENV["HOMEBREW_LD_LIBRARY_PATH"]
|
|
|
|
####################################################################### main
|
|
|
|
dirname, basename = File.split($PROGRAM_NAME)
|
|
|
|
cmd = Cmd.new(basename, ARGV)
|
|
tool = cmd.tool
|
|
args = cmd.args
|
|
|
|
log(basename, ARGV, tool, args)
|
|
|
|
args << { close_others: false }
|
|
if mac?
|
|
exec "#{dirname}/xcrun", tool, *args
|
|
else
|
|
paths = ENV["PATH"].split(":")
|
|
paths = remove_superbin_from_path(paths)
|
|
paths.unshift "#{ENV["HOMEBREW_PREFIX"]}/bin"
|
|
ENV["PATH"] = paths.join(":")
|
|
exec tool, *args
|
|
end
|
|
end
|