mini_portile2 2.4.0 → 2.8.5

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.
@@ -1,7 +1,6 @@
1
1
  require 'rbconfig'
2
2
  require 'net/http'
3
3
  require 'net/https'
4
- require 'net/ftp'
5
4
  require 'fileutils'
6
5
  require 'tempfile'
7
6
  require 'digest'
@@ -28,16 +27,76 @@ class Net::HTTP
28
27
  end
29
28
  end
30
29
 
30
+ $MINI_PORTILE_STATIC_LIBS = {}
31
+
31
32
  class MiniPortile
32
- attr_reader :name, :version, :original_host
33
+ DEFAULT_TIMEOUT = 10
34
+
35
+ attr_reader :name, :version, :original_host, :source_directory
33
36
  attr_writer :configure_options
34
37
  attr_accessor :host, :files, :patch_files, :target, :logger
35
38
 
36
39
  def self.windows?
37
- RbConfig::CONFIG['target_os'] =~ /mswin|mingw32/
40
+ target_os =~ /mswin|mingw/
41
+ end
42
+
43
+ # GNU MinGW compiled Ruby?
44
+ def self.mingw?
45
+ target_os =~ /mingw/
46
+ end
47
+
48
+ # MS Visual-C compiled Ruby?
49
+ def self.mswin?
50
+ target_os =~ /mswin/
51
+ end
52
+
53
+ def self.darwin?
54
+ target_os =~ /darwin/
55
+ end
56
+
57
+ def self.freebsd?
58
+ target_os =~ /freebsd/
59
+ end
60
+
61
+ def self.openbsd?
62
+ target_os =~ /openbsd/
63
+ end
64
+
65
+ def self.linux?
66
+ target_os =~ /linux/
67
+ end
68
+
69
+ def self.solaris?
70
+ target_os =~ /solaris/
71
+ end
72
+
73
+ def self.target_os
74
+ RbConfig::CONFIG['target_os']
75
+ end
76
+
77
+ def self.target_cpu
78
+ RbConfig::CONFIG['target_cpu']
79
+ end
80
+
81
+ def self.native_path(path)
82
+ path = File.expand_path(path)
83
+ if File::ALT_SEPARATOR
84
+ path.tr(File::SEPARATOR, File::ALT_SEPARATOR)
85
+ else
86
+ path
87
+ end
88
+ end
89
+
90
+ def self.posix_path(path)
91
+ path = File.expand_path(path)
92
+ if File::ALT_SEPARATOR
93
+ "/" + path.tr(File::ALT_SEPARATOR, File::SEPARATOR).tr(":", File::SEPARATOR)
94
+ else
95
+ path
96
+ end
38
97
  end
39
98
 
40
- def initialize(name, version)
99
+ def initialize(name, version, **kwargs)
41
100
  @name = name
42
101
  @version = version
43
102
  @target = 'ports'
@@ -45,10 +104,27 @@ class MiniPortile
45
104
  @patch_files = []
46
105
  @log_files = {}
47
106
  @logger = STDOUT
107
+ @source_directory = nil
108
+
109
+ @gcc_command = kwargs[:gcc_command]
110
+ @make_command = kwargs[:make_command]
111
+ @open_timeout = kwargs[:open_timeout] || DEFAULT_TIMEOUT
112
+ @read_timeout = kwargs[:read_timeout] || DEFAULT_TIMEOUT
48
113
 
49
114
  @original_host = @host = detect_host
50
115
  end
51
116
 
117
+ def source_directory=(path)
118
+ @source_directory = posix_path(path)
119
+ end
120
+
121
+ def prepare_build_directory
122
+ raise "source_directory is not set" if source_directory.nil?
123
+ output "Building #{@name} from source at '#{source_directory}'"
124
+ FileUtils.mkdir_p(File.join(tmp_path, [name, version].join("-")))
125
+ FileUtils.rm_rf(port_path) # make sure we always re-install
126
+ end
127
+
52
128
  def download
53
129
  files_hashs.each do |file|
54
130
  download_file(file[:url], file[:local_path])
@@ -71,9 +147,9 @@ class MiniPortile
71
147
  when which('git')
72
148
  lambda { |file|
73
149
  message "Running git apply with #{file}... "
74
- # By --work-tree=. git-apply uses the current directory as
75
- # the project root and will not search upwards for .git.
76
- execute('patch', ["git", "--git-dir=.", "--work-tree=.", "apply", "--whitespace=warn", file], :initial_message => false)
150
+ Dir.mktmpdir do |tmp_git_dir|
151
+ execute('patch', ["git", "--git-dir=#{tmp_git_dir}", "--work-tree=.", "apply", "--whitespace=warn", file], :initial_message => false)
152
+ end
77
153
  }
78
154
  when which('patch')
79
155
  lambda { |file|
@@ -100,15 +176,16 @@ class MiniPortile
100
176
  def configure
101
177
  return if configured?
102
178
 
179
+ FileUtils.mkdir_p(tmp_path)
103
180
  cache_file = File.join(tmp_path, 'configure.options_cache')
104
181
  File.open(cache_file, "w") { |f| f.write computed_options.to_s }
105
182
 
183
+ command = Array(File.join((source_directory || "."), "configure"))
106
184
  if RUBY_PLATFORM=~/mingw|mswin/
107
185
  # Windows doesn't recognize the shebang.
108
- execute('configure', %w(sh ./configure) + computed_options)
109
- else
110
- execute('configure', %w(./configure) + computed_options)
186
+ command.unshift("sh")
111
187
  end
188
+ execute('configure', command + computed_options, altlog: "config.log")
112
189
  end
113
190
 
114
191
  def compile
@@ -129,7 +206,7 @@ class MiniPortile
129
206
  end
130
207
 
131
208
  def configured?
132
- configure = File.join(work_path, 'configure')
209
+ configure = File.join((source_directory || work_path), 'configure')
133
210
  makefile = File.join(work_path, 'Makefile')
134
211
  cache_file = File.join(tmp_path, 'configure.options_cache')
135
212
 
@@ -147,9 +224,13 @@ class MiniPortile
147
224
  end
148
225
 
149
226
  def cook
150
- download unless downloaded?
151
- extract
152
- patch
227
+ if source_directory
228
+ prepare_build_directory
229
+ else
230
+ download unless downloaded?
231
+ extract
232
+ patch
233
+ end
153
234
  configure unless configured?
154
235
  compile
155
236
  install unless installed?
@@ -158,19 +239,15 @@ class MiniPortile
158
239
  end
159
240
 
160
241
  def activate
161
- lib_path = File.join(port_path, "lib")
162
242
  vars = {
163
243
  'PATH' => File.join(port_path, 'bin'),
164
- 'CPATH' => File.join(port_path, 'include'),
165
- 'LIBRARY_PATH' => lib_path
244
+ 'CPATH' => include_path,
245
+ 'LIBRARY_PATH' => lib_path,
166
246
  }.reject { |env, path| !File.directory?(path) }
167
247
 
168
248
  output "Activating #{@name} #{@version} (from #{port_path})..."
169
249
  vars.each do |var, path|
170
- full_path = File.expand_path(path)
171
-
172
- # turn into a valid Windows path (if required)
173
- full_path.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
250
+ full_path = native_path(path)
174
251
 
175
252
  # save current variable value
176
253
  old_value = ENV[var] || ''
@@ -182,7 +259,7 @@ class MiniPortile
182
259
 
183
260
  # rely on LDFLAGS when cross-compiling
184
261
  if File.exist?(lib_path) && (@host != @original_host)
185
- full_path = File.expand_path(lib_path)
262
+ full_path = native_path(lib_path)
186
263
 
187
264
  old_value = ENV.fetch("LDFLAGS", "")
188
265
 
@@ -192,11 +269,126 @@ class MiniPortile
192
269
  end
193
270
  end
194
271
 
272
+ # pkg: the pkg-config file name (without the .pc extension)
273
+ # dir: inject the directory path for the pkg-config file (probably only useful for tests)
274
+ # static: the name of the static library archive (without the "lib" prefix or the file extension), or nil for dynamic linking
275
+ #
276
+ # we might be able to be terribly clever and infer the name of the static archive file, but
277
+ # unfortunately projects have so much freedom in what they can report (for name, for libs, etc.)
278
+ # that it feels unreliable to try to do so, so I'm preferring to just have the developer make it
279
+ # explicit.
280
+ def mkmf_config(pkg: nil, dir: nil, static: nil)
281
+ require "mkmf"
282
+
283
+ if pkg
284
+ dir ||= File.join(lib_path, "pkgconfig")
285
+ pcfile = File.join(dir, "#{pkg}.pc")
286
+ unless File.exist?(pcfile)
287
+ raise ArgumentError, "pkg-config file '#{pcfile}' does not exist"
288
+ end
289
+
290
+ output "Configuring MakeMakefile for #{File.basename(pcfile)} (in #{File.dirname(pcfile)})\n"
291
+
292
+ # on macos, pkg-config will not return --cflags without this
293
+ ENV["PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"] = "t"
294
+
295
+ # append to PKG_CONFIG_PATH as we go, so later pkg-config files can depend on earlier ones
296
+ ENV["PKG_CONFIG_PATH"] = [ENV["PKG_CONFIG_PATH"], dir].compact.join(File::PATH_SEPARATOR)
297
+
298
+ incflags = minimal_pkg_config(pcfile, "cflags-only-I")
299
+ cflags = minimal_pkg_config(pcfile, "cflags-only-other")
300
+ if static
301
+ ldflags = minimal_pkg_config(pcfile, "libs-only-L", "static")
302
+ libflags = minimal_pkg_config(pcfile, "libs-only-l", "static")
303
+ else
304
+ ldflags = minimal_pkg_config(pcfile, "libs-only-L")
305
+ libflags = minimal_pkg_config(pcfile, "libs-only-l")
306
+ end
307
+ else
308
+ output "Configuring MakeMakefile for #{@name} #{@version} (from #{path})\n"
309
+
310
+ lib_name = name.sub(/\Alib/, "") # TODO: use delete_prefix when we no longer support ruby 2.4
311
+
312
+ incflags = Dir.exist?(include_path) ? "-I#{include_path}" : ""
313
+ cflags = ""
314
+ ldflags = Dir.exist?(lib_path) ? "-L#{lib_path}" : ""
315
+ libflags = Dir.exist?(lib_path) ? "-l#{lib_name}" : ""
316
+ end
317
+
318
+ if static
319
+ libdir = lib_path
320
+ if pcfile
321
+ variables = minimal_pkg_config(pcfile, "print-variables").split("\n").map(&:strip)
322
+ if variables.include?("libdir")
323
+ libdir = minimal_pkg_config(pcfile, "variable=libdir")
324
+ end
325
+ end
326
+
327
+ #
328
+ # keep track of the libraries we're statically linking against, and fix up ldflags and
329
+ # libflags to make sure we link statically against the recipe's libaries.
330
+ #
331
+ # this avoids the unintentionally dynamically linking against system libraries, and makes sure
332
+ # that if multiple pkg-config files reference each other that we are able to intercept flags
333
+ # from dependent packages that reference the static archive.
334
+ #
335
+ $MINI_PORTILE_STATIC_LIBS[static] = libdir
336
+ static_ldflags = $MINI_PORTILE_STATIC_LIBS.values.map { |v| "-L#{v}" }
337
+ static_libflags = $MINI_PORTILE_STATIC_LIBS.keys.map { |v| "-l#{v}" }
338
+
339
+ # remove `-L#{libdir}` and `-lfoo`. we don't need them since we link against the static
340
+ # archive using the full path.
341
+ ldflags = ldflags.shellsplit.reject { |f| static_ldflags.include?(f) }.shelljoin
342
+ libflags = libflags.shellsplit.reject { |f| static_libflags.include?(f) }.shelljoin
343
+
344
+ # prepend the full path to the static archive to the linker flags
345
+ static_archive = File.join(libdir, "lib#{static}.#{$LIBEXT}")
346
+ libflags = [static_archive, libflags].join(" ").strip
347
+ end
348
+
349
+ # prefer this package by prepending to search paths and library flags
350
+ #
351
+ # convert the ldflags into a list of directories and append to $LIBPATH (instead of just using
352
+ # $LDFLAGS) to ensure we get the `-Wl,-rpath` linker flag for re-finding shared libraries.
353
+ $INCFLAGS = [incflags, $INCFLAGS].join(" ").strip
354
+ libpaths = ldflags.shellsplit.map { |f| f.sub(/\A-L/, "") }
355
+ $LIBPATH = libpaths | $LIBPATH
356
+ $libs = [libflags, $libs].join(" ").strip
357
+
358
+ # prefer this package's compiler flags by appending them to the command line
359
+ $CFLAGS = [$CFLAGS, cflags].join(" ").strip
360
+ $CXXFLAGS = [$CXXFLAGS, cflags].join(" ").strip
361
+ end
362
+
195
363
  def path
196
364
  File.expand_path(port_path)
197
365
  end
198
366
 
199
- private
367
+ def include_path
368
+ File.join(path, "include")
369
+ end
370
+
371
+ def lib_path
372
+ File.join(path, "lib")
373
+ end
374
+
375
+ def gcc_cmd
376
+ (ENV["CC"] || @gcc_command || RbConfig::CONFIG["CC"] || "gcc").dup
377
+ end
378
+
379
+ def make_cmd
380
+ (ENV["MAKE"] || @make_command || ENV["make"] || "make").dup
381
+ end
382
+
383
+ private
384
+
385
+ def native_path(path)
386
+ MiniPortile.native_path(path)
387
+ end
388
+
389
+ def posix_path(path)
390
+ MiniPortile.posix_path(path)
391
+ end
200
392
 
201
393
  def tmp_path
202
394
  "tmp/#{@host}/ports/#{@name}/#{@version}"
@@ -270,15 +462,18 @@ private
270
462
  io.close_write
271
463
  io.read
272
464
  end
273
- raise "invalid gpg key provided" unless /\[GNUPG:\] IMPORT_OK \d+ (?<key_id>[0-9a-f]+)/i =~ gpg_status
465
+ key_ids = gpg_status.scan(/\[GNUPG:\] IMPORT_OK \d+ (?<key_id>[0-9a-f]+)/i).map(&:first)
466
+ raise "invalid gpg key provided" if key_ids.empty?
274
467
 
275
468
  # verify the signature against our keyring
276
469
  gpg_status = IO.popen([gpg_exe, "--status-fd", "1", "--no-default-keyring", "--keyring", KEYRING_NAME, "--verify", signature_file, file[:local_path]], &:read)
277
470
 
278
471
  # remove the key from our keyring
279
- IO.popen([gpg_exe, "--batch", "--yes", "--no-default-keyring", "--keyring", KEYRING_NAME, "--delete-keys", key_id], &:read)
472
+ key_ids.each do |key_id|
473
+ IO.popen([gpg_exe, "--batch", "--yes", "--no-default-keyring", "--keyring", KEYRING_NAME, "--delete-keys", key_id], &:read)
474
+ raise "unable to delete the imported key" unless $?.exitstatus==0
475
+ end
280
476
 
281
- raise "unable to delete the imported key" unless $?.exitstatus==0
282
477
  raise "signature mismatch" unless gpg_status.match(/^\[GNUPG:\] VALIDSIG/)
283
478
 
284
479
  else
@@ -318,6 +513,8 @@ private
318
513
  'z'
319
514
  when '.bz2', '.tbz2'
320
515
  'j'
516
+ when '.xz'
517
+ 'J'
321
518
  when '.Z'
322
519
  'Z'
323
520
  else
@@ -367,24 +564,37 @@ private
367
564
  execute('extract', [tar_exe, "#{tar_compression_switch(filename)}xf", file, "-C", target], {:cd => Dir.pwd, :initial_message => false})
368
565
  end
369
566
 
370
- def execute(action, command, options={})
371
- log_out = log_file(action)
567
+ # command could be an array of args, or one string containing a command passed to the shell. See
568
+ # Process.spawn for more information.
569
+ def execute(action, command, command_opts={})
570
+ opt_message = command_opts.fetch(:initial_message, true)
571
+ opt_debug = command_opts.fetch(:debug, false)
572
+ opt_cd = command_opts.fetch(:cd) { work_path }
573
+ opt_env = command_opts.fetch(:env) { Hash.new }
574
+ opt_altlog = command_opts.fetch(:altlog, nil)
372
575
 
373
- Dir.chdir (options.fetch(:cd){ work_path }) do
374
- if options.fetch(:initial_message){ true }
375
- message "Running '#{action}' for #{@name} #{@version}... "
376
- end
576
+ log_out = log_file(action)
577
+
578
+ Dir.chdir(opt_cd) do
579
+ output "DEBUG: env is #{opt_env.inspect}" if opt_debug
580
+ output "DEBUG: command is #{command.inspect}" if opt_debug
581
+ message "Running '#{action}' for #{@name} #{@version}... " if opt_message
377
582
 
378
583
  if Process.respond_to?(:spawn) && ! RbConfig.respond_to?(:java)
379
- args = [command].flatten + [{[:out, :err]=>[log_out, "a"]}]
584
+ options = {[:out, :err]=>[log_out, "a"]}
585
+ output "DEBUG: options are #{options.inspect}" if opt_debug
586
+ args = [opt_env, command, options].flatten
380
587
  pid = spawn(*args)
381
588
  Process.wait(pid)
382
589
  else
383
- redirected = if command.kind_of?(Array)
384
- %Q{#{command.map(&:shellescape).join(" ")} > #{log_out.shellescape} 2>&1}
385
- else
386
- %Q{#{command} > #{log_out.shellescape} 2>&1}
387
- end
590
+ env_args = opt_env.map { |k,v| "#{k}=#{v}".shellescape }.join(" ")
591
+ c = if command.kind_of?(Array)
592
+ command.map(&:shellescape).join(" ")
593
+ else
594
+ command
595
+ end
596
+ redirected = %Q{env #{env_args} #{c} > #{log_out.shellescape} 2>&1}
597
+ output "DEBUG: final command is #{redirected.inspect}" if opt_debug
388
598
  system redirected
389
599
  end
390
600
 
@@ -392,12 +602,12 @@ private
392
602
  output "OK"
393
603
  return true
394
604
  else
395
- if File.exist? log_out
396
- output "ERROR, review '#{log_out}' to see what happened. Last lines are:"
397
- output("=" * 72)
398
- log_lines = File.readlines(log_out)
399
- output(log_lines[-[log_lines.length, 20].min .. -1])
400
- output("=" * 72)
605
+ output "ERROR. Please review logs to see what happened:\n"
606
+ [log_out, opt_altlog].compact.each do |log|
607
+ next unless File.exist?(log)
608
+ output("----- contents of '#{log}' -----")
609
+ output(File.read(log))
610
+ output("----- end of file -----")
401
611
  end
402
612
  raise "Failed to complete #{action} task"
403
613
  end
@@ -448,7 +658,6 @@ private
448
658
  def download_file_http(url, full_path, count = 3)
449
659
  filename = File.basename(full_path)
450
660
  with_tempfile(filename, full_path) do |temp_file|
451
- progress = 0
452
661
  total = 0
453
662
  params = {
454
663
  "Accept-Encoding" => 'identity',
@@ -457,12 +666,13 @@ private
457
666
  if total
458
667
  new_progress = (bytes * 100) / total
459
668
  message "\rDownloading %s (%3d%%) " % [filename, new_progress]
460
- progress = new_progress
461
669
  else
462
670
  # Content-Length is unavailable because Transfer-Encoding is chunked
463
671
  message "\rDownloading %s " % [filename]
464
672
  end
465
- }
673
+ },
674
+ :open_timeout => @open_timeout,
675
+ :read_timeout => @read_timeout,
466
676
  }
467
677
  proxy_uri = URI.parse(url).scheme.downcase == 'https' ?
468
678
  ENV["https_proxy"] :
@@ -487,7 +697,7 @@ private
487
697
  return download_file(redirect.url, full_path, count-1)
488
698
  rescue => e
489
699
  count = count - 1
490
- puts "#{count} retrie(s) left for #{filename}"
700
+ puts "#{count} retrie(s) left for #{filename} (#{e.message})"
491
701
  if count > 0
492
702
  sleep 1
493
703
  return download_file_http(url, full_path, count)
@@ -505,17 +715,18 @@ private
505
715
  end
506
716
 
507
717
  def download_file_ftp(uri, full_path)
718
+ require "net/ftp"
508
719
  filename = File.basename(uri.path)
509
720
  with_tempfile(filename, full_path) do |temp_file|
510
- progress = 0
511
721
  total = 0
512
722
  params = {
513
723
  :content_length_proc => lambda{|length| total = length },
514
724
  :progress_proc => lambda{|bytes|
515
725
  new_progress = (bytes * 100) / total
516
726
  message "\rDownloading %s (%3d%%) " % [filename, new_progress]
517
- progress = new_progress
518
- }
727
+ },
728
+ :open_timeout => @open_timeout,
729
+ :read_timeout => @read_timeout,
519
730
  }
520
731
  if ENV["ftp_proxy"]
521
732
  _, userinfo, _p_host, _p_port = URI.split(ENV['ftp_proxy'])
@@ -530,6 +741,8 @@ private
530
741
  end
531
742
  output
532
743
  end
744
+ rescue LoadError
745
+ raise LoadError, "Ruby #{RUBY_VERSION} does not provide the net-ftp gem, please add it as a dependency if you need to use FTP"
533
746
  rescue Net::FTPError
534
747
  return false
535
748
  end
@@ -544,13 +757,28 @@ private
544
757
  FileUtils.mv temp_file.path, full_path, :force => true
545
758
  end
546
759
 
547
- def gcc_cmd
548
- cc = ENV["CC"] || RbConfig::CONFIG["CC"] || "gcc"
549
- return cc.dup
550
- end
760
+ #
761
+ # this minimal version of pkg_config is based on ruby 29dc9378 (2023-01-09)
762
+ #
763
+ # specifically with the fix from b90e56e6 to support multiple pkg-config options, and removing
764
+ # code paths that aren't helpful for mini-portile's use case of parsing pc files.
765
+ #
766
+ def minimal_pkg_config(pkg, *pcoptions)
767
+ if pcoptions.empty?
768
+ raise ArgumentError, "no pkg-config options are given"
769
+ end
551
770
 
552
- def make_cmd
553
- m = ENV['MAKE'] || ENV['make'] || 'make'
554
- return m.dup
771
+ if ($PKGCONFIG ||=
772
+ (pkgconfig = MakeMakefile.with_config("pkg-config") {MakeMakefile.config_string("PKG_CONFIG") || "pkg-config"}) &&
773
+ MakeMakefile.find_executable0(pkgconfig) && pkgconfig)
774
+ pkgconfig = $PKGCONFIG
775
+ else
776
+ raise RuntimeError, "pkg-config is not found"
777
+ end
778
+
779
+ pcoptions = Array(pcoptions).map { |o| "--#{o}" }
780
+ response = IO.popen([pkgconfig, *pcoptions, pkg], err:[:child, :out], &:read)
781
+ raise RuntimeError, response unless $?.success?
782
+ response.strip
555
783
  end
556
784
  end
@@ -1,16 +1,24 @@
1
1
  require 'mini_portile2/mini_portile'
2
+ require 'open3'
2
3
 
3
4
  class MiniPortileCMake < MiniPortile
5
+ attr_accessor :system_name
6
+
4
7
  def configure_prefix
5
8
  "-DCMAKE_INSTALL_PREFIX=#{File.expand_path(port_path)}"
6
9
  end
7
10
 
11
+ def initialize(name, version, **kwargs)
12
+ super(name, version, **kwargs)
13
+ @cmake_command = kwargs[:cmake_command]
14
+ @cmake_build_type = kwargs[:cmake_build_type]
15
+ end
16
+
8
17
  def configure_defaults
9
- if MiniPortile.windows?
10
- ['-G "NMake Makefiles"']
11
- else
12
- []
13
- end
18
+ [
19
+ generator_defaults,
20
+ cmake_compile_flags,
21
+ ].flatten
14
22
  end
15
23
 
16
24
  def configure
@@ -19,7 +27,7 @@ class MiniPortileCMake < MiniPortile
19
27
  cache_file = File.join(tmp_path, 'configure.options_cache')
20
28
  File.open(cache_file, "w") { |f| f.write computed_options.to_s }
21
29
 
22
- execute('configure', %w(cmake) + computed_options + ["."])
30
+ execute('configure', [cmake_cmd] + computed_options + ["."])
23
31
  end
24
32
 
25
33
  def configured?
@@ -34,7 +42,103 @@ class MiniPortileCMake < MiniPortile
34
42
  end
35
43
 
36
44
  def make_cmd
37
- return "nmake" if MiniPortile.windows?
45
+ return "nmake" if MiniPortile.mswin?
38
46
  super
39
47
  end
48
+
49
+ def cmake_cmd
50
+ (ENV["CMAKE"] || @cmake_command || "cmake").dup
51
+ end
52
+
53
+ def cmake_build_type
54
+ (ENV["CMAKE_BUILD_TYPE"] || @cmake_build_type || "Release").dup
55
+ end
56
+
57
+ private
58
+
59
+ def generator_defaults
60
+ if MiniPortile.mswin? && generator_available?('NMake')
61
+ ['-G', 'NMake Makefiles']
62
+ elsif MiniPortile.mingw? && generator_available?('MSYS')
63
+ ['-G', 'MSYS Makefiles']
64
+ else
65
+ []
66
+ end
67
+ end
68
+
69
+ def cmake_compile_flags
70
+ c_compiler, cxx_compiler = find_c_and_cxx_compilers(host)
71
+
72
+ # needed to ensure cross-compilation with CMake targets the right CPU and compilers
73
+ [
74
+ "-DCMAKE_SYSTEM_NAME=#{cmake_system_name}",
75
+ "-DCMAKE_SYSTEM_PROCESSOR=#{cpu_type}",
76
+ "-DCMAKE_C_COMPILER=#{c_compiler}",
77
+ "-DCMAKE_CXX_COMPILER=#{cxx_compiler}",
78
+ "-DCMAKE_BUILD_TYPE=#{cmake_build_type}",
79
+ ]
80
+ end
81
+
82
+ def find_compiler(compilers)
83
+ compilers.find { |binary| which(binary) }
84
+ end
85
+
86
+ # configure automatically searches for the right compiler based on the
87
+ # `--host` parameter. However, CMake doesn't have an equivalent feature.
88
+ # Search for the right compiler for the target architecture using
89
+ # some basic heruistics.
90
+ def find_c_and_cxx_compilers(host)
91
+ c_compiler = ENV["CC"]
92
+ cxx_compiler = ENV["CXX"]
93
+
94
+ if MiniPortile.darwin?
95
+ c_compiler ||= 'clang'
96
+ cxx_compiler ||='clang++'
97
+ else
98
+ c_compiler ||= 'gcc'
99
+ cxx_compiler ||= 'g++'
100
+ end
101
+
102
+ c_platform_compiler = "#{host}-#{c_compiler}"
103
+ cxx_platform_compiler = "#{host}-#{cxx_compiler}"
104
+ c_compiler = find_compiler([c_platform_compiler, c_compiler])
105
+ cxx_compiler = find_compiler([cxx_platform_compiler, cxx_compiler])
106
+
107
+ [c_compiler, cxx_compiler]
108
+ end
109
+
110
+ # Full list: https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.4/Modules/CMakeDetermineSystem.cmake?ref_type=tags#L12-31
111
+ def cmake_system_name
112
+ return system_name if system_name
113
+
114
+ if MiniPortile.linux?
115
+ 'Linux'
116
+ elsif MiniPortile.darwin?
117
+ 'Darwin'
118
+ elsif MiniPortile.windows?
119
+ 'Windows'
120
+ elsif MiniPortile.freebsd?
121
+ 'FreeBSD'
122
+ elsif MiniPortile.openbsd?
123
+ 'OpenBSD'
124
+ elsif MiniPortile.solaris?
125
+ 'SunOS'
126
+ else
127
+ raise "Unable to set CMAKE_SYSTEM_NAME for #{MiniPortile.target_os}"
128
+ end
129
+ end
130
+
131
+ def generator_available?(generator_type)
132
+ stdout_str, status = Open3.capture2("#{cmake_cmd} --help")
133
+
134
+ raise 'Unable to determine whether CMake supports #{generator_type} Makefile generator' unless status.success?
135
+
136
+ stdout_str.include?("#{generator_type} Makefiles")
137
+ end
138
+
139
+ def cpu_type
140
+ return 'x86_64' if MiniPortile.target_cpu == 'x64'
141
+
142
+ MiniPortile.target_cpu
143
+ end
40
144
  end
@@ -1,3 +1,3 @@
1
1
  class MiniPortile
2
- VERSION = "2.4.0"
2
+ VERSION = "2.8.5"
3
3
  end