mini_portile2 2.1.0 → 2.8.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,14 @@
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
- require 'digest/md5'
6
+ require 'digest'
8
7
  require 'open-uri'
9
8
  require 'cgi'
10
9
  require 'rbconfig'
11
10
  require 'shellwords'
11
+ require 'open3'
12
12
 
13
13
  # Monkey patch for Net::HTTP by ruby open-uri fix:
14
14
  # https://github.com/ruby/ruby/commit/58835a9
@@ -28,23 +28,105 @@ class Net::HTTP
28
28
  end
29
29
  end
30
30
 
31
+ $MINI_PORTILE_STATIC_LIBS = {}
32
+
31
33
  class MiniPortile
32
- attr_reader :name, :version, :original_host
34
+ DEFAULT_TIMEOUT = 10
35
+
36
+ attr_reader :name, :version, :original_host, :source_directory
33
37
  attr_writer :configure_options
34
38
  attr_accessor :host, :files, :patch_files, :target, :logger
35
39
 
36
- def initialize(name, version)
40
+ def self.windows?
41
+ target_os =~ /mswin|mingw/
42
+ end
43
+
44
+ # GNU MinGW compiled Ruby?
45
+ def self.mingw?
46
+ target_os =~ /mingw/
47
+ end
48
+
49
+ # MS Visual-C compiled Ruby?
50
+ def self.mswin?
51
+ target_os =~ /mswin/
52
+ end
53
+
54
+ def self.darwin?
55
+ target_os =~ /darwin/
56
+ end
57
+
58
+ def self.freebsd?
59
+ target_os =~ /freebsd/
60
+ end
61
+
62
+ def self.openbsd?
63
+ target_os =~ /openbsd/
64
+ end
65
+
66
+ def self.linux?
67
+ target_os =~ /linux/
68
+ end
69
+
70
+ def self.solaris?
71
+ target_os =~ /solaris/
72
+ end
73
+
74
+ def self.target_os
75
+ RbConfig::CONFIG['target_os']
76
+ end
77
+
78
+ def self.target_cpu
79
+ RbConfig::CONFIG['target_cpu']
80
+ end
81
+
82
+ def self.native_path(path)
83
+ path = File.expand_path(path)
84
+ if File::ALT_SEPARATOR
85
+ path.tr(File::SEPARATOR, File::ALT_SEPARATOR)
86
+ else
87
+ path
88
+ end
89
+ end
90
+
91
+ def self.posix_path(path)
92
+ path = File.expand_path(path)
93
+ if File::ALT_SEPARATOR
94
+ "/" + path.tr(File::ALT_SEPARATOR, File::SEPARATOR).tr(":", File::SEPARATOR)
95
+ else
96
+ path
97
+ end
98
+ end
99
+
100
+ def initialize(name, version, **kwargs)
37
101
  @name = name
38
102
  @version = version
39
103
  @target = 'ports'
40
104
  @files = []
41
105
  @patch_files = []
42
106
  @log_files = {}
43
- @logger = STDOUT
107
+ @logger = kwargs[:logger] || STDOUT
108
+ @source_directory = nil
109
+
110
+ @cc_command = kwargs[:cc_command] || kwargs[:gcc_command]
111
+ @cxx_command = kwargs[:cxx_command]
112
+ @make_command = kwargs[:make_command]
113
+ @open_timeout = kwargs[:open_timeout] || DEFAULT_TIMEOUT
114
+ @read_timeout = kwargs[:read_timeout] || DEFAULT_TIMEOUT
44
115
 
45
116
  @original_host = @host = detect_host
46
117
  end
47
118
 
119
+ def source_directory=(path)
120
+ @source_directory = posix_path(path)
121
+ end
122
+
123
+ def prepare_build_directory
124
+ raise "source_directory is not set" if source_directory.nil?
125
+ output "Building #{@name} from source at '#{source_directory}'"
126
+ FileUtils.mkdir_p(File.join(tmp_path, [name, version].join("-")))
127
+ FileUtils.rm_rf(port_path) # make sure we always re-install
128
+ end
129
+
48
130
  def download
49
131
  files_hashs.each do |file|
50
132
  download_file(file[:url], file[:local_path])
@@ -54,6 +136,7 @@ class MiniPortile
54
136
 
55
137
  def extract
56
138
  files_hashs.each do |file|
139
+ verify_file(file)
57
140
  extract_file(file[:local_path], tmp_path)
58
141
  end
59
142
  end
@@ -66,9 +149,9 @@ class MiniPortile
66
149
  when which('git')
67
150
  lambda { |file|
68
151
  message "Running git apply with #{file}... "
69
- # By --work-tree=. git-apply uses the current directory as
70
- # the project root and will not search upwards for .git.
71
- execute('patch', ["git", "--work-tree=.", "apply", "--whitespace=warn", file], :initial_message => false)
152
+ Dir.mktmpdir do |tmp_git_dir|
153
+ execute('patch', ["git", "--git-dir=#{tmp_git_dir}", "--work-tree=.", "apply", "--whitespace=warn", file], :initial_message => false)
154
+ end
72
155
  }
73
156
  when which('patch')
74
157
  lambda { |file|
@@ -95,16 +178,16 @@ class MiniPortile
95
178
  def configure
96
179
  return if configured?
97
180
 
98
- md5_file = File.join(tmp_path, 'configure.md5')
99
- digest = Digest::MD5.hexdigest(computed_options.to_s)
100
- File.open(md5_file, "w") { |f| f.write digest }
181
+ FileUtils.mkdir_p(tmp_path)
182
+ cache_file = File.join(tmp_path, 'configure.options_cache')
183
+ File.open(cache_file, "w") { |f| f.write computed_options.to_s }
101
184
 
185
+ command = Array(File.join((source_directory || "."), "configure"))
102
186
  if RUBY_PLATFORM=~/mingw|mswin/
103
187
  # Windows doesn't recognize the shebang.
104
- execute('configure', %w(sh ./configure) + computed_options)
105
- else
106
- execute('configure', %w(./configure) + computed_options)
188
+ command.unshift("sh")
107
189
  end
190
+ execute('configure', command + computed_options, altlog: "config.log")
108
191
  end
109
192
 
110
193
  def compile
@@ -125,14 +208,14 @@ class MiniPortile
125
208
  end
126
209
 
127
210
  def configured?
128
- configure = File.join(work_path, 'configure')
211
+ configure = File.join((source_directory || work_path), 'configure')
129
212
  makefile = File.join(work_path, 'Makefile')
130
- md5_file = File.join(tmp_path, 'configure.md5')
213
+ cache_file = File.join(tmp_path, 'configure.options_cache')
131
214
 
132
- stored_md5 = File.exist?(md5_file) ? File.read(md5_file) : ""
133
- current_md5 = Digest::MD5.hexdigest(computed_options.to_s)
215
+ stored_options = File.exist?(cache_file) ? File.read(cache_file) : ""
216
+ current_options = computed_options.to_s
134
217
 
135
- (current_md5 == stored_md5) && newer?(makefile, configure)
218
+ (current_options == stored_options) && newer?(makefile, configure)
136
219
  end
137
220
 
138
221
  def installed?
@@ -143,9 +226,13 @@ class MiniPortile
143
226
  end
144
227
 
145
228
  def cook
146
- download unless downloaded?
147
- extract
148
- patch
229
+ if source_directory
230
+ prepare_build_directory
231
+ else
232
+ download unless downloaded?
233
+ extract
234
+ patch
235
+ end
149
236
  configure unless configured?
150
237
  compile
151
238
  install unless installed?
@@ -154,19 +241,15 @@ class MiniPortile
154
241
  end
155
242
 
156
243
  def activate
157
- lib_path = File.join(port_path, "lib")
158
244
  vars = {
159
245
  'PATH' => File.join(port_path, 'bin'),
160
- 'CPATH' => File.join(port_path, 'include'),
161
- 'LIBRARY_PATH' => lib_path
246
+ 'CPATH' => include_path,
247
+ 'LIBRARY_PATH' => lib_path,
162
248
  }.reject { |env, path| !File.directory?(path) }
163
249
 
164
250
  output "Activating #{@name} #{@version} (from #{port_path})..."
165
251
  vars.each do |var, path|
166
- full_path = File.expand_path(path)
167
-
168
- # turn into a valid Windows path (if required)
169
- full_path.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
252
+ full_path = native_path(path)
170
253
 
171
254
  # save current variable value
172
255
  old_value = ENV[var] || ''
@@ -178,7 +261,7 @@ class MiniPortile
178
261
 
179
262
  # rely on LDFLAGS when cross-compiling
180
263
  if File.exist?(lib_path) && (@host != @original_host)
181
- full_path = File.expand_path(lib_path)
264
+ full_path = native_path(lib_path)
182
265
 
183
266
  old_value = ENV.fetch("LDFLAGS", "")
184
267
 
@@ -188,11 +271,129 @@ class MiniPortile
188
271
  end
189
272
  end
190
273
 
274
+ # pkg: the pkg-config file name (without the .pc extension)
275
+ # dir: inject the directory path for the pkg-config file (probably only useful for tests)
276
+ # static: the name of the static library archive (without the "lib" prefix or the file extension), or nil for dynamic linking
277
+ #
278
+ # we might be able to be terribly clever and infer the name of the static archive file, but
279
+ # unfortunately projects have so much freedom in what they can report (for name, for libs, etc.)
280
+ # that it feels unreliable to try to do so, so I'm preferring to just have the developer make it
281
+ # explicit.
282
+ def mkmf_config(pkg: nil, dir: nil, static: nil)
283
+ require "mkmf"
284
+
285
+ if pkg
286
+ dir ||= File.join(lib_path, "pkgconfig")
287
+ pcfile = File.join(dir, "#{pkg}.pc")
288
+ unless File.exist?(pcfile)
289
+ raise ArgumentError, "pkg-config file '#{pcfile}' does not exist"
290
+ end
291
+
292
+ output "Configuring MakeMakefile for #{File.basename(pcfile)} (in #{File.dirname(pcfile)})\n"
293
+
294
+ # on macos, pkg-config will not return --cflags without this
295
+ ENV["PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"] = "t"
296
+
297
+ # append to PKG_CONFIG_PATH as we go, so later pkg-config files can depend on earlier ones
298
+ ENV["PKG_CONFIG_PATH"] = [ENV["PKG_CONFIG_PATH"], dir].compact.join(File::PATH_SEPARATOR)
299
+
300
+ incflags = minimal_pkg_config(pcfile, "cflags-only-I")
301
+ cflags = minimal_pkg_config(pcfile, "cflags-only-other")
302
+ if static
303
+ ldflags = minimal_pkg_config(pcfile, "libs-only-L", "static")
304
+ libflags = minimal_pkg_config(pcfile, "libs-only-l", "static")
305
+ else
306
+ ldflags = minimal_pkg_config(pcfile, "libs-only-L")
307
+ libflags = minimal_pkg_config(pcfile, "libs-only-l")
308
+ end
309
+ else
310
+ output "Configuring MakeMakefile for #{@name} #{@version} (from #{path})\n"
311
+
312
+ lib_name = name.sub(/\Alib/, "") # TODO: use delete_prefix when we no longer support ruby 2.4
313
+
314
+ incflags = Dir.exist?(include_path) ? "-I#{include_path}" : ""
315
+ cflags = ""
316
+ ldflags = Dir.exist?(lib_path) ? "-L#{lib_path}" : ""
317
+ libflags = Dir.exist?(lib_path) ? "-l#{lib_name}" : ""
318
+ end
319
+
320
+ if static
321
+ libdir = lib_path
322
+ if pcfile
323
+ pcfile_libdir = minimal_pkg_config(pcfile, "variable=libdir").strip
324
+ libdir = pcfile_libdir unless pcfile_libdir.empty?
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
+
191
363
  def path
192
364
  File.expand_path(port_path)
193
365
  end
194
366
 
195
- 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 cc_cmd
376
+ (ENV["CC"] || @cc_command || RbConfig::CONFIG["CC"] || "gcc").dup
377
+ end
378
+ alias :gcc_cmd :cc_cmd
379
+
380
+ def cxx_cmd
381
+ (ENV["CXX"] || @cxx_command || RbConfig::CONFIG["CXX"] || "g++").dup
382
+ end
383
+
384
+ def make_cmd
385
+ (ENV["MAKE"] || @make_command || ENV["make"] || "make").dup
386
+ end
387
+
388
+ private
389
+
390
+ def native_path(path)
391
+ MiniPortile.native_path(path)
392
+ end
393
+
394
+ def posix_path(path)
395
+ MiniPortile.posix_path(path)
396
+ end
196
397
 
197
398
  def tmp_path
198
399
  "tmp/#{@host}/ports/#{@name}/#{@version}"
@@ -247,16 +448,55 @@ private
247
448
  end
248
449
  end
249
450
 
451
+ KEYRING_NAME = "mini_portile_keyring.gpg"
452
+
250
453
  def verify_file(file)
251
- digest = case
252
- when exp=file[:sha256] then Digest::SHA256
253
- when exp=file[:sha1] then Digest::SHA1
254
- when exp=file[:md5] then Digest::MD5
255
- end
256
- if digest
257
- is = digest.file(file[:local_path]).hexdigest
258
- unless is == exp.downcase
259
- raise "Downloaded file '#{file[:local_path]}' has wrong hash: expected: #{exp} is: #{is}"
454
+ if file.has_key?(:gpg)
455
+ gpg = file[:gpg]
456
+
457
+ signature_url = gpg[:signature_url] || "#{file[:url]}.asc"
458
+ signature_file = file[:local_path] + ".asc"
459
+ # download the signature file
460
+ download_file(signature_url, signature_file)
461
+
462
+ gpg_exe = which('gpg2') || which('gpg') || raise("Neither GPG nor GPG2 is installed")
463
+
464
+ # import the key into our own keyring
465
+ gpg_error = nil
466
+ gpg_status = Open3.popen3(gpg_exe, "--status-fd", "1", "--no-default-keyring", "--keyring", KEYRING_NAME, "--import") do |gpg_in, gpg_out, gpg_err, _thread|
467
+ gpg_in.write gpg[:key]
468
+ gpg_in.close
469
+ gpg_error = gpg_err.read
470
+ gpg_out.read
471
+ end
472
+ key_ids = gpg_status.scan(/\[GNUPG:\] IMPORT_OK \d+ (?<key_id>[0-9a-f]+)/i).map(&:first)
473
+ raise "invalid gpg key provided:\n#{gpg_error}" if key_ids.empty?
474
+
475
+ begin
476
+ # verify the signature against our keyring
477
+ gpg_status, gpg_error, _status = Open3.capture3(gpg_exe, "--status-fd", "1", "--no-default-keyring", "--keyring", KEYRING_NAME, "--verify", signature_file, file[:local_path])
478
+
479
+ raise "signature mismatch:\n#{gpg_error}" unless gpg_status.match(/^\[GNUPG:\] VALIDSIG/)
480
+ ensure
481
+ # remove the key from our keyring
482
+ key_ids.each do |key_id|
483
+ IO.popen([gpg_exe, "--batch", "--yes", "--no-default-keyring", "--keyring", KEYRING_NAME, "--delete-keys", key_id], &:read)
484
+ raise "unable to delete the imported key" unless $?.exitstatus==0
485
+ end
486
+ end
487
+
488
+
489
+ else
490
+ digest = case
491
+ when exp=file[:sha256] then Digest::SHA256
492
+ when exp=file[:sha1] then Digest::SHA1
493
+ when exp=file[:md5] then Digest::MD5
494
+ end
495
+ if digest
496
+ is = digest.file(file[:local_path]).hexdigest
497
+ unless is == exp.downcase
498
+ raise "Downloaded file '#{file[:local_path]}' has wrong hash: expected: #{exp} is: #{is}"
499
+ end
260
500
  end
261
501
  end
262
502
  end
@@ -268,27 +508,6 @@ private
268
508
  }
269
509
  end
270
510
 
271
- def tar_exe
272
- @@tar_exe ||= begin
273
- %w[gtar bsdtar tar basic-bsdtar].find { |c|
274
- which(c)
275
- }
276
- end
277
- end
278
-
279
- def tar_compression_switch(filename)
280
- case File.extname(filename)
281
- when '.gz', '.tgz'
282
- 'z'
283
- when '.bz2', '.tbz2'
284
- 'j'
285
- when '.Z'
286
- 'Z'
287
- else
288
- ''
289
- end
290
- end
291
-
292
511
  # From: http://stackoverflow.com/a/5471032/7672
293
512
  # Thanks, Mislav!
294
513
  #
@@ -315,6 +534,8 @@ private
315
534
  output = `#{gcc_cmd} -v 2>&1`
316
535
  if m = output.match(/^Target\: (.*)$/)
317
536
  @detect_host = m[1]
537
+ else
538
+ @detect_host = nil
318
539
  end
319
540
 
320
541
  @detect_host
@@ -323,32 +544,68 @@ private
323
544
  end
324
545
  end
325
546
 
547
+ TAR_EXECUTABLES = %w[gtar bsdtar tar basic-bsdtar]
548
+ def tar_exe
549
+ @@tar_exe ||= begin
550
+ TAR_EXECUTABLES.find { |c|
551
+ which(c)
552
+ } or raise("tar not found - please make sure that one of the following commands is in the PATH: #{TAR_EXECUTABLES.join(", ")}")
553
+ end
554
+ end
555
+
556
+ def tar_command(file, target)
557
+ case File.extname(file)
558
+ when '.gz', '.tgz'
559
+ [tar_exe, 'xzf', file, '-C', target]
560
+ when '.bz2', '.tbz2'
561
+ [tar_exe, 'xjf', file, '-C', target]
562
+ when '.xz'
563
+ # NOTE: OpenBSD's tar command does not support the -J option
564
+ "xzcat #{file.shellescape} | #{tar_exe.shellescape} xf - -C #{target.shellescape}"
565
+ else
566
+ [tar_exe, 'xf', file, '-C', target]
567
+ end
568
+ end
569
+
326
570
  def extract_file(file, target)
327
571
  filename = File.basename(file)
328
572
  FileUtils.mkdir_p target
329
573
 
330
574
  message "Extracting #{filename} into #{target}... "
331
- execute('extract', [tar_exe, "#{tar_compression_switch(filename)}xf", file, "-C", target], {:cd => Dir.pwd, :initial_message => false})
575
+ execute('extract', tar_command(file, target) , {:cd => Dir.pwd, :initial_message => false})
332
576
  end
333
577
 
334
- def execute(action, command, options={})
335
- log_out = log_file(action)
578
+ # command could be an array of args, or one string containing a command passed to the shell. See
579
+ # Process.spawn for more information.
580
+ def execute(action, command, command_opts={})
581
+ opt_message = command_opts.fetch(:initial_message, true)
582
+ opt_debug = command_opts.fetch(:debug, false)
583
+ opt_cd = command_opts.fetch(:cd) { work_path }
584
+ opt_env = command_opts.fetch(:env) { Hash.new }
585
+ opt_altlog = command_opts.fetch(:altlog, nil)
336
586
 
337
- Dir.chdir (options.fetch(:cd){ work_path }) do
338
- if options.fetch(:initial_message){ true }
339
- message "Running '#{action}' for #{@name} #{@version}... "
340
- end
587
+ log_out = log_file(action)
588
+
589
+ Dir.chdir(opt_cd) do
590
+ output "DEBUG: env is #{opt_env.inspect}" if opt_debug
591
+ output "DEBUG: command is #{command.inspect}" if opt_debug
592
+ message "Running '#{action}' for #{@name} #{@version}... " if opt_message
341
593
 
342
594
  if Process.respond_to?(:spawn) && ! RbConfig.respond_to?(:java)
343
- args = [command].flatten + [{[:out, :err]=>[log_out, "a"]}]
595
+ options = {[:out, :err]=>[log_out, "a"]}
596
+ output "DEBUG: options are #{options.inspect}" if opt_debug
597
+ args = [opt_env, command, options].flatten
344
598
  pid = spawn(*args)
345
599
  Process.wait(pid)
346
600
  else
347
- redirected = if command.kind_of?(Array)
348
- %Q{#{command.map(&:shellescape).join(" ")} > #{log_out.shellescape} 2>&1}
349
- else
350
- %Q{#{command} > #{log_out.shellescape} 2>&1}
351
- end
601
+ env_args = opt_env.map { |k,v| "#{k}=#{v}".shellescape }.join(" ")
602
+ c = if command.kind_of?(Array)
603
+ command.map(&:shellescape).join(" ")
604
+ else
605
+ command
606
+ end
607
+ redirected = %Q{env #{env_args} #{c} > #{log_out.shellescape} 2>&1}
608
+ output "DEBUG: final command is #{redirected.inspect}" if opt_debug
352
609
  system redirected
353
610
  end
354
611
 
@@ -356,12 +613,12 @@ private
356
613
  output "OK"
357
614
  return true
358
615
  else
359
- if File.exist? log_out
360
- output "ERROR, review '#{log_out}' to see what happened. Last lines are:"
361
- output("=" * 72)
362
- log_lines = File.readlines(log_out)
363
- output(log_lines[-[log_lines.length, 20].min .. -1])
364
- output("=" * 72)
616
+ output "ERROR. Please review logs to see what happened:\n"
617
+ [log_out, opt_altlog].compact.each do |log|
618
+ next unless File.exist?(log)
619
+ output("----- contents of '#{log}' -----")
620
+ output(File.read(log))
621
+ output("----- end of file -----")
365
622
  end
366
623
  raise "Failed to complete #{action} task"
367
624
  end
@@ -412,16 +669,21 @@ private
412
669
  def download_file_http(url, full_path, count = 3)
413
670
  filename = File.basename(full_path)
414
671
  with_tempfile(filename, full_path) do |temp_file|
415
- progress = 0
416
672
  total = 0
417
673
  params = {
418
674
  "Accept-Encoding" => 'identity',
419
675
  :content_length_proc => lambda{|length| total = length },
420
676
  :progress_proc => lambda{|bytes|
421
- new_progress = (bytes * 100) / total
422
- message "\rDownloading %s (%3d%%) " % [filename, new_progress]
423
- progress = new_progress
424
- }
677
+ if total
678
+ new_progress = (bytes * 100) / total
679
+ message "\rDownloading %s (%3d%%) " % [filename, new_progress]
680
+ else
681
+ # Content-Length is unavailable because Transfer-Encoding is chunked
682
+ message "\rDownloading %s " % [filename]
683
+ end
684
+ },
685
+ :open_timeout => @open_timeout,
686
+ :read_timeout => @read_timeout,
425
687
  }
426
688
  proxy_uri = URI.parse(url).scheme.downcase == 'https' ?
427
689
  ENV["https_proxy"] :
@@ -434,6 +696,7 @@ private
434
696
  [proxy_uri, proxy_user, proxy_pass]
435
697
  end
436
698
  end
699
+
437
700
  begin
438
701
  OpenURI.open_uri(url, 'rb', params) do |io|
439
702
  temp_file << io.read
@@ -442,8 +705,15 @@ private
442
705
  rescue OpenURI::HTTPRedirect => redirect
443
706
  raise "Too many redirections for the original URL, halting." if count <= 0
444
707
  count = count - 1
445
- return download_file(redirect.url, full_path, count - 1)
708
+ return download_file(redirect.url, full_path, count-1)
446
709
  rescue => e
710
+ count = count - 1
711
+ @logger.puts "#{count} retrie(s) left for #{filename} (#{e.message})"
712
+ if count > 0
713
+ sleep 1
714
+ return download_file_http(url, full_path, count)
715
+ end
716
+
447
717
  output e.message
448
718
  return false
449
719
  end
@@ -456,17 +726,18 @@ private
456
726
  end
457
727
 
458
728
  def download_file_ftp(uri, full_path)
729
+ require "net/ftp"
459
730
  filename = File.basename(uri.path)
460
731
  with_tempfile(filename, full_path) do |temp_file|
461
- progress = 0
462
732
  total = 0
463
733
  params = {
464
734
  :content_length_proc => lambda{|length| total = length },
465
735
  :progress_proc => lambda{|bytes|
466
736
  new_progress = (bytes * 100) / total
467
737
  message "\rDownloading %s (%3d%%) " % [filename, new_progress]
468
- progress = new_progress
469
- }
738
+ },
739
+ :open_timeout => @open_timeout,
740
+ :read_timeout => @read_timeout,
470
741
  }
471
742
  if ENV["ftp_proxy"]
472
743
  _, userinfo, _p_host, _p_port = URI.split(ENV['ftp_proxy'])
@@ -481,6 +752,8 @@ private
481
752
  end
482
753
  output
483
754
  end
755
+ rescue LoadError
756
+ raise LoadError, "Ruby #{RUBY_VERSION} does not provide the net-ftp gem, please add it as a dependency if you need to use FTP"
484
757
  rescue Net::FTPError
485
758
  return false
486
759
  end
@@ -495,13 +768,28 @@ private
495
768
  FileUtils.mv temp_file.path, full_path, :force => true
496
769
  end
497
770
 
498
- def gcc_cmd
499
- cc = ENV["CC"] || RbConfig::CONFIG["CC"] || "gcc"
500
- return cc.dup
501
- end
771
+ #
772
+ # this minimal version of pkg_config is based on ruby 29dc9378 (2023-01-09)
773
+ #
774
+ # specifically with the fix from b90e56e6 to support multiple pkg-config options, and removing
775
+ # code paths that aren't helpful for mini-portile's use case of parsing pc files.
776
+ #
777
+ def minimal_pkg_config(pkg, *pcoptions)
778
+ if pcoptions.empty?
779
+ raise ArgumentError, "no pkg-config options are given"
780
+ end
502
781
 
503
- def make_cmd
504
- m = ENV['MAKE'] || ENV['make'] || 'make'
505
- return m.dup
782
+ if ($PKGCONFIG ||=
783
+ (pkgconfig = MakeMakefile.with_config("pkg-config") {MakeMakefile.config_string("PKG_CONFIG") || "pkg-config"}) &&
784
+ MakeMakefile.find_executable0(pkgconfig) && pkgconfig)
785
+ pkgconfig = $PKGCONFIG
786
+ else
787
+ raise RuntimeError, "pkg-config is not found"
788
+ end
789
+
790
+ pcoptions = Array(pcoptions).map { |o| "--#{o}" }
791
+ response = IO.popen([pkgconfig, *pcoptions, pkg], err:[:child, :out], &:read)
792
+ raise RuntimeError, response unless $?.success?
793
+ response.strip
506
794
  end
507
795
  end