mkmf-lite 0.7.4 → 0.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83a4d986b70bc7bcd5c5787245bd5b76f335dc6079124d6bdd18847b128dcb3c
4
- data.tar.gz: c688342d4e86a77221dbbd2dd12fc3c3a77ac92892bb4aab230af11bc50ff5ec
3
+ metadata.gz: 221bc258ee206eb8c19c1705bdcaa769326508d901d8f8720d1b60e7e704ca5c
4
+ data.tar.gz: 3383b7a19d218372b56c2e8a1938af79dcdd2b4636c4fc555069190ccfcb5cb5
5
5
  SHA512:
6
- metadata.gz: c36047013680595f8776cd8e87813ccf4fdf2aac43056d6ffc676c61c262f502e8c3114b3a09ca6b1610eede8ea6c3b59bce4e8631e0e785afd385c789d61aad
7
- data.tar.gz: 0ef5c1481603d4ffdc840d6f468f4ddcaa0e03b81f2648e6395f48df72b21d772c24ff17638db365d2c1316f57e24cff8ac6880b7895b5710178625df43bf4be
6
+ metadata.gz: 53fd358a2dcdab183376c2d809e4c305b5ff022a81e0aa21a75041019745194adc946e1ed54005ea74a0a17f3b7b476efc7227576ae40bf0b3533b64ebce84b6
7
+ data.tar.gz: 291f1e2ac0b2eb7c84501dd5d82d8309569951230f5742f712ab6951bc8bcc7e2c164e96cf944923d93b61d486e9863ccaf9413bfe00cc3b0c4ecdd41c614f12
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGES.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.8.0 - 29-Jun-2026
2
+ * Removed implicit Linux-style library flags from compiler probes.
3
+ * Compile probes now use argv-style command execution and per-probe temporary
4
+ directories.
5
+ * Improved support for include directories with spaces.
6
+ * Added FreeBSD CI coverage.
7
+ * Updated RuboCop configuration for current rubocop-rspec.
8
+
9
+ ## 0.7.5 - 24-Dec-2025
10
+ * Automatically include homebrew lib for have_library on Macs.
11
+ * Modified the have_library to work with or without leading "lib".
12
+
1
13
  ## 0.7.4 - 23-Dec-2025
2
14
  * Added have_library method.
3
15
 
data/ROADMAP.md ADDED
@@ -0,0 +1,104 @@
1
+ # Roadmap
2
+
3
+ This branch focuses on making `mkmf-lite` more platform-neutral, safer to use
4
+ inside applications, and easier to validate across Unix-like systems. The main
5
+ goal is to avoid FreeBSD-specific special cases where a more general portability
6
+ improvement would solve the same problem.
7
+
8
+ ## 0.8.0 - Portability Foundation
9
+
10
+ Keep the public API stable and harden the existing implementation.
11
+
12
+ * Replace hard-coded default libraries in `cpp_libraries`.
13
+ * Stop assuming Linux-style `rt`, `dl`, `crypt`, and `m` availability.
14
+ * Fix the clang branch that currently emits `-Lrt`, `-Ldl`, `-Lcrypt`, and
15
+ `-Lm`; these are library search path flags, not library link flags.
16
+ * Prefer Ruby's `RbConfig` values where they are appropriate, and only add
17
+ explicit libraries when a probe requires them.
18
+ * Use a per-probe temporary directory instead of shared `conftest.c` and
19
+ `conftest.exe` files in `Dir.tmpdir`.
20
+ * Avoid global `Dir.chdir` during probe compilation when practical.
21
+ * Replace shell-built compiler command strings with argv-style execution.
22
+ * Capture compiler diagnostics internally without emitting unwanted output.
23
+ * Add FreeBSD CI coverage, likely through Cirrus CI or a GitHub Actions
24
+ FreeBSD runner.
25
+
26
+ ## 0.9.0 - API Improvements
27
+
28
+ Add clearer extension points while preserving the existing positional API.
29
+
30
+ * Add keyword options for include directories, library directories, compile
31
+ flags, and link flags.
32
+ * Consider examples such as:
33
+
34
+ ```ruby
35
+ have_header('foo.h', include_dirs: ['/usr/local/include'])
36
+ have_library(
37
+ 'foo',
38
+ 'foo_init',
39
+ headers: ['foo.h'],
40
+ lib_dirs: ['/usr/local/lib']
41
+ )
42
+ ```
43
+
44
+ * Add access to the last failed compile command and diagnostics.
45
+ * Consider a small configuration API for global defaults such as compiler,
46
+ include paths, library paths, and extra flags.
47
+ * Keep memoized public probes, but document when memoization applies and how
48
+ callers should think about process lifetime.
49
+
50
+ ## 1.0.0 - Stable Contract
51
+
52
+ Finalize the behavior expected by downstream users.
53
+
54
+ * Document the supported public API and compatibility expectations.
55
+ * Remove unnecessary runtime dependencies where feasible.
56
+ * Document compiler selection, probe memoization, error handling, and platform
57
+ support.
58
+ * Keep the library focused on lightweight compile/link probes rather than
59
+ becoming a replacement for stdlib `mkmf`.
60
+
61
+ ## C Probe Correctness
62
+
63
+ These improvements can land in the earliest release where they fit cleanly.
64
+
65
+ * Print `sizeof` results using `size_t` and `%zu`.
66
+ * Print `offsetof` results with an appropriate unsigned size format.
67
+ * Avoid casting sizes and offsets down to `int`.
68
+ * Review function probes under stricter C modes, where implicit declarations
69
+ and old-style function assumptions may fail.
70
+ * Consider compile-time assertions for probes that only need success or failure.
71
+
72
+ ## Dependency Reduction
73
+
74
+ Reducing dependencies is useful, but should not distract from the portability
75
+ foundation.
76
+
77
+ * Remove `ptools` if it is only needed for `File.which`.
78
+ * Replace `File.which` with a small internal executable lookup based on
79
+ `ENV['PATH']`.
80
+ * Reevaluate `memoist` after the probe and command execution behavior is stable.
81
+
82
+ ## Test Coverage
83
+
84
+ The test suite should exercise command construction and failure modes, not only
85
+ successful local probes.
86
+
87
+ * Add specs for generated compiler/linker arguments.
88
+ * Test include and library directories containing spaces.
89
+ * Test library names with and without a leading `lib` prefix.
90
+ * Test parallel probe invocations to catch temporary file collisions.
91
+ * Add mocked `RbConfig` coverage for Linux, macOS, FreeBSD, Windows/MSVC, and
92
+ JRuby.
93
+ * Add a regression test proving clang does not receive bogus `-Lrt`-style
94
+ flags.
95
+
96
+ ## Documentation
97
+
98
+ Documentation should make the portability model explicit.
99
+
100
+ * Explain that probes compile tiny C programs using Ruby's configured compiler.
101
+ * Clarify how `mkmf-lite` differs from stdlib `mkmf`.
102
+ * Add FFI-oriented examples for common Unix-like use cases.
103
+ * Document how to pass include and library paths for ports-installed libraries,
104
+ especially `/usr/local/include` and `/usr/local/lib` on FreeBSD.
data/lib/mkmf/lite.rb CHANGED
@@ -4,8 +4,8 @@ require 'erb'
4
4
  require 'rbconfig'
5
5
  require 'tmpdir'
6
6
  require 'open3'
7
+ require 'shellwords'
7
8
  require 'ptools'
8
- require 'fileutils'
9
9
  require 'memoist'
10
10
 
11
11
  # The Mkmf module serves as a namespace only.
@@ -16,7 +16,7 @@ module Mkmf
16
16
  extend Memoist
17
17
 
18
18
  # The version of the mkmf-lite library
19
- MKMF_LITE_VERSION = '0.7.4'
19
+ MKMF_LITE_VERSION = '0.8.0'
20
20
 
21
21
  private
22
22
 
@@ -51,27 +51,35 @@ module Mkmf
51
51
  'conftest.c'
52
52
  end
53
53
 
54
- def cpp_out_file
54
+ def cpp_out_file(output_file = 'conftest.exe')
55
55
  if windows_with_cl_compiler?
56
- '/Feconftest.exe'
56
+ "/Fe#{output_file}"
57
57
  else
58
- '-o conftest.exe'
58
+ ['-o', output_file]
59
59
  end
60
60
  end
61
61
 
62
- memoize :cpp_out_file
63
-
64
62
  def cpp_libraries
65
- return nil if windows_with_cl_compiler? || jruby?
63
+ nil
64
+ end
66
65
 
67
- if cpp_command.match?(/clang/i)
68
- '-Lrt -Ldl -Lcrypt -Lm'
69
- else
70
- '-lrt -ldl -lcrypt -lm'
66
+ memoize :cpp_libraries
67
+
68
+ def cpp_library_paths
69
+ paths = []
70
+
71
+ # Add Homebrew library paths on macOS
72
+ if RbConfig::CONFIG['host_os'].match?(/darwin/)
73
+ # Apple Silicon Macs
74
+ paths << '-L/opt/homebrew/lib' if File.directory?('/opt/homebrew/lib')
75
+ # Intel Macs
76
+ paths << '-L/usr/local/lib' if File.directory?('/usr/local/lib')
71
77
  end
78
+
79
+ paths.empty? ? nil : paths
72
80
  end
73
81
 
74
- memoize :cpp_libraries
82
+ memoize :cpp_library_paths
75
83
 
76
84
  public
77
85
 
@@ -117,7 +125,14 @@ module Mkmf
117
125
  #
118
126
  # Returns true if the library can be linked, or false otherwise.
119
127
  #
128
+ # Note: The library name should not include the 'lib' prefix or file
129
+ # extension. For example, use 'xerces-c' not 'libxerces-c' or 'libxerces-c.dylib'.
130
+ # However, if the 'lib' prefix is provided, it will be automatically stripped.
131
+ #
120
132
  def have_library(library, function = nil, headers = [])
133
+ # Strip 'lib' prefix if present (e.g., 'libxerces-c' -> 'xerces-c')
134
+ library = library.sub(/^lib/, '') unless windows_with_cl_compiler?
135
+
121
136
  headers = get_header_string(headers)
122
137
  erb = ERB.new(read_template('have_library.erb'))
123
138
  code = erb.result(binding)
@@ -218,34 +233,31 @@ module Mkmf
218
233
  def build_directory_options(directories)
219
234
  return nil if directories.empty?
220
235
 
221
- directories.map { |dir| "-I#{dir}" }.join(' ')
236
+ directories.flatten.map { |dir| "-I#{dir}" }
222
237
  end
223
238
 
224
- def build_compile_command(command_options = nil, library_options = nil)
225
- command_parts = [cpp_command]
226
- command_parts << command_options if command_options
227
- command_parts << cpp_libraries if cpp_libraries
228
- command_parts << cpp_defs
229
- command_parts << cpp_out_file
230
- command_parts << cpp_source_file
231
- command_parts << library_options if library_options
239
+ def build_compile_command(command_options = nil, library_options = nil, paths = {})
240
+ source_file = paths.fetch(:source_file, cpp_source_file)
241
+ output_file = paths.fetch(:output_file, 'conftest.exe')
232
242
 
233
- command_parts.compact.join(' ')
234
- end
243
+ command_parts = shellwords(cpp_command)
244
+ command_parts.concat(shellwords(command_options))
245
+ command_parts.concat(shellwords(cpp_library_paths))
246
+ command_parts.concat(shellwords(cpp_libraries))
247
+ command_parts.concat(shellwords(cpp_defs))
248
+ command_parts.concat(shellwords(cpp_out_file(output_file)))
249
+ command_parts << source_file
250
+ command_parts.concat(shellwords(library_options))
235
251
 
236
- def with_suppressed_output
237
- stderr_orig = $stderr.dup
238
- stdout_orig = $stdout.dup
239
-
240
- $stderr.reopen(IO::NULL)
241
- $stdout.reopen(IO::NULL)
252
+ command_parts
253
+ end
242
254
 
243
- yield
244
- ensure
245
- $stderr.reopen(stderr_orig)
246
- $stdout.reopen(stdout_orig)
247
- stderr_orig.close
248
- stdout_orig.close
255
+ def shellwords(options)
256
+ if options.is_a?(Array)
257
+ options.flatten.compact.map(&:to_s)
258
+ else
259
+ Shellwords.split(options.to_s)
260
+ end
249
261
  end
250
262
 
251
263
  # Take an array of header file names (or convert it to an array if it's a
@@ -279,56 +291,53 @@ module Mkmf
279
291
  # The code generated is expected to print a number to STDOUT, which
280
292
  # is then grabbed and returned as an integer.
281
293
  #
282
- # Note that $stderr is temporarily redirected to the null device because
283
- # we don't actually care about the reason for failure, though a Ruby
284
- # error is raised if the compilation step fails.
285
- #
286
294
  def try_to_execute(code, command_options = nil)
287
295
  result = 0
288
296
 
289
- Dir.chdir(Dir.tmpdir) do
290
- File.write(cpp_source_file, code)
291
- command = build_compile_command(command_options)
292
-
293
- compilation_successful = with_suppressed_output { system(command) }
294
-
295
- if compilation_successful
296
- conftest = File::ALT_SEPARATOR ? 'conftest.exe' : './conftest.exe'
297
-
298
- Open3.popen3(conftest) do |stdin, stdout, stderr|
299
- stdin.close
300
- stderr.close
301
- output = stdout.gets
302
- result = output&.chomp&.to_i || 0
303
- end
297
+ Dir.mktmpdir('mkmf-lite') do |dir|
298
+ source_file = File.join(dir, cpp_source_file)
299
+ output_file = File.join(dir, 'conftest.exe')
300
+ File.write(source_file, code)
301
+ command = build_compile_command(
302
+ command_options,
303
+ nil,
304
+ :source_file => source_file,
305
+ :output_file => output_file
306
+ )
307
+
308
+ _stdout, stderr, status = Open3.capture3(*command)
309
+
310
+ if status.success?
311
+ output, = Open3.capture2(output_file)
312
+ result = output.chomp.to_i
304
313
  else
305
- raise StandardError, "Failed to compile source code with command '#{command}':\n===\n#{code}==="
314
+ message = "Failed to compile source code with command '#{command.shelljoin}':\n#{stderr}===\n#{code}==="
315
+ raise StandardError, message
306
316
  end
307
317
  end
308
318
 
309
319
  result
310
- ensure
311
- FileUtils.rm_f(File.join(Dir.tmpdir, cpp_source_file))
312
- FileUtils.rm_f(File.join(Dir.tmpdir, 'conftest.exe'))
313
320
  end
314
321
 
315
322
  # Create a temporary bit of C source code in the temp directory, and
316
323
  # try to compile it. If it succeeds, return true. Otherwise, return
317
324
  # false.
318
325
  #
319
- # Note that $stderr is temporarily redirected to the null device because
320
- # we don't actually care about the reason for failure.
321
- #
322
326
  def try_to_compile(code, command_options = nil, library_options = nil)
323
- Dir.chdir(Dir.tmpdir) do
324
- File.write(cpp_source_file, code)
325
- command = build_compile_command(command_options, library_options)
326
-
327
- with_suppressed_output { system(command) }
327
+ Dir.mktmpdir('mkmf-lite') do |dir|
328
+ source_file = File.join(dir, cpp_source_file)
329
+ output_file = File.join(dir, 'conftest.exe')
330
+ File.write(source_file, code)
331
+ command = build_compile_command(
332
+ command_options,
333
+ library_options,
334
+ :source_file => source_file,
335
+ :output_file => output_file
336
+ )
337
+
338
+ _stdout, _stderr, status = Open3.capture3(*command)
339
+ status.success?
328
340
  end
329
- ensure
330
- FileUtils.rm_f(File.join(Dir.tmpdir, cpp_source_file))
331
- FileUtils.rm_f(File.join(Dir.tmpdir, 'conftest.exe'))
332
341
  end
333
342
 
334
343
  # Slurp the contents of the template file for evaluation later.
data/mkmf-lite.gemspec CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'mkmf-lite'
5
5
  spec.summary = 'A lighter version of mkmf designed for use as a library'
6
- spec.version = '0.7.4'
6
+ spec.version = '0.8.0'
7
7
  spec.author = 'Daniel J. Berger'
8
8
  spec.license = 'Apache-2.0'
9
9
  spec.email = 'djberg96@gmail.com'
@@ -9,6 +9,7 @@ require 'rubygems'
9
9
  require 'rspec'
10
10
  require 'mkmf/lite'
11
11
  require 'fileutils'
12
+ require 'tmpdir'
12
13
 
13
14
  RSpec.describe Mkmf::Lite do
14
15
  subject { Class.new{ |obj| obj.extend Mkmf::Lite } }
@@ -20,7 +21,7 @@ RSpec.describe Mkmf::Lite do
20
21
 
21
22
  describe 'constants' do
22
23
  example 'version information' do
23
- expect(described_class::MKMF_LITE_VERSION).to eq('0.7.4')
24
+ expect(described_class::MKMF_LITE_VERSION).to eq('0.8.0')
24
25
  expect(described_class::MKMF_LITE_VERSION).to be_frozen
25
26
  end
26
27
  end
@@ -39,6 +40,16 @@ RSpec.describe Mkmf::Lite do
39
40
  expect{ subject.have_header('stdio.h', '/usr/local/include') }.not_to raise_error
40
41
  expect{ subject.have_header('stdio.h', '/usr/local/include', '/usr/include') }.not_to raise_error
41
42
  end
43
+
44
+ example 'have_header accepts a directory with spaces' do
45
+ Dir.mktmpdir('mkmf lite') do |dir|
46
+ header = 'mkmf_lite_space_header.h'
47
+
48
+ File.write(File.join(dir, header), "#define MKMF_LITE_SPACE_HEADER 1\n")
49
+
50
+ expect(subject.have_header(header, dir)).to be(true)
51
+ end
52
+ end
42
53
  end
43
54
 
44
55
  context 'have_func' do
@@ -194,4 +205,29 @@ RSpec.describe Mkmf::Lite do
194
205
  expect{ subject.have_library('m', 'sqrt', 'math.h', 'bogus') }.to raise_error(ArgumentError)
195
206
  end
196
207
  end
208
+
209
+ describe 'command construction' do
210
+ let(:command_with_spaces) do
211
+ subject.send(
212
+ :build_compile_command,
213
+ ['-I/tmp/include dir'],
214
+ nil,
215
+ :source_file => 'source.c',
216
+ :output_file => 'output file'
217
+ )
218
+ end
219
+
220
+ example 'build_compile_command returns argv-style command parts' do
221
+ expect(command_with_spaces).to include('-I/tmp/include dir')
222
+ expect(command_with_spaces).to include('source.c')
223
+ expect(command_with_spaces.any? { |part| part.include?('output file') }).to be(true)
224
+ end
225
+
226
+ example 'build_compile_command does not include implicit Linux libraries' do
227
+ command = subject.send(:build_compile_command)
228
+
229
+ expect(command).not_to include('-Lrt', '-Ldl', '-Lcrypt', '-Lm')
230
+ expect(command).not_to include('-lrt', '-ldl', '-lcrypt', '-lm')
231
+ end
232
+ end
197
233
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mkmf-lite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel J. Berger
@@ -136,10 +136,9 @@ files:
136
136
  - LICENSE
137
137
  - MANIFEST.md
138
138
  - README.md
139
+ - ROADMAP.md
139
140
  - Rakefile
140
141
  - certs/djberg96_pub.pem
141
- - conftest.c
142
- - conftest.exe
143
142
  - lib/mkmf-lite.rb
144
143
  - lib/mkmf/lite.rb
145
144
  - lib/mkmf/templates/check_offsetof.erb
@@ -179,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
179
178
  - !ruby/object:Gem::Version
180
179
  version: '0'
181
180
  requirements: []
182
- rubygems_version: 3.7.2
181
+ rubygems_version: 4.0.12
183
182
  specification_version: 4
184
183
  summary: A lighter version of mkmf designed for use as a library
185
184
  test_files:
metadata.gz.sig CHANGED
Binary file
data/conftest.c DELETED
@@ -1,5 +0,0 @@
1
- #include <foobar.h>
2
-
3
- int main(){
4
- return 0;
5
- }
data/conftest.exe DELETED
Binary file