summaryrefslogtreecommitdiff
path: root/spec/ruby/optional/capi/spec_helper.rb
blob: eda4964b6952c99a1b57df59a34b51d48eccc3ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# Require the main spec_helper.rb at the end to let `ruby ...spec.rb` work

# MRI magic to use built but not installed ruby
$extmk = false

require 'rbconfig'

OBJDIR ||= File.expand_path("../../../ext/#{RUBY_ENGINE}/#{RUBY_VERSION}", __FILE__)

def object_path
  mkdir_p(OBJDIR)
  OBJDIR
end

def compile_extension(name)
  debug = false
  run_mkmf_in_process = RUBY_ENGINE == 'truffleruby'

  core_ext_dir = File.expand_path("../ext", __FILE__)

  spec_caller_location = caller_locations.find { |c| c.path.end_with?('_spec.rb') }
  spec_file_path = spec_caller_location.path
  spec_ext_dir = File.expand_path("../ext", spec_file_path)

  ext = "#{name}_spec"
  lib = "#{object_path}/#{ext}.#{RbConfig::CONFIG['DLEXT']}"
  ruby_header = "#{RbConfig::CONFIG['rubyhdrdir']}/ruby.h"

  if RbConfig::CONFIG["ENABLE_SHARED"] == "yes"
    if PlatformGuard.windows?
      libruby_so = "#{RbConfig::CONFIG['bindir']}/#{RbConfig::CONFIG['LIBRUBY_SO']}"
    else
      libruby_so = "#{RbConfig::CONFIG['libdir']}/#{RbConfig::CONFIG['LIBRUBY_SO']}"
    end
  end

  begin
    mtime = File.mtime(lib)
  rescue Errno::ENOENT
    # not found, then compile
  else
    case # if lib is older than headers, source or libruby, then recompile
    when mtime <= File.mtime("#{core_ext_dir}/rubyspec.h")
    when mtime <= File.mtime("#{spec_ext_dir}/#{ext}.c")
    when mtime <= File.mtime(ruby_header)
    when libruby_so && mtime <= File.mtime(libruby_so)
    else
      return lib # up-to-date
    end
  end

  # Copy needed source files to tmpdir
  tmpdir = tmp("cext_#{name}")
  Dir.mkdir(tmpdir)
  begin
    ["#{core_ext_dir}/rubyspec.h", "#{spec_ext_dir}/#{ext}.c"].each do |file|
      cp file, "#{tmpdir}/#{File.basename(file)}"
    end

    Dir.chdir(tmpdir) do
      if run_mkmf_in_process
        required = require 'mkmf'
        # Reinitialize mkmf if already required
        init_mkmf unless required
        create_makefile(ext, tmpdir)
      else
        File.write("extconf.rb", "require 'mkmf'\n" +
          "$ruby = ENV.values_at('RUBY_EXE', 'RUBY_FLAGS').join(' ')\n" +
          # MRI magic to consider building non-bundled extensions
          "$extout = nil\n" +
          "create_makefile(#{ext.inspect})\n")
        output = ruby_exe("extconf.rb")
        raise "extconf failed:\n#{output}" unless $?.success?
        $stderr.puts output if debug
      end

      # Do not capture stderr as we want to show compiler warnings
      make, opts = setup_make
      output = IO.popen([make, "V=1", "DESTDIR=", opts], &:read)
      raise "#{make} failed:\n#{output}" unless $?.success?
      $stderr.puts output if debug

      cp File.basename(lib), lib
    end
  ensure
    rm_r tmpdir
  end

  File.chmod(0755, lib)
  lib
end

def setup_make
  make = ENV['MAKE']
  make ||= (RbConfig::CONFIG['host_os'].include?("mswin") ? "nmake" : "make")
  make_flags = ENV["MAKEFLAGS"] || ''

  # suppress logo of nmake.exe to stderr
  if File.basename(make, ".*").downcase == "nmake" and !make_flags.include?("l")
    ENV["MAKEFLAGS"] = "l#{make_flags}"
  end

  opts = {}
  if /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ make_flags
    begin
      r = IO.for_fd($1.to_i(10), "rb", autoclose: false)
      w = IO.for_fd($2.to_i(10), "wb", autoclose: false)
    rescue Errno::EBADF
    else
      opts[r] = r
      opts[w] = w
    end
  end

  [make, opts]
end

def load_extension(name)
  require compile_extension(name)
rescue LoadError => e
  if %r{/usr/sbin/execerror ruby "\(ld 3 1 main ([/a-zA-Z0-9_\-.]+_spec\.so)"} =~ e.message
    system('/usr/sbin/execerror', "#{RbConfig::CONFIG["bindir"]}/ruby", "(ld 3 1 main #{$1}")
  end
  raise
end

# Constants
CAPI_SIZEOF_LONG = [0].pack('l!').size

# Require the main spec_helper.rb only here so load_extension() is defined
# when running specs with `ruby ...spec.rb`
require_relative '../../spec_helper'