rb_sys 0.1.3 → 0.1.14

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: 9d969efd4bca62e26a6a7712b64b3332f38fb461dfced29c6121246909ef5f2b
4
+ data.tar.gz: 0b0000f89e5d9236cdc8ce829dcf930c37a2e57935612ffaa8533b09854eec1f
5
5
  SHA512:
6
- metadata.gz: a2cbd1b6111a37dca8391436b38163c563c0e35f38c0a73f28314f0eb4fd5eabda17ce392b71c20e8e7bb1fc5619fcb8d6dd95443acb35a86ea2830869e90aa7
7
- data.tar.gz: d494ee43053464007c2ecbe7dffdf0acd0b908a8e4d3b5f4b607371472dd70e07c328fd56d692911446030b8e4c8e6fc8d7fd5ce891bb867afc582b969f89ae1
6
+ metadata.gz: 0d93ec24df3af15692ae906a6415854dcdb3c791e7a19228c0d127d92f4853292e435bc32f0ff2748839ad9e2119fea1c5facdf3623676072ea6abe2035a4fa4
7
+ data.tar.gz: 825eb9ed1e5e65fd75869e8302a4b6184281c176e0cad3d460f14b02d471548fa26cb36a68a433d1d6fb9839fa0bb0da80e5e3be55eb38cb596f9997e198209c
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,328 @@
1
+ module RbSys
2
+ class CargoBuilder < Gem::Ext::Builder
3
+ attr_accessor :spec, :runner, :profile, :env, :features, :target, :extra_rustc_args, :dry_run, :ext_dir
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("RB_SYS_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
+ @ext_dir = nil
18
+ end
19
+
20
+ def build(_extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd)
21
+ require "fileutils"
22
+ require "shellwords"
23
+
24
+ build_crate(dest_path, results, args, cargo_dir)
25
+ validate_cargo_build!(dest_path)
26
+ rename_cdylib_for_ruby_compatibility(dest_path)
27
+ finalize_directory(dest_path, lib_dir, cargo_dir)
28
+ results
29
+ end
30
+
31
+ def build_crate(dest_path, results, args, cargo_dir)
32
+ env = build_env
33
+ cmd = cargo_command(dest_path, args)
34
+ runner.call cmd, results, "cargo", cargo_dir, env
35
+
36
+ results
37
+ end
38
+
39
+ def build_env
40
+ build_env = rb_config_env
41
+ build_env["RUBY_STATIC"] = "true" if ruby_static? && ENV.key?("RUBY_STATIC")
42
+ build_env.merge(env)
43
+ end
44
+
45
+ def cargo_command(dest_path, args = [])
46
+ manifest = File.join(ext_dir, "Cargo.toml")
47
+ cargo = ENV.fetch("CARGO", "cargo")
48
+
49
+ cmd = []
50
+ cmd += [cargo, "rustc"]
51
+ cmd += ["--target", target] if target
52
+ cmd += ["--target-dir", dest_path]
53
+ cmd += ["--features", features.join(",")] unless features.empty?
54
+ cmd += ["--manifest-path", manifest]
55
+ cmd += ["--lib"]
56
+ cmd += ["--profile", profile.to_s]
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
+ ]
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
+ unless dry_run
221
+ File.open(deffile_path, "w") do |f|
222
+ f.puts "EXPORTS"
223
+ f.puts "#{export_prefix.strip}Init_#{spec.name}"
224
+ end
225
+ end
226
+
227
+ deffile_path
228
+ end
229
+
230
+ # We have to basically reimplement RbConfig::CONFIG['SOEXT'] here to support
231
+ # Ruby < 2.5
232
+ #
233
+ # @see https://github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185
234
+ def so_ext
235
+ return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT")
236
+
237
+ if win_target?
238
+ "dll"
239
+ elsif darwin_target?
240
+ "dylib"
241
+ else
242
+ "so"
243
+ end
244
+ end
245
+
246
+ # Corresponds to $(LIBPATH) in mkmf
247
+ def mkmf_libpath
248
+ ["-L", "native=#{makefile_config("libdir")}"]
249
+ end
250
+
251
+ def makefile_config(var_name)
252
+ val = RbConfig::MAKEFILE_CONFIG[var_name]
253
+
254
+ return unless val
255
+
256
+ RbConfig.expand(val.dup)
257
+ end
258
+
259
+ # Copied from ExtConfBuilder
260
+ def finalize_directory(dest_path, lib_dir, extension_dir)
261
+ require "fileutils"
262
+ require "tempfile"
263
+
264
+ ext_path = final_extension_path(dest_path)
265
+
266
+ begin
267
+ tmp_dest = Dir.mktmpdir(".gem.", extension_dir)
268
+
269
+ # Some versions of `mktmpdir` return absolute paths, which will break make
270
+ # if the paths contain spaces. However, on Ruby 1.9.x on Windows, relative
271
+ # paths cause all C extension builds to fail.
272
+ #
273
+ # As such, we convert to a relative path unless we are using Ruby 1.9.x on
274
+ # Windows. This means that when using Ruby 1.9.x on Windows, paths with
275
+ # spaces do not work.
276
+ #
277
+ # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940
278
+ tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir)
279
+
280
+ if tmp_dest_relative
281
+ full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
282
+
283
+ # TODO: remove in RubyGems 3
284
+ if Gem.install_extension_in_lib && lib_dir
285
+ FileUtils.mkdir_p lib_dir
286
+ FileUtils.cp_r ext_path, lib_dir, remove_destination: true
287
+ end
288
+
289
+ FileUtils::Entry_.new(full_tmp_dest).traverse do |ent|
290
+ destent = ent.class.new(dest_path, ent.rel)
291
+ destent.exist? || FileUtils.mv(ent.path, destent.path)
292
+ end
293
+ end
294
+ ensure
295
+ FileUtils.rm_rf tmp_dest if tmp_dest
296
+ end
297
+ end
298
+
299
+ def get_relative_path(path, base)
300
+ path[0..base.length - 1] = "." if path.start_with?(base)
301
+ path
302
+ end
303
+
304
+ def profile_target_directory
305
+ case profile.to_sym
306
+ when :release then "release"
307
+ when :dev then "debug"
308
+ else raise "unknown target directory for profile: #{profile}"
309
+ end
310
+ end
311
+
312
+ # Error raised when no cdylib artifact was created
313
+ class DylibNotFoundError < StandardError
314
+ def initialize(dir)
315
+ files = Dir.glob(File.join(dir, "**", "*")).map { |f| "- #{f}" }.join "\n"
316
+
317
+ super <<~MSG
318
+ Dynamic library not found for Rust extension (in #{dir})
319
+
320
+ Make sure you set "crate-type" in Cargo.toml to "cdylib"
321
+
322
+ Found files:
323
+ #{files}
324
+ MSG
325
+ end
326
+ end
327
+ end
328
+ 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, &blk)
12
32
  if target.include?("/")
13
33
  target_prefix, target = File.split(target)
14
34
  target_prefix[0, 0] = "/"
@@ -17,37 +37,72 @@ module RbSys
17
37
  end
18
38
 
19
39
  spec = Struct.new(:name, :metadata).new(target, {})
20
- builder = Gem::Ext::CargoBuilder.new(spec)
21
- srcprefix ||= "$(srcdir)/#{srcprefix}".chomp("/")
40
+ builder = CargoBuilder.new(spec)
41
+
42
+ yield builder if blk
43
+
44
+ srcprefix = "$(srcdir)/#{builder.ext_dir}".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
+
61
+ ifeq ($(RB_SYS_CARGO_PROFILE),release)
62
+ RB_SYS_CARGO_PROFILE_FLAG = --release
63
+ else
64
+ RB_SYS_CARGO_PROFILE_FLAG = --profile $(RB_SYS_CARGO_PROFILE)
65
+ endif
66
+
26
67
  target_prefix = #{target_prefix}
27
- CARGO_PROFILE = release
28
- CLEANLIBS = $(RUSTLIB) $(DLLIB)
68
+ TARGET_NAME = #{target[/\A\w+/]}
69
+ TARGET_ENTRY = #{RbConfig::CONFIG["EXPORT_PREFIX"]}Init_$(TARGET_NAME)
70
+ CLEANLIBS = $(RUSTLIB) $(DLLIB) $(DEFFILE)
29
71
  DISTCLEANDIRS = target/
30
72
  RUBYARCHDIR = $(sitearchdir)$(target_prefix)
31
73
  RUSTLIB = #{dllib_path(builder)}
32
74
  TARGET = #{target}
33
75
  DLLIB = $(TARGET).#{RbConfig::CONFIG["DLEXT"]}
34
-
76
+ TARGET_DIR = #{Dir.pwd}/target/$(RB_SYS_TARGET_DIR)
77
+ DEFFILE = $(TARGET_DIR)/$(TARGET)-$(arch).def
35
78
  #{base_makefile(srcdir)}
36
-
37
79
  #{env_vars(builder)}
38
80
 
39
81
  FORCE: ;
40
82
 
41
- $(DLLIB): FORCE
42
- \t#{cargo_command(srcdir, builder)}
43
- \t$(COPY) "$(RUSTLIB)" $@
83
+ $(TARGET_DIR):
84
+ \t$(ECHO) creating target directory \\($(@)\\)
85
+ \t$(Q) $(MAKEDIRS) $(TARGET_DIR)
86
+
87
+ $(DEFFILE): $(TARGET_DIR)
88
+ \t$(ECHO) generating $(@)
89
+ \t$(Q) ($(COPY) $(srcdir)/$(TARGET).def $@ 2> /dev/null) || (echo EXPORTS && echo $(TARGET_ENTRY)) > $@
90
+
91
+ $(DLLIB): $(DEFFILE) FORCE
92
+ \t$(ECHO) generating $(@) \\("$(RB_SYS_CARGO_PROFILE)"\\)
93
+ \t$(Q) #{full_cargo_command}
94
+ \t$(Q) $(COPY) "$(RUSTLIB)" $@
44
95
 
45
- install: $(DLLIB)
46
- \t$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
96
+ install: $(DLLIB) Makefile
97
+ \t$(ECHO) installing $(DLLIB)
98
+ \t$(Q) $(MAKEDIRS) $(RUBYARCHDIR)
99
+ \t$(Q) $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
47
100
 
48
101
  all: #{$extout ? "install" : "$(DLLIB)"}
49
102
  MAKE
50
103
 
104
+ gsub_cargo_command!(make_install, builder: builder)
105
+
51
106
  File.write("Makefile", make_install)
52
107
  end
53
108
  # rubocop:enable Style/GlobalVars
@@ -63,19 +118,40 @@ module RbSys
63
118
  end
64
119
 
65
120
  def cargo_command(cargo_dir, builder)
121
+ builder.ext_dir = cargo_dir
66
122
  dest_path = File.join(Dir.pwd, "target")
67
- args = []
68
- cargo_cmd = builder.cargo_command(cargo_dir, dest_path, args)
69
- Shellwords.join(cargo_cmd).gsub("\\=", "=")
123
+ args = ARGV.dup
124
+ args.shift if args.first == "--"
125
+ cargo_cmd = builder.cargo_command(dest_path, args)
126
+ Shellwords.join(cargo_cmd).gsub("\\=", "=").gsub(/\Acargo/, "$(CARGO)")
70
127
  end
71
128
 
72
129
  def env_vars(builder)
73
- builder.build_env.map { |k, v| %($(DLLIB): export #{k} = #{v.gsub("\n", '\n')}) }.join("\n")
130
+ lines = builder.build_env.map { |k, v| env_line(k, v) }
131
+ lines << env_line("CC", env_or_makefile_config("CC"))
132
+ lines << env_line("AR", env_or_makefile_config("AR")) unless env_or_makefile_config("AR") == "libtool -static"
133
+ lines.compact.join("\n")
134
+ end
135
+
136
+ def env_line(k, v)
137
+ return unless v
138
+ %($(DLLIB): export #{k} = #{v.gsub("\n", '\n')})
139
+ end
140
+
141
+ def env_or_makefile_config(key)
142
+ ENV[key] || RbConfig::MAKEFILE_CONFIG[key]
74
143
  end
75
144
 
76
145
  def dllib_path(builder)
77
146
  builder.cargo_dylib_path(File.join(Dir.pwd, "target"))
78
147
  end
148
+
149
+ def gsub_cargo_command!(cargo_command, builder:)
150
+ cargo_command.gsub!(/--profile \w+/, "$(RB_SYS_CARGO_PROFILE_FLAG)")
151
+ cargo_command.gsub!(%r{--features \S+}, "--features $(RB_SYS_CARGO_FEATURES)")
152
+ cargo_command.gsub!(%r{/target/\w+/}, "/target/$(RB_SYS_TARGET_DIR)/")
153
+ cargo_command
154
+ end
79
155
  end
80
156
  end
81
157
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RbSys
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.14"
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.1.3
4
+ version: 0.1.14
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-28 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