rb_sys 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/rb_sys/cargo_builder/link_flag_converter.rb +25 -0
- data/lib/rb_sys/cargo_builder.rb +333 -0
- data/lib/rb_sys/mkmf.rb +15 -5
- data/lib/rb_sys/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
- data/vendor/rubygems/ext/cargo_builder/link_flag_converter.rb +0 -23
- data/vendor/rubygems/ext/cargo_builder.rb +0 -336
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b22b7aeab8b1c9d4e6ad7225f110ad9274594ac53bf1b6284421bb9728d6cdb2
|
4
|
+
data.tar.gz: d14f49223946ead201af5b97ab3bf872924da318427762db28939201cca10248
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 "
|
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 =
|
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#{
|
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
|
|
data/lib/rb_sys/version.rb
CHANGED
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.
|
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-
|
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
|