1# Copyright 2015 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15require 'etc' 16require 'mkmf' 17require_relative '../../lib/grpc/version.rb' 18 19windows = RUBY_PLATFORM =~ /mingw|mswin/ 20windows_ucrt = RUBY_PLATFORM =~ /(mingw|mswin).*ucrt/ 21bsd = RUBY_PLATFORM =~ /bsd/ 22darwin = RUBY_PLATFORM =~ /darwin/ 23linux = RUBY_PLATFORM =~ /linux/ 24cross_compiling = ENV['RCD_HOST_RUBY_VERSION'] # set by rake-compiler-dock in build containers 25# TruffleRuby uses the Sulong LLVM runtime, which is different from Apple's. 26apple_toolchain = darwin && RUBY_ENGINE != 'truffleruby' 27 28grpc_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..')) 29 30grpc_config = ENV['GRPC_CONFIG'] || 'opt' 31 32ENV['MACOSX_DEPLOYMENT_TARGET'] = '10.14' 33 34def debug_symbols_output_dir 35 d = ENV['GRPC_RUBY_DEBUG_SYMBOLS_OUTPUT_DIR'] 36 return nil if d.nil? or d.size == 0 37 d 38end 39 40def maybe_remove_strip_all_linker_flag(flags) 41 if debug_symbols_output_dir 42 # Hack to prevent automatic stripping during shared library linking. 43 # rake-compiler-dock sets the -s LDFLAG when building rubies for 44 # cross compilation, and this -s flag propagates into RbConfig. Stripping 45 # during the link is problematic because it prevents us from saving 46 # debug symbols. We want to first link our shared library, then save 47 # debug symbols, and only after that strip. 48 flags = flags.split(' ') 49 flags = flags.reject {|flag| flag == '-s'} 50 flags = flags.join(' ') 51 end 52 flags 53end 54 55def env_unset?(name) 56 ENV[name].nil? || ENV[name].size == 0 57end 58 59def inherit_env_or_rbconfig(name) 60 ENV[name] = inherit_rbconfig(name) if env_unset?(name) 61end 62 63def inherit_rbconfig(name, linker_flag: false) 64 value = RbConfig::CONFIG[name] || '' 65 if linker_flag 66 value = maybe_remove_strip_all_linker_flag(value) 67 end 68 p "extconf.rb setting ENV[#{name}] = #{value}" 69 ENV[name] = value 70end 71 72def env_append(name, string) 73 ENV[name] += ' ' + string 74end 75 76# build grpc C-core 77inherit_env_or_rbconfig 'AR' 78inherit_env_or_rbconfig 'CC' 79inherit_env_or_rbconfig 'CXX' 80inherit_env_or_rbconfig 'RANLIB' 81inherit_env_or_rbconfig 'STRIP' 82inherit_rbconfig 'CPPFLAGS' 83inherit_rbconfig('LDFLAGS', linker_flag: true) 84 85ENV['LD'] = ENV['CC'] if env_unset?('LD') 86ENV['LDXX'] = ENV['CXX'] if env_unset?('LDXX') 87 88if RUBY_ENGINE == 'truffleruby' 89 # ensure we can find the system's OpenSSL 90 env_append 'CPPFLAGS', RbConfig::CONFIG['cppflags'] 91end 92 93if apple_toolchain && !cross_compiling 94 ENV['AR'] = 'libtool' 95 ENV['ARFLAGS'] = '-o' 96end 97 98# Don't embed on TruffleRuby (constant-time crypto is unsafe with Sulong, slow build times) 99ENV['EMBED_OPENSSL'] = (RUBY_ENGINE != 'truffleruby').to_s 100# Don't embed on TruffleRuby (the system zlib is already linked for the zlib C extension, slow build times) 101ENV['EMBED_ZLIB'] = (RUBY_ENGINE != 'truffleruby').to_s 102 103ENV['ARCH_FLAGS'] = RbConfig::CONFIG['ARCH_FLAG'] 104if apple_toolchain && !cross_compiling 105 if RUBY_PLATFORM =~ /arm64/ 106 ENV['ARCH_FLAGS'] = '-arch arm64' 107 else 108 ENV['ARCH_FLAGS'] = '-arch i386 -arch x86_64' 109 end 110end 111 112env_append 'CPPFLAGS', '-DGRPC_XDS_USER_AGENT_NAME_SUFFIX="\"RUBY\""' 113 114require_relative '../../lib/grpc/version' 115env_append 'CPPFLAGS', '-DGRPC_XDS_USER_AGENT_VERSION_SUFFIX="\"' + GRPC::VERSION + '\""' 116env_append 'CPPFLAGS', '-DGRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK=1' 117 118output_dir = File.expand_path(RbConfig::CONFIG['topdir']) 119grpc_lib_dir = File.join(output_dir, 'libs', grpc_config) 120ENV['BUILDDIR'] = output_dir 121 122strip_tool = RbConfig::CONFIG['STRIP'] 123strip_tool += ' -x' if apple_toolchain 124 125unless windows 126 puts 'Building internal gRPC into ' + grpc_lib_dir 127 nproc = 4 128 nproc = Etc.nprocessors if Etc.respond_to? :nprocessors 129 nproc_override = ENV['GRPC_RUBY_BUILD_PROCS'] 130 unless nproc_override.nil? or nproc_override.size == 0 131 nproc = nproc_override 132 puts "Overriding make parallelism to #{nproc}" 133 end 134 make = bsd ? 'gmake' : 'make' 135 cmd = "#{make} -j#{nproc} -C #{grpc_root} #{grpc_lib_dir}/libgrpc.a CONFIG=#{grpc_config} Q=" 136 puts "Building grpc native library: #{cmd}" 137 system(cmd) 138 exit 1 unless $? == 0 139end 140 141# C-core built, generate Makefile for ruby extension 142$LDFLAGS = maybe_remove_strip_all_linker_flag($LDFLAGS) 143$DLDFLAGS = maybe_remove_strip_all_linker_flag($DLDFLAGS) 144 145$CFLAGS << ' -DGRPC_RUBY_WINDOWS_UCRT' if windows_ucrt 146$CFLAGS << ' -I' + File.join(grpc_root, 'include') 147$CFLAGS << ' -g' 148 149def have_ruby_abi_version() 150 return true if RUBY_ENGINE == 'truffleruby' 151 # ruby_abi_version is only available in development versions: https://github.com/ruby/ruby/pull/6231 152 # See also discussion for Ruby 3.4 in https://github.com/grpc/grpc/pull/38338 and https://github.com/grpc/grpc/pull/38487 153 return false if RUBY_PATCHLEVEL >= 0 154 155 m = /(\d+)\.(\d+)/.match(RUBY_VERSION) 156 if m.nil? 157 puts "Failed to parse ruby version: #{RUBY_VERSION}. Assuming ruby_abi_version symbol is NOT present." 158 return false 159 end 160 major = m[1].to_i 161 minor = m[2].to_i 162 if major >= 3 and minor >= 2 163 puts "Ruby version #{RUBY_VERSION} >= 3.2. Assuming ruby_abi_version symbol is present." 164 return true 165 end 166 puts "Ruby version #{RUBY_VERSION} < 3.2. Assuming ruby_abi_version symbol is NOT present." 167 false 168end 169 170def ext_export_filename() 171 name = 'ext-export' 172 name += '-truffleruby' if RUBY_ENGINE == 'truffleruby' 173 name += '-with-ruby-abi-version' if have_ruby_abi_version() 174 name 175end 176 177ext_export_file = File.join(grpc_root, 'src', 'ruby', 'ext', 'grpc', ext_export_filename()) 178$LDFLAGS << ' -Wl,--version-script="' + ext_export_file + '.gcc"' if linux 179if apple_toolchain 180 $LDFLAGS << ' -weak_framework CoreFoundation' 181 $LDFLAGS << ' -Wl,-exported_symbols_list,"' + ext_export_file + '.clang"' 182end 183 184$LDFLAGS << ' ' + File.join(grpc_lib_dir, 'libgrpc.a') unless windows 185if grpc_config == 'gcov' 186 $CFLAGS << ' -O0 -fprofile-arcs -ftest-coverage' 187 $LDFLAGS << ' -fprofile-arcs -ftest-coverage -rdynamic' 188end 189 190if grpc_config == 'dbg' 191 $CFLAGS << ' -O0' 192end 193 194# Do not statically link standard libraries on TruffleRuby as this does not work when compiling to bitcode 195if linux && RUBY_ENGINE != 'truffleruby' 196 $LDFLAGS << ' -static-libgcc -static-libstdc++' 197end 198$LDFLAGS << ' -static' if windows 199 200$CFLAGS << ' -std=c11 ' 201$CFLAGS << ' -Wall ' 202$CFLAGS << ' -Wextra ' 203$CFLAGS << ' -pedantic ' 204 205output = File.join('grpc', 'grpc_c') 206puts "extconf.rb $LDFLAGS: #{$LDFLAGS}" 207puts "extconf.rb $DLDFLAGS: #{$DLDFLAGS}" 208puts "extconf.rb $CFLAGS: #{$CFLAGS}" 209puts 'Generating Makefile for ' + output 210create_makefile(output) 211 212ruby_major_minor = /(\d+\.\d+)/.match(RUBY_VERSION).to_s 213debug_symbols = "grpc-#{GRPC::VERSION}-#{RUBY_PLATFORM}-ruby-#{ruby_major_minor}.dbg" 214 215File.open('Makefile.new', 'w') do |o| 216 o.puts 'hijack_remove_unused_artifacts: all remove_unused_artifacts' 217 o.puts 218 o.write(File.read('Makefile')) 219 o.puts 220 o.puts 'remove_unused_artifacts: $(DLLIB)' 221 # Now that the extension library has been linked, we can remove unused artifacts 222 # that take up a lot of disk space. 223 rm_obj_cmd = "rm -rf #{File.join(output_dir, 'objs')}" 224 o.puts "\t$(ECHO) Removing unused object artifacts: #{rm_obj_cmd}" 225 o.puts "\t$(Q) #{rm_obj_cmd}" 226 rm_grpc_core_libs = "rm -f #{grpc_lib_dir}/*.a" 227 o.puts "\t$(ECHO) Removing unused grpc core libraries: #{rm_grpc_core_libs}" 228 o.puts "\t$(Q) #{rm_grpc_core_libs}" 229end 230File.rename('Makefile.new', 'Makefile') 231 232if grpc_config == 'opt' 233 File.open('Makefile.new', 'w') do |o| 234 o.puts 'hijack: all strip' 235 o.puts 236 o.write(File.read('Makefile')) 237 o.puts 238 o.puts 'strip: $(DLLIB)' 239 if debug_symbols_output_dir 240 o.puts "\t$(ECHO) Saving debug symbols in #{debug_symbols_output_dir}/#{debug_symbols}" 241 o.puts "\t$(Q) objcopy --only-keep-debug $(DLLIB) #{debug_symbols_output_dir}/#{debug_symbols}" 242 end 243 o.puts "\t$(ECHO) Stripping $(DLLIB)" 244 o.puts "\t$(Q) #{strip_tool} $(DLLIB)" 245 end 246 File.rename('Makefile.new', 'Makefile') 247end 248 249if ENV['GRPC_RUBY_TEST_ONLY_WORKAROUND_MAKE_INSTALL_BUG'] 250 # Note: this env var setting is intended to work around a problem observed 251 # with the ginstall command on grpc's macos automated test infrastructure, 252 # and is not guaranteed to work in the wild. 253 # Also see https://github.com/rake-compiler/rake-compiler/issues/210. 254 puts 'Overriding the generated Makefile install target to use cp' 255 File.open('Makefile.new', 'w') do |o| 256 File.foreach('Makefile') do |i| 257 if i.start_with?('INSTALL_PROG = ') 258 override = 'INSTALL_PROG = cp' 259 puts "Replacing generated Makefile line: |#{i}|, with: |#{override}|" 260 o.puts override 261 else 262 o.puts i 263 end 264 end 265 end 266 File.rename('Makefile.new', 'Makefile') 267end 268