1# -*- ruby -*- 2require 'rake/extensiontask' 3require 'rspec/core/rake_task' 4require 'rubocop/rake_task' 5require 'bundler/gem_tasks' 6require 'fileutils' 7require 'tmpdir' 8 9require_relative 'build_config.rb' 10 11load 'tools/distrib/rake_compiler_docker_image.rb' 12 13# Add rubocop style checking tasks 14RuboCop::RakeTask.new(:rubocop) do |task| 15 task.options = ['-c', 'src/ruby/.rubocop.yml'] 16 # add end2end tests to formatter but don't add generated proto _pb.rb's 17 task.patterns = ['src/ruby/{lib,spec}/**/*.rb', 'src/ruby/end2end/*.rb'] 18end 19 20spec = Gem::Specification.load('grpc.gemspec') 21 22Gem::PackageTask.new(spec) do |pkg| 23end 24 25# Add the extension compiler task 26Rake::ExtensionTask.new('grpc_c', spec) do |ext| 27 ext.source_pattern = '**/*.{c,h}' 28 ext.ext_dir = File.join('src', 'ruby', 'ext', 'grpc') 29 ext.lib_dir = File.join('src', 'ruby', 'lib', 'grpc') 30 ext.cross_compile = true 31 ext.cross_platform = [ 32 'x86-mingw32', 'x64-mingw32', 'x64-mingw-ucrt', 33 'x86_64-linux', 'x86-linux', 'aarch64-linux', 34 'x86_64-darwin', 'arm64-darwin', 35 ] 36 ext.cross_compiling do |spec| 37 spec.files = spec.files.select { 38 |file| file.start_with?( 39 "src/ruby/bin/", "src/ruby/ext/", "src/ruby/lib/", "src/ruby/pb/") 40 } 41 spec.files += %w( etc/roots.pem grpc_c.32-msvcrt.ruby grpc_c.64-msvcrt.ruby grpc_c.64-ucrt.ruby ) 42 end 43end 44 45CLEAN.add "src/ruby/lib/grpc/[0-9].[0-9]", "src/ruby/lib/grpc/grpc_c.{bundle,so}" 46 47# Define the test suites 48SPEC_SUITES = [ 49 { id: :wrapper, title: 'wrapper layer', files: %w(src/ruby/spec/*.rb) }, 50 { id: :idiomatic, title: 'idiomatic layer', dir: %w(src/ruby/spec/generic), 51 tags: ['~bidi', '~server'] }, 52 { id: :bidi, title: 'bidi tests', dir: %w(src/ruby/spec/generic), 53 tag: 'bidi' }, 54 { id: :server, title: 'rpc server thread tests', dir: %w(src/ruby/spec/generic), 55 tag: 'server' }, 56 { id: :pb, title: 'protobuf service tests', dir: %w(src/ruby/spec/pb) } 57] 58namespace :suite do 59 SPEC_SUITES.each do |suite| 60 desc "Run all specs in the #{suite[:title]} spec suite" 61 RSpec::Core::RakeTask.new(suite[:id]) do |t| 62 ENV['COVERAGE_NAME'] = suite[:id].to_s 63 spec_files = [] 64 suite[:files].each { |f| spec_files += Dir[f] } if suite[:files] 65 66 if suite[:dir] 67 suite[:dir].each { |f| spec_files += Dir["#{f}/**/*_spec.rb"] } 68 end 69 helper = 'src/ruby/spec/spec_helper.rb' 70 spec_files << helper unless spec_files.include?(helper) 71 72 t.pattern = spec_files 73 t.rspec_opts = "--tag #{suite[:tag]}" if suite[:tag] 74 if suite[:tags] 75 t.rspec_opts = suite[:tags].map { |x| "--tag #{x}" }.join(' ') 76 end 77 end 78 end 79end 80 81desc 'Build the Windows gRPC DLLs for Ruby. The argument contains the list of platforms for which to build dll. Empty placeholder files will be created for platforms that were not selected.' 82task 'dlls', [:plat] do |t, args| 83 grpc_config = ENV['GRPC_CONFIG'] || 'opt' 84 verbose = ENV['V'] || '0' 85 # use env variable to set artifact build paralellism 86 nproc_override = ENV['GRPC_RUBY_BUILD_PROCS'] || `nproc`.strip 87 plat_list = args[:plat] 88 89 build_configs = [ 90 { cross: 'x86_64-w64-mingw32', out: 'grpc_c.64-ucrt.ruby', platform: 'x64-mingw-ucrt' }, 91 { cross: 'x86_64-w64-mingw32', out: 'grpc_c.64-msvcrt.ruby', platform: 'x64-mingw32' }, 92 { cross: 'i686-w64-mingw32', out: 'grpc_c.32-msvcrt.ruby', platform: 'x86-mingw32' } 93 ] 94 selected_build_configs = [] 95 build_configs.each do |config| 96 if plat_list.include?(config[:platform]) 97 # build the DLL (as grpc_c.*.ruby) 98 selected_build_configs.append(config) 99 else 100 # create an empty grpc_c.*.ruby file as a placeholder 101 FileUtils.touch config[:out] 102 end 103 end 104 105 env = 'CPPFLAGS="-D_WIN32_WINNT=0x600 -DNTDDI_VERSION=0x06000000 -DUNICODE -D_UNICODE -Wno-unused-variable -Wno-unused-result -DCARES_STATICLIB -Wno-error=conversion -Wno-sign-compare -Wno-parentheses -Wno-format -DWIN32_LEAN_AND_MEAN" ' 106 env += 'CFLAGS="-Wno-incompatible-pointer-types" ' 107 env += 'CXXFLAGS="-std=c++14 -fno-exceptions" ' 108 env += 'LDFLAGS=-static ' 109 env += 'SYSTEM=MINGW32 ' 110 env += 'EMBED_ZLIB=true ' 111 env += 'EMBED_OPENSSL=true ' 112 env += 'BUILDDIR=/tmp ' 113 env += "V=#{verbose} " 114 env += "GRPC_RUBY_BUILD_PROCS=#{nproc_override} " 115 116 out = GrpcBuildConfig::CORE_WINDOWS_DLL 117 118 # propagate env variables with ccache configuration to the rake-compiler-dock docker container 119 # and setup ccache symlinks as needed. 120 # TODO(jtattermusch): deduplicate creation of prepare_ccache_cmd 121 prepare_ccache_cmd = "export GRPC_BUILD_ENABLE_CCACHE=\"#{ENV.fetch('GRPC_BUILD_ENABLE_CCACHE', '')}\" && " 122 prepare_ccache_cmd += "export CCACHE_SECONDARY_STORAGE=\"#{ENV.fetch('CCACHE_SECONDARY_STORAGE', '')}\" && " 123 prepare_ccache_cmd += "export PATH=\"$PATH:/usr/local/bin\" && " 124 prepare_ccache_cmd += "source tools/internal_ci/helper_scripts/prepare_ccache_symlinks_rc " 125 126 selected_build_configs.each do |opt| 127 env_comp = "CC=#{opt[:cross]}-gcc " 128 env_comp += "CXX=#{opt[:cross]}-g++ " 129 env_comp += "LD=#{opt[:cross]}-gcc " 130 env_comp += "LDXX=#{opt[:cross]}-g++ " 131 run_rake_compiler(opt[:platform], <<~EOT) 132 #{prepare_ccache_cmd} && \ 133 gem update --system --no-document && \ 134 #{env} #{env_comp} make -j#{nproc_override} #{out} && \ 135 #{opt[:cross]}-strip -x -S #{out} && \ 136 cp #{out} #{opt[:out]} 137 EOT 138 end 139end 140 141desc 'Build the native gem file under rake_compiler_dock. Optionally one can pass argument to build only native gem for a chosen platform.' 142task 'gem:native', [:plat] do |t, args| 143 verbose = ENV['V'] || '0' 144 145 grpc_config = ENV['GRPC_CONFIG'] || 'opt' 146 ruby_cc_versions = ['3.3.0', '3.2.0', '3.1.0', '3.0.0', '2.7.0'].join(':') 147 selected_plat = "#{args[:plat]}" 148 149 # use env variable to set artifact build paralellism 150 nproc_override = ENV['GRPC_RUBY_BUILD_PROCS'] || `nproc`.strip 151 152 # propagate env variables with ccache configuration to the rake-compiler-dock docker container 153 # and setup ccache symlinks as needed. 154 prepare_ccache_cmd = "export GRPC_BUILD_ENABLE_CCACHE=\"#{ENV.fetch('GRPC_BUILD_ENABLE_CCACHE', '')}\" && " 155 prepare_ccache_cmd += "export CCACHE_SECONDARY_STORAGE=\"#{ENV.fetch('CCACHE_SECONDARY_STORAGE', '')}\" && " 156 prepare_ccache_cmd += "export PATH=\"$PATH:/usr/local/bin\" && " 157 prepare_ccache_cmd += "source tools/internal_ci/helper_scripts/prepare_ccache_symlinks_rc " 158 159 supported_windows_platforms = ['x86-mingw32', 'x64-mingw32', 'x64-mingw-ucrt'] 160 supported_unix_platforms = ['x86_64-linux', 'x86-linux', 'aarch64-linux', 'x86_64-darwin', 'arm64-darwin'] 161 supported_platforms = supported_windows_platforms + supported_unix_platforms 162 163 if selected_plat.empty? 164 # build everything 165 windows_platforms = supported_windows_platforms 166 unix_platforms = supported_unix_platforms 167 else 168 # build only selected platform 169 if supported_windows_platforms.include?(selected_plat) 170 windows_platforms = [selected_plat] 171 unix_platforms = [] 172 elsif supported_unix_platforms.include?(selected_plat) 173 windows_platforms = [] 174 unix_platforms = [selected_plat] 175 else 176 fail "Unsupported platform '#{selected_plat}' passed as an argument." 177 end 178 end 179 180 # Create the windows dlls or create the empty placeholders 181 Rake::Task['dlls'].execute(plat: windows_platforms) 182 183 windows_platforms.each do |plat| 184 run_rake_compiler(plat, <<~EOT) 185 #{prepare_ccache_cmd} && \ 186 gem update --system --no-document && \ 187 bundle && \ 188 bundle exec rake clean && \ 189 bundle exec rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem pkg/#{spec.full_name}.gem \ 190 RUBY_CC_VERSION=#{ruby_cc_versions} \ 191 V=#{verbose} \ 192 GRPC_CONFIG=#{grpc_config} \ 193 GRPC_RUBY_BUILD_PROCS=#{nproc_override} 194 EOT 195 end 196 197 # Truncate grpc_c.*.ruby files because they're for Windows only and we don't want 198 # them to take up space in the gems that don't target windows. 199 File.truncate('grpc_c.32-msvcrt.ruby', 0) 200 File.truncate('grpc_c.64-msvcrt.ruby', 0) 201 File.truncate('grpc_c.64-ucrt.ruby', 0) 202 203 `mkdir -p src/ruby/nativedebug/symbols` 204 # TODO(apolcyn): make debug symbols work on apple platforms. 205 # Currently we hit "objcopy: grpc_c.bundle: file format not recognized" 206 # TODO(apolcyn): make debug symbols work on aarch64 linux. 207 # Currently we hit "objcopy: Unable to recognise the format of the input file `grpc_c.so'" 208 unix_platforms_without_debug_symbols = ['x86_64-darwin', 'arm64-darwin', 'aarch64-linux'] 209 210 unix_platforms.each do |plat| 211 if unix_platforms_without_debug_symbols.include?(plat) 212 debug_symbols_dir = '' 213 else 214 debug_symbols_dir = File.join(Dir.pwd, 'src/ruby/nativedebug/symbols') 215 end 216 run_rake_compiler(plat, <<~EOT) 217 #{prepare_ccache_cmd} && \ 218 gem update --system --no-document && \ 219 bundle && \ 220 bundle exec rake clean && \ 221 export GRPC_RUBY_DEBUG_SYMBOLS_OUTPUT_DIR=#{debug_symbols_dir} && \ 222 bundle exec rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem pkg/#{spec.full_name}.gem \ 223 RUBY_CC_VERSION=#{ruby_cc_versions} \ 224 V=#{verbose} \ 225 GRPC_CONFIG=#{grpc_config} \ 226 GRPC_RUBY_BUILD_PROCS=#{nproc_override} 227 EOT 228 end 229 # Generate debug symbol packages to complement the native libraries we just built 230 unix_platforms.each do |plat| 231 unless unix_platforms_without_debug_symbols.include?(plat) 232 `bash src/ruby/nativedebug/build_package.sh #{plat}` 233 `cp src/ruby/nativedebug/pkg/*.gem pkg/` 234 end 235 end 236end 237 238# Define dependencies between the suites. 239task 'suite:wrapper' => [:compile, :rubocop] 240task 'suite:idiomatic' => 'suite:wrapper' 241task 'suite:bidi' => 'suite:wrapper' 242task 'suite:server' => 'suite:wrapper' 243task 'suite:pb' => 'suite:server' 244 245desc 'Compiles the gRPC extension then runs all the tests' 246task all: ['suite:idiomatic', 'suite:bidi', 'suite:pb', 'suite:server'] 247task default: :all 248