관리-도구
편집 파일: compiler.rb
# Phusion Passenger - https://www.phusionpassenger.com/ # Copyright (c) 2010-2018 Phusion Holding B.V. # # "Passenger", "Phusion Passenger" and "Union Station" are registered # trademarks of Phusion Holding B.V. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. PhusionPassenger.require_passenger_lib 'platform_info' PhusionPassenger.require_passenger_lib 'platform_info/operating_system' module PhusionPassenger module PlatformInfo private def self.detect_language_extension(language) case language when :c return "c" when :cxx return "cpp" else raise ArgumentError, "Unsupported language #{language.inspect}" end end private_class_method :detect_language_extension def self.detect_compiler_type_name(language) case language when :c return "C" when :cxx return "C++" else raise ArgumentError, "Unsupported language #{language.inspect}" end end private_class_method :detect_compiler_type_name def self.create_compiler_command(language, flags1, flags2, link = false) case language when :c result = [cc, link ? ENV['EXTRA_PRE_LDFLAGS'] : nil, ENV['EXTRA_PRE_CFLAGS'], flags1, flags2, ENV['EXTRA_CFLAGS'], ENV['EXTRA_LDFLAGS']] when :cxx result = [cxx, link ? ENV['EXTRA_PRE_LDFLAGS'] : nil, ENV['EXTRA_PRE_CXXFLAGS'], flags1, flags2, ENV['EXTRA_CXXFLAGS'], ENV['EXTRA_LDFLAGS']] else raise ArgumentError, "Unsupported language #{language.inspect}" end return result.compact.join(" ").strip end private_class_method :create_compiler_command def self.run_compiler(description, command, source_file, source, capture_output = false) if verbose? message = "#{description}\n" << "Running: #{command}\n" if source.strip.empty? message << "Source file is empty." else message << "Source file contains:\n" << "-------------------------\n" << unindent(source) << "\n-------------------------" end log(message) end if capture_output begin output = `#{command} 2>&1` result = $?.exitstatus == 0 rescue SystemCallError => e result = nil exec_error_reason = e.message end log("Output:\n" << "-------------------------\n" << output.to_s << "\n-------------------------") elsif verbose? result = system(command) else result = system("(#{command}) >/dev/null 2>/dev/null") end if result.nil? log("Command could not be executed! #{exec_error_reason}".strip) return false elsif result log("Check succeeded") if capture_output return { :result => true, :output => output } else return true end else log("Check failed with exit status #{$?.exitstatus}") if capture_output == :always return { :result => false, :output => output } else return false end end end private_class_method :run_compiler def self.cc_or_cxx_supports_feliminate_unused_debug?(language) ext = detect_language_extension(language) compiler_type_name = detect_compiler_type_name(language) create_temp_file("passenger-compile-check.#{ext}") do |filename, f| f.close begin command = create_compiler_command(language, "-c '#{filename}' -o '#{filename}.o'", '-feliminate-unused-debug-symbols -feliminate-unused-debug-types') result = run_compiler("Checking for #{compiler_type_name} compiler '-feliminate-unused-debug-{symbols,types}' support", command, filename, '', true) return result && result[:output].empty? ensure File.unlink("#{filename}.o") rescue nil end end end private_class_method :cc_or_cxx_supports_feliminate_unused_debug? def self.cc_or_cxx_supports_blocks?(language) ext = detect_language_extension(language) compiler_type_name = detect_compiler_type_name(language) command = create_compiler_command(language,"-E -dM",'- </dev/null') result = `#{command}` return result.include? "__BLOCKS__" end private_class_method :cc_or_cxx_supports_blocks? public def self.cc return string_env('CC', default_cc) end memoize :cc def self.cxx return string_env('CXX', default_cxx) end memoize :cxx def self.default_cc # On most platforms, we'll want to use the same compiler as what the rest # of the system uses, so that we generate compatible binaries. That's # most likely the 'cc' command. We used to use 'gcc' by default. # # See for example this issue with OS X Mavericks (10.9). They switched from # GCC to Clang as the default compiler. Since the Nginx by default uses 'cc' # as the compiler, we'll have to do that too. Otherwise we'll get C++ linker # errors because Nginx is compiled with Clang while Phusion Passenger is # compiled with GCC. # https://code.google.com/p/phusion-passenger/issues/detail?id=950 if PlatformInfo.find_command('cc') return 'cc' else return 'gcc' end end def self.default_cxx if PlatformInfo.find_command('c++') return 'c++' else return 'g++' end end def self.cc_is_gcc? `#{cc} -v 2>&1` =~ /gcc version/ end memoize :cc_is_gcc? def self.cxx_is_gcc? `#{cxx} -v 2>&1` =~ /gcc version/ end memoize :cxx_is_gcc? def self.cc_is_clang? `#{cc} --version 2>&1` =~ /clang( version|-)/ end memoize :cc_is_clang? def self.cxx_is_clang? `#{cxx} --version 2>&1` =~ /clang( version|-)/ end memoize :cxx_is_clang? def self.cc_is_sun_studio? `#{cc} -V 2>&1` =~ /Sun C/ || `#{cc} -flags 2>&1` =~ /Sun C/ end memoize :cc_is_sun_studio? def self.cxx_is_sun_studio? `#{cxx} -V 2>&1` =~ /Sun C/ || `#{cxx} -flags 2>&1` =~ /Sun C/ end memoize :cxx_is_sun_studio? # Looks for the given C or C++ header. This works by invoking the compiler and # searching in the compiler's header search path. Returns its full filename, # or true if this function knows that the header exists but can't find it (e.g. # because the compiler cannot tell us what its header search path is). # Returns nil if the header cannot be found. def self.find_header(header_name, language, flags = nil) extension = detect_language_extension(language) create_temp_file("passenger-compile-check.#{extension}") do |filename, f| source = %Q{ #include <#{header_name}> } f.puts(source) f.close begin command = create_compiler_command(language, "-v -c '#{filename}' -o '#{filename}.o'", flags) if result = run_compiler("Checking for #{header_name}", command, filename, source, true) result[:output] =~ /^#include <...> search starts here:$(.+?)^End of search list\.$/m search_paths = $1.to_s.strip.split("\n").map{ |line| line.strip } search_paths.each do |dir| if File.file?("#{dir}/#{header_name}") return "#{dir}/#{header_name}" end end return true else return nil end ensure File.unlink("#{filename}.o") rescue nil end end end def self.try_compile(description, language, source, flags = nil) extension = detect_language_extension(language) create_temp_file("passenger-compile-check.#{extension}") do |filename, f| f.puts(source) f.close command = create_compiler_command(language, "-c '#{filename}' -o '#{filename}.o'", flags) return run_compiler(description, command, filename, source) end end # Like try_compile, but designed for checking whether a warning flag is # supported. Compilers sometimes do not error out upon encountering an # unsupported warning flag, but merely print a warning. This method checks # for that too. def self.try_compile_with_warning_flag(description, language, source, flags = nil) extension = detect_language_extension(language) result = nil create_temp_file("passenger-compile-check.#{extension}") do |filename, f| f.puts(source) f.close command = create_compiler_command(language, "-c '#{filename}' -o '#{filename}.o'", flags) result = run_compiler(description, command, filename, source, true) result = result && result[:result] && result[:output] !~ /unknown warning option|unrecognized command line option/i end return false if !result # For some reason, GCC does not complain about a warning flag # not being supported unless the source contains another error. So we # check for this. create_temp_file("passenger-compile-check.#{extension}") do |filename, f| source = %Q{ void foo() { return error; } } f.puts(source) f.close command = create_compiler_command(language, "-c '#{filename}' -o '#{filename}.o'", flags) result = run_compiler("#{description} (really)", command, filename, source, :always) end result && !result[:output].include?(flags) end def self.try_link(description, language, source, flags = nil) extension = detect_language_extension(language) create_temp_file("passenger-link-check.#{extension}") do |filename, f| f.puts(source) f.close command = create_compiler_command(language, "'#{filename}' -o '#{filename}.out'", flags, true) return run_compiler(description, command, filename, source) end end def self.try_compile_and_run(description, language, source, flags = nil) extension = detect_language_extension(language) create_temp_file("passenger-run-check.#{extension}", tmpexedir) do |filename, f| f.puts(source) f.close command = create_compiler_command(language, "'#{filename}' -o '#{filename}.out'", flags, true) if run_compiler(description, command, filename, source) log("Running #{filename}.out") begin output = `'#{filename}.out' 2>&1` rescue SystemCallError => e log("Command failed: #{e}") return false end status = $?.exitstatus log("Command exited with status #{status}. Output:\n--------------\n#{output}\n--------------") return status == 0 else return false end end end # Checks whether the compiler supports "-arch #{arch}". def self.compiler_supports_architecture?(arch) return try_compile("Checking for C compiler '-arch' support", :c, '', "-arch #{arch}") end def self.cc_supports_visibility_flag? return false if os_name_simple == "aix" return try_compile("Checking for C compiler '-fvisibility' support", :c, '', '-fvisibility=hidden') end memoize :cc_supports_visibility_flag?, true def self.cc_supports_fno_limit_debug_info_flag? try_compile_with_warning_flag( "Checking for C compiler '-fno-limit-debug-info' support", :c, '', '-fno-limit-debug-info') end memoize :cc_supports_fno_limit_debug_info_flag? def self.cxx_supports_visibility_flag? return false if os_name_simple == "aix" return try_compile("Checking for C++ compiler '-fvisibility' support", :cxx, '', '-fvisibility=hidden') end memoize :cxx_supports_visibility_flag?, true def self.cc_supports_wno_attributes_flag? return try_compile_with_warning_flag( "Checking for C compiler '-Wno-attributes' support", :c, '', '-Wno-attributes') end memoize :cc_supports_wno_attributes_flag?, true def self.cxx_supports_wno_attributes_flag? return try_compile_with_warning_flag( "Checking for C++ compiler '-Wno-attributes' support", :cxx, '', '-Wno-attributes') end memoize :cxx_supports_wno_attributes_flag?, true def self.cc_supports_wno_missing_field_initializers_flag? return try_compile_with_warning_flag( "Checking for C compiler '-Wno-missing-field-initializers' support", :c, '', '-Wno-missing-field-initializers') end memoize :cc_supports_wno_missing_field_initializers_flag?, true def self.cxx_supports_wno_missing_field_initializers_flag? return try_compile_with_warning_flag( "Checking for C++ compiler '-Wno-missing-field-initializers' support", :cxx, '', '-Wno-missing-field-initializers') end memoize :cxx_supports_wno_missing_field_initializers_flag?, true def self.cc_supports_wno_unknown_pragmas_flag? return try_compile_with_warning_flag( "Checking for C compiler '-Wno-unknown-pragmas' support", :c, '', '-Wno-unknown-pragmas') end memoize :cc_supports_wno_unknown_pragmas_flag?, true def self.cxx_supports_wno_unknown_pragmas_flag? return try_compile_with_warning_flag( "Checking for C++ compiler '-Wno-unknown-pragmas' support", :cxx, '', '-Wno-unknown-pragmas') end memoize :cxx_supports_wno_unknown_pragmas_flag?, true def self.cxx_supports_wno_unused_local_typedefs_flag? return try_compile_with_warning_flag( "Checking for C++ compiler '-Wno-unused-local-typedefs' support", :cxx, '', '-Wno-unused-local-typedefs') end memoize :cxx_supports_wno_unused_local_typedefs_flag?, true def self.cxx_supports_wno_format_nonliteral_flag? return try_compile_with_warning_flag( "Checking for C++ compiler '-Wno-format-nonliteral' support", :cxx, '', '-Wno-format-nonliteral') end memoize :cxx_supports_wno_format_nonliteral_flag?, true def self.cxx_supports_fno_limit_debug_info_flag? try_compile_with_warning_flag( "Checking for C++ compiler '-fno-limit-debug-info' support", :cxx, '', '-fno-limit-debug-info') end memoize :cxx_supports_fno_limit_debug_info_flag? def self.cc_supports_no_tls_direct_seg_refs_option? return try_compile("Checking for C compiler '-mno-tls-direct-seg-refs' support", :c, '', '-mno-tls-direct-seg-refs') end memoize :cc_supports_no_tls_direct_seg_refs_option?, true def self.cxx_supports_no_tls_direct_seg_refs_option? return try_compile("Checking for C++ compiler '-mno-tls-direct-seg-refs' support", :cxx, '', '-mno-tls-direct-seg-refs') end memoize :cxx_supports_no_tls_direct_seg_refs_option?, true def self.compiler_supports_wno_ambiguous_member_template? try_compile_with_warning_flag( "Checking for C++ compiler '-Wno-ambiguous-member-template' support", :cxx, '', '-Wno-ambiguous-member-template') end memoize :compiler_supports_wno_ambiguous_member_template?, true def self.cc_supports_feliminate_unused_debug? return cc_or_cxx_supports_feliminate_unused_debug?(:c) end memoize :cc_supports_feliminate_unused_debug?, true def self.cxx_supports_feliminate_unused_debug? return cc_or_cxx_supports_feliminate_unused_debug?(:cxx) end memoize :cxx_supports_feliminate_unused_debug?, true def self.cc_block_support_ok? return (os_name_simple != 'macosx' || cc_or_cxx_supports_blocks?(:c) || os_version >= "10.13" ) end memoize :cc_block_support_ok?, true def self.cxx_block_support_ok? return (os_name_simple != 'macosx' || cc_or_cxx_supports_blocks?(:cxx) || os_version >= "10.13" ) end memoize :cxx_block_support_ok?, true # Returns whether compiling C++ with -fvisibility=hidden might result # in tons of useless warnings, like this: # http://code.google.com/p/phusion-passenger/issues/detail?id=526 # This appears to be a bug in older g++ versions: # http://gcc.gnu.org/ml/gcc-patches/2006-07/msg00861.html # Warnings should be suppressed with -Wno-attributes. def self.cc_visibility_flag_generates_warnings? if os_name_simple == "linux" && `#{cc} -v 2>&1` =~ /gcc version (.*?)/ return $1 <= "4.1.2" else return false end end memoize :cc_visibility_flag_generates_warnings?, true def self.cxx_visibility_flag_generates_warnings? if os_name_simple == "linux" && `#{cxx} -v 2>&1` =~ /gcc version (.*?)/ return $1 <= "4.1.2" else return false end end memoize :cxx_visibility_flag_generates_warnings?, true def self.address_sanitizer_flag if cc_is_clang? if `#{cc} --help` =~ /-fsanitize=/ "-fsanitize=address" else "-faddress-sanitizer" end else nil end end memoize :address_sanitizer_flag def self.cxx_11_flag # C++11 support on FreeBSD 10.0 + Clang seems to be bugged. # http://llvm.org/bugs/show_bug.cgi?id=18310 return nil if os_name_simple == "freebsd" source = %{ struct Foo { Foo(Foo &&f) { } }; } if try_compile("Checking for C++ -std=gnu++11 compiler flag", :cxx, source, '-std=gnu++11') return "-std=gnu++11" elsif try_compile("Checking for C++ -std=c++11 compiler flag", :cxx, source, '-std=c++11') return "-std=c++11" else return nil end end memoize :cxx_11_flag, true def self.has_rt_library? return try_link("Checking for -lrt support", :c, "int main() { return 0; }\n", '-lrt') end memoize :has_rt_library?, true def self.has_math_library? return try_link("Checking for -lmath support", :c, "int main() { return 0; }\n", '-lmath') end memoize :has_math_library?, true def self.has_dl_library? return try_link("Checking for -ldl support", :c, "int main() { return 0; }\n", '-ldl') end memoize :has_dl_library?, true def self.has_alloca_h? return try_compile("Checking for alloca.h", :c, '#include <alloca.h>') end memoize :has_alloca_h?, true def self.has_accept4? return try_compile("Checking for accept4()", :c, %Q{ #define _GNU_SOURCE #include <sys/socket.h> static void *foo = accept4; }) end memoize :has_accept4?, true # C compiler flags that should be passed in order to enable debugging information. def self.debugging_cflags # According to OpenBSD's pthreads man page, pthreads do not work # correctly when an app is compiled with -g. It recommends using # -ggdb instead. # # In any case we'll always want to use -ggdb for better GDB debugging. if cc_is_gcc? result = '-ggdb' else result = '-g' end if cc_supports_fno_limit_debug_info_flag? result << ' -fno-limit-debug-info' end result end # C++ compiler flags that should be passed in order to enable debugging information. def self.debugging_cxxflags # According to OpenBSD's pthreads man page, pthreads do not work # correctly when an app is compiled with -g. It recommends using # -ggdb instead. # # In any case we'll always want to use -ggdb for better GDB debugging. if cc_is_gcc? result = '-ggdb' else result = '-g' end if cxx_supports_fno_limit_debug_info_flag? result << ' -fno-limit-debug-info' end result end def self.export_dynamic_flags if os_name_simple == "linux" return '-rdynamic' else return nil end end def self.make return string_env('MAKE', find_command('make')) end memoize :make, true def self.gnu_make if result = string_env('GMAKE') return result else result = find_command('gmake') if !result result = find_command('make') if result if `#{result} --version 2>&1` =~ /GNU/ return result else return nil end else return nil end else return result end end end memoize :gnu_make, true def self.xcode_select_version if find_command('xcode-select') `xcode-select --version` =~ /version (.+)\./ return $1 else return nil end end end end # module PhusionPassenger