#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0 $:.unshift "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8" require 'pathname' require 'set' require 'stringio' class Logger def initialize @io = StringIO.new end def puts(*args) @io.puts(*args) end def log! return unless ENV.key? 'HOMEBREW_CC_LOG_PATH' path = "#{ENV['HOMEBREW_CC_LOG_PATH']}.cc" puts File.open(path, File::WRONLY | File::APPEND | File::CREAT) { |f| f.write(@io.string) } end end LOGGER = Logger.new class Cmd attr_reader :brewfix, :brewtmp, :sdkroot def initialize path, args @arg0 = File.basename(path).freeze @args = args.freeze @brewfix = ENV['HOMEBREW_PREFIX'] @brewtmp = ENV['HOMEBREW_TEMP'] @sdkroot = ENV['HOMEBREW_SDKROOT'] end def mode if @arg0 == 'cpp' or @arg0 == 'ld' @arg0.to_sym elsif @args.include? '-c' if @arg0 =~ /(?:c|g|clang)\+\+/ :cxx else :cc end elsif @args.include? '-E' :ccE else if @arg0 =~ /(?:c|g|clang)\+\+/ :cxxld else :ccld end end end def tool @tool ||= case @arg0 when 'ld' then 'ld' when 'cpp' then 'cpp' when /\w\+\+(-\d\.\d)?$/ case ENV['HOMEBREW_CC'] when /clang/ 'clang++' when /llvm-gcc/ 'llvm-g++-4.2' when /gcc(-\d\.\d)?$/ 'g++' + $1.to_s 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. ENV['HOMEBREW_CC'] end end def args if @args.length == 1 and @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 if !cccfg?("O") || tool == "ld" || configure? args = @args.dup else args = refurbished_args end if sdkroot if tool == "ld" args << "-syslibroot" << sdkroot else args << "--sysroot=#{sdkroot}" end end allflags = 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.compact make_fuss(allflags) allflags end def refurbished_args @lset = Set.new(libpath + syslibpath) @iset = Set.new(cpath.flatten) args = [] enum = @args.each loop do case arg = enum.next when "-arch" if cccfg?("K") args << arg << enum.next else enum.next end when "-m32" args << arg if cccfg?("3") || cccfg?("K") when "-m64" args << arg if cccfg?("K") 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?/, '-gdwarf-2', /^-march=.+/, /^-mtune=.+/, /^-mcpu=.+/, /^-O[0-9zs]?$/, '-fast', '-no-cpp-precomp', '-pedantic', '-pedantic-errors' when '-fopenmp', '-lgomp', '-mno-fused-madd', '-fforce-addr', '-fno-defer-pop', '-mno-dynamic-no-pic', '-fearly-inlining', '-finline-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' # clang doesn't support these flags args << arg if not tool =~ /^clang/ when /^-W[alp],/, /^-Wno-/ 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/ # We set the sysroot enum.next when '-dylib' args << "-Wl,#{arg}" when /^-I(.+)?/ # Support both "-Ifoo" (one argument) and "-I foo" (two arguments) val = chuzzle($1) || enum.next path = canonical_path(val) args << "-I#{val}" if keep?(path) && @iset.add?(path) when /^-L(.+)?/ val = chuzzle($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 case path when %r{^#{Regexp.escape(brewfix)}}o, %r{^#{Regexp.escape(brewtmp)}}o # maybe homebrew is installed to /sw or /opt/brew true when %r{^/opt}, %r{^/sw}, %r{/usr/X11} false else true end end def cflags args = [] return args unless cccfg? 'O' or configure? args << '-pipe' args << '-w' unless configure? args.concat(optflags) args.concat(archflags) args << "-std=#{@arg0}" if @arg0 =~ /c[89]9/ args end def cxxflags args = cflags args << '-std=c++11' if cccfg? 'x' args << '-stdlib=libc++' if cccfg? 'g' args << '-stdlib=libstdc++' if cccfg? 'h' args end def optflags args = [] args << "-#{ENV['HOMEBREW_OPTIMIZATION_LEVEL']}" args.concat ENV['HOMEBREW_OPTFLAGS'].split(' ') if ENV['HOMEBREW_OPTFLAGS'] args end def archflags ENV["HOMEBREW_ARCHFLAGS"].split(" ") end def syspath if sdkroot %W{#{sdkroot}/usr /usr/local} else %W{/usr /usr/local} end end def syslibpath # We reject brew's lib as we explicitly add this as a -L flag, thus it # is given higher priority by cc, so it surpasses the system libpath. # NOTE this only counts if Homebrew is installed at /usr/local syspath.reject { |d| d == brewfix }.map! { |d| File.join(d, "lib") } end # Paths added as "-isystem" and "-I" flags def cpath cpath = path_split("CMAKE_PREFIX_PATH").map! { |d| File.join(d, "include") } cpath += path_split("CMAKE_INCLUDE_PATH") opt = cpath.grep(%r{^#{Regexp.escape(brewfix)}/opt}o) sys = cpath - opt [sys, opt] end # Paths added as "-L" flags def libpath libpath = path_split("CMAKE_PREFIX_PATH").map! { |d| File.join(d, "lib") } libpath += path_split("CMAKE_LIBRARY_PATH") libpath -= syslibpath libpath end def ldflags args = path_flags("-L", libpath) case mode when :ld args << "-headerpad_max_install_names" when :ccld, :cxxld args << "-Wl,-headerpad_max_install_names" end args end # Keg-only opt paths get the "-I" treatment since it has higher priority than # "-isystem", and we want them to be searched before system directories as # well as any directories added by the build system. def cppflags sys, opt = cpath path_flags("-isystem", sys) + path_flags("-I", opt) end def make_fuss args return unless make_fuss? dels = @args - args adds = args - @args LOGGER.puts "superenv removed: #{dels*' '}" unless dels.empty? LOGGER.puts "superenv added: #{adds*' '}" unless adds.empty? end def make_fuss? cccfg? 'O' and not configure? end def configure? # configure scripts generated with autoconf 2.61 or later export as_nl ENV.key? 'as_nl' end def cccfg? flags flags.split('').all?{|c| ENV['HOMEBREW_CCCFG'].include? c } if ENV['HOMEBREW_CCCFG'] 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 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 if __FILE__ == $PROGRAM_NAME ##################################################################### sanity abort "The build-tool has reset ENV. --env=std required." unless ENV['HOMEBREW_BREW_FILE'] if (cc = ENV["HOMEBREW_CC"]).nil? || cc.empty? || cc == "cc" # those values are not allowed ENV['HOMEBREW_CC'] = 'clang' end ####################################################################### main LOGGER.puts "#{File.basename($0)} called with: #{ARGV.join(" ")}" cmd = Cmd.new($0, ARGV) tool, args = cmd.tool, cmd.args LOGGER.puts "superenv executed: #{tool} #{args.join(" ")}" LOGGER.log! exec "xcrun", tool, *args end