rb_sys 0.9.0 → 0.9.3

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