• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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++17 -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  target_ruby_minor_versions = ['3.4', '3.3', '3.2', '3.1', '3.0']
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  require 'rake_compiler_dock'
181
182  # Create the windows dlls or create the empty placeholders
183  Rake::Task['dlls'].execute(plat: windows_platforms)
184
185  windows_platforms.each do |plat|
186    run_rake_compiler(plat, <<~EOT)
187      #{prepare_ccache_cmd} && \
188      gem update --system --no-document && \
189      bundle update && \
190      bundle exec rake clean && \
191      bundle exec rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem pkg/#{spec.full_name}.gem \
192        RUBY_CC_VERSION=#{RakeCompilerDock.ruby_cc_version(*target_ruby_minor_versions)} \
193        V=#{verbose} \
194        GRPC_CONFIG=#{grpc_config} \
195        GRPC_RUBY_BUILD_PROCS=#{nproc_override}
196    EOT
197  end
198
199  # Truncate grpc_c.*.ruby files because they're for Windows only and we don't want
200  # them to take up space in the gems that don't target windows.
201  File.truncate('grpc_c.32-msvcrt.ruby', 0)
202  File.truncate('grpc_c.64-msvcrt.ruby', 0)
203  File.truncate('grpc_c.64-ucrt.ruby', 0)
204
205  `mkdir -p src/ruby/nativedebug/symbols`
206  # TODO(apolcyn): make debug symbols work on apple platforms.
207  # Currently we hit "objcopy: grpc_c.bundle: file format not recognized"
208  # TODO(apolcyn): make debug symbols work on aarch64 linux.
209  # Currently we hit "objcopy: Unable to recognise the format of the input file `grpc_c.so'"
210  unix_platforms_without_debug_symbols = ['x86_64-darwin', 'arm64-darwin', 'aarch64-linux']
211
212  unix_platforms.each do |plat|
213    if unix_platforms_without_debug_symbols.include?(plat)
214      debug_symbols_dir = ''
215    else
216      debug_symbols_dir = File.join(Dir.pwd, 'src/ruby/nativedebug/symbols')
217    end
218    run_rake_compiler(plat, <<~EOT)
219      #{prepare_ccache_cmd} && \
220      gem update --system --no-document && \
221      bundle update && \
222      bundle exec rake clean && \
223      export GRPC_RUBY_DEBUG_SYMBOLS_OUTPUT_DIR=#{debug_symbols_dir} && \
224      bundle exec rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem pkg/#{spec.full_name}.gem \
225        RUBY_CC_VERSION=#{RakeCompilerDock.ruby_cc_version(*target_ruby_minor_versions)} \
226        V=#{verbose} \
227        GRPC_CONFIG=#{grpc_config} \
228        GRPC_RUBY_BUILD_PROCS=#{nproc_override}
229    EOT
230  end
231  # Generate debug symbol packages to complement the native libraries we just built
232  unix_platforms.each do |plat|
233    unless unix_platforms_without_debug_symbols.include?(plat)
234      `bash src/ruby/nativedebug/build_package.sh #{plat}`
235      `cp src/ruby/nativedebug/pkg/*.gem pkg/`
236    end
237  end
238end
239
240# Define dependencies between the suites.
241task 'suite:wrapper' => [:compile, :rubocop]
242task 'suite:idiomatic' => 'suite:wrapper'
243task 'suite:bidi' => 'suite:wrapper'
244task 'suite:server' => 'suite:wrapper'
245task 'suite:pb' => 'suite:server'
246
247desc 'Compiles the gRPC extension then runs all the tests'
248task all: ['suite:idiomatic', 'suite:bidi', 'suite:pb', 'suite:server']
249task default: :all
250