rb_sys 0.1.3 → 0.9.2

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