rb_sys 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27affe876fb5901792fe8af81776d7b6b6094b7b97849c1a8894296c6d119f77
4
- data.tar.gz: a1e59e9580925d7d0a470ec33c72f4fe296148efacb546901d40b4fb99c543bf
3
+ metadata.gz: b22b7aeab8b1c9d4e6ad7225f110ad9274594ac53bf1b6284421bb9728d6cdb2
4
+ data.tar.gz: d14f49223946ead201af5b97ab3bf872924da318427762db28939201cca10248
5
5
  SHA512:
6
- metadata.gz: acfa547f5b88a8274c384a6d72953b22eaf5c8b551092caac377a533d80160a1f9fb76dbdf09668957dbe016ed78b6c1e5bfd57bb0ccfaf1cbab85b675353046
7
- data.tar.gz: c56ed902664615dd777a9611fcbe3b7ae2229a8832690c47b568340df9746278939fd82d3b05dea1c7b258116ce2deb59a1c30ac954b648ddb552b922e70eef8
6
+ metadata.gz: '049e3e24630545530e01983ab43288e992e73cfcc74e8b2f070735ece4aa3b1e5488a5fc473410c6539f00d162e8366c0b7aebde3bbda2cdf57f579a6d684f18'
7
+ data.tar.gz: 979ba1c4c4faf11316f576ea1ec103c244bb20dcca42a13e0e23cf7b886e5f249bb8169a14b6bcf2cf22aedf121b53a9e96a6068e6e6432e873924714f6368ae
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RbSys
4
+ class CargoBuilder
5
+ # Converts Ruby link flags into something cargo understands
6
+ class LinkFlagConverter
7
+ def self.convert(arg)
8
+ case arg.chomp
9
+ when /^-L\s*(.+)$/
10
+ ["-L", "native=#{$1}"]
11
+ when /^--library=(\w+\S+)$/, /^-l\s*(\w+\S+)$/
12
+ ["-l", $1]
13
+ when /^-l\s*:lib(\S+).a$/
14
+ ["-l", "static=#{$1}"]
15
+ when /^-l\s*:lib(\S+).(so|dylib|dll)$/
16
+ ["-l", "dylib=#{$1}"]
17
+ when /^-F\s*(.*)$/
18
+ ["-l", "framework=#{$1}"]
19
+ else
20
+ ["-C", "link_arg=#{arg}"]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,333 @@
1
+ module RbSys
2
+ class CargoBuilder < Gem::Ext::Builder
3
+ attr_accessor :spec, :runner, :profile, :env, :features, :target, :extra_rustc_args
4
+
5
+ def initialize(spec)
6
+ require "rubygems/command"
7
+ require_relative "cargo_builder/link_flag_converter"
8
+
9
+ @spec = spec
10
+ @runner = self.class.method(:run)
11
+ @profile = ENV.fetch("CARGO_BUILD_PROFILE", :release).to_sym
12
+ @env = {}
13
+ @features = []
14
+ @target = ENV["CARGO_BUILD_TARGET"]
15
+ @extra_rustc_args = []
16
+ end
17
+
18
+ def build(_extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd)
19
+ require "fileutils"
20
+ require "shellwords"
21
+
22
+ build_crate(dest_path, results, args, cargo_dir)
23
+ validate_cargo_build!(dest_path)
24
+ rename_cdylib_for_ruby_compatibility(dest_path)
25
+ finalize_directory(dest_path, lib_dir, cargo_dir)
26
+ results
27
+ end
28
+
29
+ def build_crate(dest_path, results, args, cargo_dir)
30
+ env = build_env
31
+ cmd = cargo_command(cargo_dir, dest_path, args)
32
+ runner.call cmd, results, "cargo", cargo_dir, env
33
+
34
+ results
35
+ end
36
+
37
+ def build_env
38
+ build_env = rb_config_env
39
+ build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC")
40
+ build_env.merge(env)
41
+ end
42
+
43
+ def cargo_command(cargo_dir, dest_path, args = [])
44
+ manifest = File.join(cargo_dir, "Cargo.toml")
45
+ cargo = ENV.fetch("CARGO", "cargo")
46
+
47
+ cmd = []
48
+ cmd += [cargo, "rustc"]
49
+ cmd += ["--target", target] if target
50
+ cmd += ["--target-dir", dest_path]
51
+ cmd += ["--features", features.join(",")] unless features.empty?
52
+ cmd += ["--manifest-path", manifest]
53
+ cmd += ["--lib"]
54
+ cmd += ["--profile", profile.to_s]
55
+ cmd += ["--locked"] if profile.to_s == "release"
56
+ cmd += Gem::Command.build_args
57
+ cmd += args
58
+ cmd += ["--"]
59
+ cmd += [*cargo_rustc_args(dest_path)]
60
+ cmd += extra_rustc_args
61
+ cmd
62
+ end
63
+
64
+ def cargo_dylib_path(dest_path)
65
+ prefix = so_ext == "dll" ? "" : "lib"
66
+ path_parts = [dest_path]
67
+ path_parts << target if target
68
+ path_parts += [profile_target_directory, "#{prefix}#{cargo_crate_name}.#{so_ext}"]
69
+ File.join(*path_parts)
70
+ end
71
+
72
+ private
73
+
74
+ def rb_config_env
75
+ result = {}
76
+ RbConfig::CONFIG.each { |k, v| result["RBCONFIG_#{k}"] = v }
77
+ result
78
+ end
79
+
80
+ def cargo_rustc_args(dest_dir)
81
+ [
82
+ *linker_args,
83
+ *mkmf_libpath,
84
+ *rustc_dynamic_linker_flags(dest_dir),
85
+ *rustc_lib_flags(dest_dir),
86
+ *platform_specific_rustc_args(dest_dir),
87
+ *debug_flags
88
+ ]
89
+ end
90
+
91
+ def platform_specific_rustc_args(dest_dir, flags = [])
92
+ if mingw_target?
93
+ # On mingw platforms, mkmf adds libruby to the linker flags
94
+ flags += libruby_args(dest_dir)
95
+
96
+ # Make sure ALSR is used on mingw
97
+ # see https://github.com/rust-lang/rust/pull/75406/files
98
+ flags += ["-C", "link-arg=-Wl,--dynamicbase"]
99
+ flags += ["-C", "link-arg=-Wl,--disable-auto-image-base"]
100
+
101
+ # If the gem is installed on a host with build tools installed, but is
102
+ # run on one that isn't the missing libraries will cause the extension
103
+ # to fail on start.
104
+ flags += ["-C", "link-arg=-static-libgcc"]
105
+ end
106
+
107
+ flags
108
+ end
109
+
110
+ # We want to use the same linker that Ruby uses, so that the linker flags from
111
+ # mkmf work properly.
112
+ def linker_args
113
+ # Have to handle CC="cl /nologo" on mswin
114
+ cc_flag = Shellwords.split(makefile_config("CC"))
115
+ linker = cc_flag.shift
116
+ link_args = cc_flag.flat_map { |a| ["-C", "link-arg=#{a}"] }
117
+
118
+ ["-C", "linker=#{linker}", *link_args]
119
+ end
120
+
121
+ def libruby_args(dest_dir)
122
+ libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED")
123
+ raw_libs = Shellwords.split(libs)
124
+ raw_libs.flat_map { |l| ldflag_to_link_modifier(l) }
125
+ end
126
+
127
+ def ruby_static?
128
+ return true if %w[1 true].include?(ENV["RUBY_STATIC"])
129
+
130
+ makefile_config("ENABLE_SHARED") == "no"
131
+ end
132
+
133
+ # Ruby expects the dylib to follow a file name convention for loading
134
+ def rename_cdylib_for_ruby_compatibility(dest_path)
135
+ new_path = final_extension_path(dest_path)
136
+ FileUtils.cp(cargo_dylib_path(dest_path), new_path)
137
+ new_path
138
+ end
139
+
140
+ def validate_cargo_build!(dir)
141
+ dylib_path = cargo_dylib_path(dir)
142
+
143
+ raise DylibNotFoundError, dir unless File.exist?(dylib_path)
144
+
145
+ dylib_path
146
+ end
147
+
148
+ def final_extension_path(dest_path)
149
+ dylib_path = cargo_dylib_path(dest_path)
150
+ dlext_name = "#{spec.name}.#{makefile_config("DLEXT")}"
151
+ dylib_path.gsub(File.basename(dylib_path), dlext_name)
152
+ end
153
+
154
+ def cargo_crate_name
155
+ spec.metadata.fetch("cargo_crate_name", spec.name).tr("-", "_")
156
+ end
157
+
158
+ def rustc_dynamic_linker_flags(dest_dir)
159
+ split_flags("DLDFLAGS")
160
+ .map { |arg| maybe_resolve_ldflag_variable(arg, dest_dir) }
161
+ .compact
162
+ .flat_map { |arg| ldflag_to_link_modifier(arg) }
163
+ end
164
+
165
+ def rustc_lib_flags(dest_dir)
166
+ split_flags("LIBS").flat_map { |arg| ldflag_to_link_modifier(arg) }
167
+ end
168
+
169
+ def split_flags(var)
170
+ Shellwords.split(RbConfig::CONFIG.fetch(var, ""))
171
+ end
172
+
173
+ def ldflag_to_link_modifier(arg)
174
+ LinkFlagConverter.convert(arg)
175
+ end
176
+
177
+ def msvc_target?
178
+ makefile_config("target_os").include?("msvc")
179
+ end
180
+
181
+ def darwin_target?
182
+ makefile_config("target_os").include?("darwin")
183
+ end
184
+
185
+ def mingw_target?
186
+ makefile_config("target_os").include?("mingw")
187
+ end
188
+
189
+ def win_target?
190
+ target_platform = RbConfig::CONFIG["target_os"]
191
+ !!Gem::WIN_PATTERNS.find { |r| target_platform =~ r }
192
+ end
193
+
194
+ # Interpolate substition vars in the arg (i.e. $(DEFFILE))
195
+ def maybe_resolve_ldflag_variable(input_arg, dest_dir)
196
+ var_matches = input_arg.match(/\$\((\w+)\)/)
197
+
198
+ return input_arg unless var_matches
199
+
200
+ var_name = var_matches[1]
201
+
202
+ return input_arg if var_name.nil? || var_name.chomp.empty?
203
+
204
+ case var_name
205
+ # On windows, it is assumed that mkmf has setup an exports file for the
206
+ # extension, so we have to to create one ourselves.
207
+ when "DEFFILE"
208
+ write_deffile(dest_dir)
209
+ else
210
+ RbConfig::CONFIG[var_name]
211
+ end
212
+ end
213
+
214
+ def write_deffile(dest_path)
215
+ dest_dir = File.dirname(final_extension_path(dest_path))
216
+ FileUtils.mkdir_p(dest_dir)
217
+ deffile_path = File.join(dest_dir, "#{spec.name}-#{RbConfig::CONFIG["arch"]}.def")
218
+ export_prefix = makefile_config("EXPORT_PREFIX") || ""
219
+
220
+ File.open(deffile_path, "w") do |f|
221
+ f.puts "EXPORTS"
222
+ f.puts "#{export_prefix.strip}Init_#{spec.name}"
223
+ end
224
+
225
+ deffile_path
226
+ end
227
+
228
+ # We have to basically reimplement RbConfig::CONFIG['SOEXT'] here to support
229
+ # Ruby < 2.5
230
+ #
231
+ # @see https://github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185
232
+ def so_ext
233
+ return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT")
234
+
235
+ if win_target?
236
+ "dll"
237
+ elsif darwin_target?
238
+ "dylib"
239
+ else
240
+ "so"
241
+ end
242
+ end
243
+
244
+ # Corresponds to $(LIBPATH) in mkmf
245
+ def mkmf_libpath
246
+ ["-L", "native=#{makefile_config("libdir")}"]
247
+ end
248
+
249
+ def makefile_config(var_name)
250
+ val = RbConfig::MAKEFILE_CONFIG[var_name]
251
+
252
+ return unless val
253
+
254
+ RbConfig.expand(val.dup)
255
+ end
256
+
257
+ # Good balance between binary size and debugability
258
+ def debug_flags
259
+ return [] if profile == :dev
260
+
261
+ ["-C", "debuginfo=1"]
262
+ end
263
+
264
+ # Copied from ExtConfBuilder
265
+ def finalize_directory(dest_path, lib_dir, extension_dir)
266
+ require "fileutils"
267
+ require "tempfile"
268
+
269
+ ext_path = final_extension_path(dest_path)
270
+
271
+ begin
272
+ tmp_dest = Dir.mktmpdir(".gem.", extension_dir)
273
+
274
+ # Some versions of `mktmpdir` return absolute paths, which will break make
275
+ # if the paths contain spaces. However, on Ruby 1.9.x on Windows, relative
276
+ # paths cause all C extension builds to fail.
277
+ #
278
+ # As such, we convert to a relative path unless we are using Ruby 1.9.x on
279
+ # Windows. This means that when using Ruby 1.9.x on Windows, paths with
280
+ # spaces do not work.
281
+ #
282
+ # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940
283
+ tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir)
284
+
285
+ if tmp_dest_relative
286
+ full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
287
+
288
+ # TODO: remove in RubyGems 3
289
+ if Gem.install_extension_in_lib && lib_dir
290
+ FileUtils.mkdir_p lib_dir
291
+ FileUtils.cp_r ext_path, lib_dir, remove_destination: true
292
+ end
293
+
294
+ FileUtils::Entry_.new(full_tmp_dest).traverse do |ent|
295
+ destent = ent.class.new(dest_path, ent.rel)
296
+ destent.exist? || FileUtils.mv(ent.path, destent.path)
297
+ end
298
+ end
299
+ ensure
300
+ FileUtils.rm_rf tmp_dest if tmp_dest
301
+ end
302
+ end
303
+
304
+ def get_relative_path(path, base)
305
+ path[0..base.length - 1] = "." if path.start_with?(base)
306
+ path
307
+ end
308
+
309
+ def profile_target_directory
310
+ case profile
311
+ when :release then "release"
312
+ when :dev then "debug"
313
+ else raise "unknown target directory for profile: #{profile}"
314
+ end
315
+ end
316
+
317
+ # Error raised when no cdylib artifact was created
318
+ class DylibNotFoundError < StandardError
319
+ def initialize(dir)
320
+ files = Dir.glob(File.join(dir, "**", "*")).map { |f| "- #{f}" }.join "\n"
321
+
322
+ super <<~MSG
323
+ Dynamic library not found for Rust extension (in #{dir})
324
+
325
+ Make sure you set "crate-type" in Cargo.toml to "cdylib"
326
+
327
+ Found files:
328
+ #{files}
329
+ MSG
330
+ end
331
+ end
332
+ end
333
+ end
data/lib/rb_sys/mkmf.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "rubygems/ext"
4
4
  require "shellwords"
5
- require_relative "./../../vendor/rubygems/ext/cargo_builder"
5
+ require_relative "cargo_builder"
6
6
 
7
7
  # Root module
8
8
  module RbSys
@@ -37,17 +37,21 @@ module RbSys
37
37
  end
38
38
 
39
39
  spec = Struct.new(:name, :metadata).new(target, {})
40
- builder = Gem::Ext::CargoBuilder.new(spec)
40
+ builder = CargoBuilder.new(spec)
41
41
 
42
42
  yield builder if blk
43
43
 
44
44
  srcprefix ||= "$(srcdir)/#{srcprefix}".chomp("/")
45
45
  RbConfig.expand(srcdir = srcprefix.dup)
46
46
 
47
+ full_cargo_command = cargo_command(srcdir, builder)
48
+ gsub_cargo_command!(full_cargo_command, builder: builder)
49
+
47
50
  # rubocop:disable Style/GlobalVars
48
51
  make_install = <<~MAKE
52
+ RB_SYS_CARGO_PROFILE ?= #{builder.profile}
53
+ RB_SYS_CARGO_FEATURES ?= #{builder.features.join(",")}
49
54
  target_prefix = #{target_prefix}
50
- CARGO_PROFILE = release
51
55
  CLEANLIBS = $(RUSTLIB) $(DLLIB)
52
56
  DISTCLEANDIRS = target/
53
57
  RUBYARCHDIR = $(sitearchdir)$(target_prefix)
@@ -62,10 +66,10 @@ module RbSys
62
66
  FORCE: ;
63
67
 
64
68
  $(DLLIB): FORCE
65
- \t#{cargo_command(srcdir, builder)}
69
+ \t#{full_cargo_command}
66
70
  \t$(COPY) "$(RUSTLIB)" $@
67
71
 
68
- install: $(DLLIB)
72
+ install: $(DLLIB) Makefile
69
73
  \t$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
70
74
 
71
75
  all: #{$extout ? "install" : "$(DLLIB)"}
@@ -111,6 +115,12 @@ module RbSys
111
115
  def dllib_path(builder)
112
116
  builder.cargo_dylib_path(File.join(Dir.pwd, "target"))
113
117
  end
118
+
119
+ def gsub_cargo_command!(cargo_command, builder:)
120
+ cargo_command.gsub!("--profile #{builder.profile}", "--profile $(RB_SYS_CARGO_PROFILE)")
121
+ cargo_command.gsub!(%r{--features \S+}, "--features $(RB_SYS_CARGO_FEATURES)")
122
+ cargo_command
123
+ end
114
124
  end
115
125
  end
116
126
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RbSys
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.1"
5
5
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rb_sys
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Ker-Seymer
@@ -30,7 +30,7 @@ cert_chain:
30
30
  Rl+ASkq2/1i07TkBpCf+2hq66+h/hx+/Y/KrUzXfe0jtvil0WESkJT2kqRqHWNhD
31
31
  9GKBxaQlXokNDtWCm1/gl6cD8WRZ0N5S4ZGJT1FLLsA=
32
32
  -----END CERTIFICATE-----
33
- date: 2022-05-25 00:00:00.000000000 Z
33
+ date: 2022-06-01 00:00:00.000000000 Z
34
34
  dependencies: []
35
35
  description:
36
36
  email:
@@ -43,11 +43,11 @@ files:
43
43
  - LICENSE-MIT
44
44
  - certs/ianks.pem
45
45
  - lib/rb_sys.rb
46
+ - lib/rb_sys/cargo_builder.rb
47
+ - lib/rb_sys/cargo_builder/link_flag_converter.rb
46
48
  - lib/rb_sys/mkmf.rb
47
49
  - lib/rb_sys/version.rb
48
50
  - sig/rb_sys.rbs
49
- - vendor/rubygems/ext/cargo_builder.rb
50
- - vendor/rubygems/ext/cargo_builder/link_flag_converter.rb
51
51
  homepage: https://github.com/oxidize-rb/rb-sys
52
52
  licenses:
53
53
  - MIT
metadata.gz.sig CHANGED
Binary file
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Gem::Ext::CargoBuilder < Gem::Ext::Builder
4
- # Converts Ruby link flags into something cargo understands
5
- class LinkFlagConverter
6
- def self.convert(arg)
7
- case arg.chomp
8
- when /^-L\s*(.+)$/
9
- ["-L", "native=#{$1}"]
10
- when /^--library=(\w+\S+)$/, /^-l\s*(\w+\S+)$/
11
- ["-l", $1]
12
- when /^-l\s*:lib(\S+).a$/
13
- ["-l", "static=#{$1}"]
14
- when /^-l\s*:lib(\S+).(so|dylib|dll)$/
15
- ["-l", "dylib=#{$1}"]
16
- when /^-F\s*(.*)$/
17
- ["-l", "framework=#{$1}"]
18
- else
19
- ["-C", "link_arg=#{arg}"]
20
- end
21
- end
22
- end
23
- end
@@ -1,336 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This class is used by rubygems to build Rust extensions. It is a thin-wrapper
4
- # over the `cargo rustc` command which takes care of building Rust code in a way
5
- # that Ruby can use.
6
- class Gem::Ext::CargoBuilder < Gem::Ext::Builder
7
- attr_accessor :spec, :runner, :profile, :env, :features, :target, :extra_rustc_args
8
-
9
- def initialize(spec)
10
- require "rubygems/command"
11
- require_relative "cargo_builder/link_flag_converter"
12
-
13
- @spec = spec
14
- @runner = self.class.method(:run)
15
- @profile = ENV.fetch("CARGO_BUILD_PROFILE", :release).to_sym
16
- @env = {}
17
- @features = []
18
- @target = ENV['CARGO_BUILD_TARGET']
19
- @extra_rustc_args = []
20
- end
21
-
22
- def build(_extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd)
23
- require "fileutils"
24
- require "shellwords"
25
-
26
- build_crate(dest_path, results, args, cargo_dir)
27
- validate_cargo_build!(dest_path)
28
- rename_cdylib_for_ruby_compatibility(dest_path)
29
- finalize_directory(dest_path, lib_dir, cargo_dir)
30
- results
31
- end
32
-
33
- def build_crate(dest_path, results, args, cargo_dir)
34
- env = build_env
35
- cmd = cargo_command(cargo_dir, dest_path, args)
36
- runner.call cmd, results, "cargo", cargo_dir, env
37
-
38
- results
39
- end
40
-
41
- def build_env
42
- build_env = rb_config_env
43
- build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC")
44
- build_env.merge(env)
45
- end
46
-
47
- def cargo_command(cargo_dir, dest_path, args = [])
48
- manifest = File.join(cargo_dir, "Cargo.toml")
49
- cargo = ENV.fetch("CARGO", "cargo")
50
-
51
- cmd = []
52
- cmd += [cargo, "rustc"]
53
- cmd += ["--target", target] if target
54
- cmd += ["--target-dir", dest_path]
55
- cmd += ["--features", features.join(",")] unless features.empty?
56
- cmd += ["--manifest-path", manifest]
57
- cmd += ["--lib"]
58
- cmd += ["--profile", profile.to_s]
59
- cmd += ["--locked"] if profile.to_s == 'release'
60
- cmd += Gem::Command.build_args
61
- cmd += args
62
- cmd += ["--"]
63
- cmd += [*cargo_rustc_args(dest_path)]
64
- cmd += extra_rustc_args
65
- cmd
66
- end
67
-
68
- def cargo_dylib_path(dest_path)
69
- prefix = so_ext == "dll" ? "" : "lib"
70
- path_parts = [dest_path]
71
- path_parts << target if target
72
- path_parts += [profile_target_directory, "#{prefix}#{cargo_crate_name}.#{so_ext}"]
73
- File.join(*path_parts)
74
- end
75
-
76
- private
77
-
78
- def rb_config_env
79
- result = {}
80
- RbConfig::CONFIG.each { |k, v| result["RBCONFIG_#{k}"] = v }
81
- result
82
- end
83
-
84
- def cargo_rustc_args(dest_dir)
85
- [
86
- *linker_args,
87
- *mkmf_libpath,
88
- *rustc_dynamic_linker_flags(dest_dir),
89
- *rustc_lib_flags(dest_dir),
90
- *platform_specific_rustc_args(dest_dir),
91
- *debug_flags
92
- ]
93
- end
94
-
95
- def platform_specific_rustc_args(dest_dir, flags = [])
96
- if mingw_target?
97
- # On mingw platforms, mkmf adds libruby to the linker flags
98
- flags += libruby_args(dest_dir)
99
-
100
- # Make sure ALSR is used on mingw
101
- # see https://github.com/rust-lang/rust/pull/75406/files
102
- flags += ["-C", "link-arg=-Wl,--dynamicbase"]
103
- flags += ["-C", "link-arg=-Wl,--disable-auto-image-base"]
104
-
105
- # If the gem is installed on a host with build tools installed, but is
106
- # run on one that isn't the missing libraries will cause the extension
107
- # to fail on start.
108
- flags += ["-C", "link-arg=-static-libgcc"]
109
- end
110
-
111
- flags
112
- end
113
-
114
- # We want to use the same linker that Ruby uses, so that the linker flags from
115
- # mkmf work properly.
116
- def linker_args
117
- # Have to handle CC="cl /nologo" on mswin
118
- cc_flag = Shellwords.split(makefile_config("CC"))
119
- linker = cc_flag.shift
120
- link_args = cc_flag.flat_map { |a| ["-C", "link-arg=#{a}"] }
121
-
122
- ["-C", "linker=#{linker}", *link_args]
123
- end
124
-
125
- def libruby_args(dest_dir)
126
- libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED")
127
- raw_libs = Shellwords.split(libs)
128
- raw_libs.flat_map { |l| ldflag_to_link_modifier(l) }
129
- end
130
-
131
- def ruby_static?
132
- return true if %w[1 true].include?(ENV["RUBY_STATIC"])
133
-
134
- makefile_config("ENABLE_SHARED") == "no"
135
- end
136
-
137
- # Ruby expects the dylib to follow a file name convention for loading
138
- def rename_cdylib_for_ruby_compatibility(dest_path)
139
- new_path = final_extension_path(dest_path)
140
- FileUtils.cp(cargo_dylib_path(dest_path), new_path)
141
- new_path
142
- end
143
-
144
- def validate_cargo_build!(dir)
145
- dylib_path = cargo_dylib_path(dir)
146
-
147
- raise DylibNotFoundError, dir unless File.exist?(dylib_path)
148
-
149
- dylib_path
150
- end
151
-
152
- def final_extension_path(dest_path)
153
- dylib_path = cargo_dylib_path(dest_path)
154
- dlext_name = "#{spec.name}.#{makefile_config("DLEXT")}"
155
- dylib_path.gsub(File.basename(dylib_path), dlext_name)
156
- end
157
-
158
- def cargo_crate_name
159
- spec.metadata.fetch("cargo_crate_name", spec.name).tr("-", "_")
160
- end
161
-
162
- def rustc_dynamic_linker_flags(dest_dir)
163
- split_flags("DLDFLAGS")
164
- .map { |arg| maybe_resolve_ldflag_variable(arg, dest_dir) }
165
- .compact
166
- .flat_map { |arg| ldflag_to_link_modifier(arg) }
167
- end
168
-
169
- def rustc_lib_flags(dest_dir)
170
- split_flags("LIBS").flat_map { |arg| ldflag_to_link_modifier(arg) }
171
- end
172
-
173
- def split_flags(var)
174
- Shellwords.split(RbConfig::CONFIG.fetch(var, ""))
175
- end
176
-
177
- def ldflag_to_link_modifier(arg)
178
- LinkFlagConverter.convert(arg)
179
- end
180
-
181
- def msvc_target?
182
- makefile_config("target_os").include?("msvc")
183
- end
184
-
185
- def darwin_target?
186
- makefile_config("target_os").include?("darwin")
187
- end
188
-
189
- def mingw_target?
190
- makefile_config("target_os").include?("mingw")
191
- end
192
-
193
- def win_target?
194
- target_platform = RbConfig::CONFIG["target_os"]
195
- !!Gem::WIN_PATTERNS.find { |r| target_platform =~ r }
196
- end
197
-
198
- # Interpolate substition vars in the arg (i.e. $(DEFFILE))
199
- def maybe_resolve_ldflag_variable(input_arg, dest_dir)
200
- var_matches = input_arg.match(/\$\((\w+)\)/)
201
-
202
- return input_arg unless var_matches
203
-
204
- var_name = var_matches[1]
205
-
206
- return input_arg if var_name.nil? || var_name.chomp.empty?
207
-
208
- case var_name
209
- # On windows, it is assumed that mkmf has setup an exports file for the
210
- # extension, so we have to to create one ourselves.
211
- when "DEFFILE"
212
- write_deffile(dest_dir)
213
- else
214
- RbConfig::CONFIG[var_name]
215
- end
216
- end
217
-
218
- def write_deffile(dest_path)
219
- dest_dir = File.dirname(final_extension_path(dest_path))
220
- FileUtils.mkdir_p(dest_dir)
221
- deffile_path = File.join(dest_dir, "#{spec.name}-#{RbConfig::CONFIG["arch"]}.def")
222
- export_prefix = makefile_config("EXPORT_PREFIX") || ""
223
-
224
- File.open(deffile_path, "w") do |f|
225
- f.puts "EXPORTS"
226
- f.puts "#{export_prefix.strip}Init_#{spec.name}"
227
- end
228
-
229
- deffile_path
230
- end
231
-
232
- # We have to basically reimplement RbConfig::CONFIG['SOEXT'] here to support
233
- # Ruby < 2.5
234
- #
235
- # @see https://github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185
236
- def so_ext
237
- return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT")
238
-
239
- if win_target?
240
- "dll"
241
- elsif darwin_target?
242
- "dylib"
243
- else
244
- "so"
245
- end
246
- end
247
-
248
- # Corresponds to $(LIBPATH) in mkmf
249
- def mkmf_libpath
250
- ["-L", "native=#{makefile_config("libdir")}"]
251
- end
252
-
253
- def makefile_config(var_name)
254
- val = RbConfig::MAKEFILE_CONFIG[var_name]
255
-
256
- return unless val
257
-
258
- RbConfig.expand(val.dup)
259
- end
260
-
261
- # Good balance between binary size and debugability
262
- def debug_flags
263
- return [] if profile == :dev
264
-
265
- ["-C", "debuginfo=1"]
266
- end
267
-
268
- # Copied from ExtConfBuilder
269
- def finalize_directory(dest_path, lib_dir, extension_dir)
270
- require "fileutils"
271
- require "tempfile"
272
-
273
- ext_path = final_extension_path(dest_path)
274
-
275
- begin
276
- tmp_dest = Dir.mktmpdir(".gem.", extension_dir)
277
-
278
- # Some versions of `mktmpdir` return absolute paths, which will break make
279
- # if the paths contain spaces. However, on Ruby 1.9.x on Windows, relative
280
- # paths cause all C extension builds to fail.
281
- #
282
- # As such, we convert to a relative path unless we are using Ruby 1.9.x on
283
- # Windows. This means that when using Ruby 1.9.x on Windows, paths with
284
- # spaces do not work.
285
- #
286
- # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940
287
- tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir)
288
-
289
- if tmp_dest_relative
290
- full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
291
-
292
- # TODO: remove in RubyGems 3
293
- if Gem.install_extension_in_lib && lib_dir
294
- FileUtils.mkdir_p lib_dir
295
- FileUtils.cp_r ext_path, lib_dir, remove_destination: true
296
- end
297
-
298
- FileUtils::Entry_.new(full_tmp_dest).traverse do |ent|
299
- destent = ent.class.new(dest_path, ent.rel)
300
- destent.exist? || FileUtils.mv(ent.path, destent.path)
301
- end
302
- end
303
- ensure
304
- FileUtils.rm_rf tmp_dest if tmp_dest
305
- end
306
- end
307
-
308
- def get_relative_path(path, base)
309
- path[0..base.length - 1] = "." if path.start_with?(base)
310
- path
311
- end
312
-
313
- def profile_target_directory
314
- case profile
315
- when :release then "release"
316
- when :dev then "debug"
317
- else raise "unknown target directory for profile: #{profile}"
318
- end
319
- end
320
-
321
- # Error raised when no cdylib artifact was created
322
- class DylibNotFoundError < StandardError
323
- def initialize(dir)
324
- files = Dir.glob(File.join(dir, "**", "*")).map { |f| "- #{f}" }.join "\n"
325
-
326
- super <<~MSG
327
- Dynamic library not found for Rust extension (in #{dir})
328
-
329
- Make sure you set "crate-type" in Cargo.toml to "cdylib"
330
-
331
- Found files:
332
- #{files}
333
- MSG
334
- end
335
- end
336
- end