utilrb 3.0.1 → 3.2.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.
@@ -1,5 +1,5 @@
1
- require 'set'
2
- require 'shellwords'
1
+ require "set"
2
+ require "shellwords"
3
3
 
4
4
  module Utilrb
5
5
  # Access to information from pkg-config(1)
@@ -32,7 +32,7 @@ module Utilrb
32
32
  # pkg.static
33
33
  #
34
34
  # Arbitrary variables defined in the .pc file can be accessed with
35
- #
35
+ #
36
36
  # pkg.prefix
37
37
  # pkg.libdir
38
38
  #
@@ -41,15 +41,6 @@ module Utilrb
41
41
  VAR_NAME_RX = /\w+/
42
42
  FIELD_NAME_RX = /[\w\.\-]+/
43
43
 
44
- class << self
45
- attr_reader :loaded_packages
46
-
47
- def clear_cache
48
- loaded_packages.clear
49
- end
50
- end
51
- @loaded_packages = Hash.new
52
-
53
44
  def self.load(path, preset_variables)
54
45
  pkg_name = File.basename(path, ".pc")
55
46
  pkg = Class.instance_method(:new).bind(PkgConfig).call(pkg_name)
@@ -57,28 +48,44 @@ module Utilrb
57
48
  pkg
58
49
  end
59
50
 
51
+ def self.load_minimal(path, preset_variables)
52
+ pkg_name = File.basename(path, ".pc")
53
+ pkg = Class.instance_method(:new).bind(PkgConfig).call(pkg_name)
54
+ pkg.load_minimal(path, preset_variables)
55
+ pkg
56
+ end
57
+
58
+ # @deprecated {PkgConfig} does not cache the packages anymore, so no
59
+ # need to call this method
60
+ def self.clear_cache
61
+ end
62
+
60
63
  # Returns the pkg-config object that matches the given name, and
61
64
  # optionally a version string
62
- def self.get(name, version_spec = nil, preset_variables = Hash.new)
63
- if !(candidates = loaded_packages[name])
64
- paths = find_all_package_files(name)
65
- if paths.empty?
66
- raise NotFound.new(name), "cannot find the pkg-config specification for #{name}"
67
- end
65
+ def self.get(name, version_spec = nil, preset_variables = Hash.new,
66
+ minimal: false, pkg_config_path: self.pkg_config_path, memo: Hash.new)
68
67
 
69
- candidates = Array.new
70
- paths.each do |p|
71
- candidates << PkgConfig.load(p, preset_variables)
72
- end
73
- loaded_packages[name] = candidates
68
+ paths = find_all_package_files(name, pkg_config_path: pkg_config_path)
69
+ if paths.empty?
70
+ raise NotFound.new(name), "cannot find the pkg-config specification for #{name}"
71
+ end
72
+
73
+ candidates = paths.map do |p|
74
+ PkgConfig.load_minimal(p, preset_variables)
74
75
  end
75
76
 
76
77
  # Now try to find a matching spec
77
- if version_match = find_matching_version(candidates, version_spec)
78
- version_match
78
+ if match = find_matching_version(candidates, version_spec)
79
+ memo[[name, version_spec]] = [false, match]
79
80
  else
80
- raise NotFound, "found #{candidates.size} packages for #{name}, but none match the version specification #{version_spec}"
81
+ raise NotFound, "found #{candidates.size} packages for #{name},"\
82
+ " but none match the version specification #{version_spec}"
81
83
  end
84
+
85
+ match.load_fields(memo: memo) unless minimal
86
+
87
+ memo[[name, version_spec]] = [true, match]
88
+ match
82
89
  end
83
90
 
84
91
  # Finds the provided package and optional version and returns its
@@ -89,8 +96,8 @@ module Utilrb
89
96
  # ...
90
97
  # @return [PkgConfig] the pkg-config description
91
98
  # @raise [NotFound] if the package is not found
92
- def self.new(name, version_spec = nil, options = Hash.new)
93
- get(name, version_spec, options)
99
+ def self.new(name, version_spec = nil, **options)
100
+ get(name, version_spec, **options)
94
101
  end
95
102
 
96
103
  # Returns the first package in +candidates+ that match the given version
@@ -124,44 +131,51 @@ module Utilrb
124
131
  end
125
132
 
126
133
  # Exception raised when a request pkg-config file is not found
127
- class NotFound < RuntimeError
134
+ class NotFound < RuntimeError
128
135
  # The name of the pkg-config package
129
- attr_reader :name
136
+ attr_reader :name
130
137
 
131
- def initialize(name); @name = name end
132
- end
138
+ def initialize(name); @name = name end
139
+ end
133
140
 
134
141
  # Exception raised when invalid data is found in a pkg-config file
135
142
  class Invalid < RuntimeError
136
143
  # The name of the pkg-config package
137
- attr_reader :name
144
+ attr_reader :name
138
145
 
139
- def initialize(name); @name = name end
146
+ def initialize(name); @name = name end
140
147
  end
141
148
 
142
-
143
- attr_reader :file
144
149
  attr_reader :path
145
-
146
- # The module name
147
- attr_reader :name
150
+
151
+ # The module name
152
+ attr_reader :name
148
153
  attr_reader :description
154
+
149
155
  # The module version as a string
150
156
  attr_reader :raw_version
157
+
151
158
  # The module version, as an array of integers
152
159
  attr_reader :version
153
160
 
161
+ attr_reader :raw_fields
162
+
154
163
  # Information extracted from the file
155
164
  attr_reader :variables
156
165
  attr_reader :fields
157
166
 
158
- # Create a PkgConfig object for the package +name+
159
- # Raises PkgConfig::NotFound if the module does not exist
160
- def initialize(name)
161
- @name = name
162
- @fields = Hash.new
163
- @variables = Hash.new
164
- end
167
+ # The list of packages that are Require:'d by this package
168
+ #
169
+ # @return [Array<PkgConfig>]
170
+ attr_reader :requires
171
+
172
+ # Create a PkgConfig object for the package +name+
173
+ # Raises PkgConfig::NotFound if the module does not exist
174
+ def initialize(name)
175
+ @name = name
176
+ @fields = Hash.new
177
+ @variables = Hash.new
178
+ end
165
179
 
166
180
  # Helper method that expands ${word} in +value+ using the name to value
167
181
  # map +variables+
@@ -169,7 +183,7 @@ module Utilrb
169
183
  # +current+ is a string that describes what we are expanding. It is used
170
184
  # to detect recursion in expansion of variables, and to give meaningful
171
185
  # errors to the user
172
- def expand_variables(value, variables, current)
186
+ def perform_substitution(value, variables, current)
173
187
  value = value.gsub(/\$\{(\w+)\}/) do |rx|
174
188
  expand_name = $1
175
189
  if expand_name == current
@@ -182,45 +196,59 @@ module Utilrb
182
196
  value
183
197
  end
184
198
 
185
- def self.parse_dependencies(string)
186
- if string =~ /,/
187
- packages = string.split(',')
188
- else
189
- packages = []
190
- words = string.split(' ')
191
- while !words.empty?
192
- w = words.shift
193
- if w =~ /[<>=]/
194
- packages[-1] += " #{w} #{words.shift}"
199
+ class DependencyLoop < RuntimeError; end
200
+
201
+ def self.parse_dependencies(string, allow_loops: false, memo: Hash.new)
202
+ string = string.gsub(',', ' ')
203
+ packages = []
204
+ words = string.split(' ')
205
+ while !words.empty?
206
+ w = words.shift
207
+ if w =~ /[<>=]/
208
+ version = words.shift
209
+ if version =~ /[\d\.]+/
210
+ packages[-1][1] = "#{w} #{version}"
195
211
  else
196
- packages << w
212
+ packages << [version, nil]
197
213
  end
214
+ else
215
+ packages << [w, nil]
198
216
  end
199
217
  end
200
-
201
218
  result = packages.map do |dep|
202
- dep = dep.strip
203
- if dep =~ /^(#{PACKAGE_NAME_RX})\s*([=<>]+.*)/
204
- PkgConfig.get($1, $2.strip)
219
+ finished, pkg = memo[dep]
220
+ if pkg
221
+ if allow_loops || finished
222
+ pkg
223
+ else
224
+ raise DependencyLoop, "found a dependency loop"
225
+ end
205
226
  else
206
- PkgConfig.get(dep)
227
+ PkgConfig.get(*dep, memo: memo)
207
228
  end
208
229
  end
209
- result
230
+ result.compact
210
231
  end
211
232
 
212
233
  SHELL_VARS = %w{Cflags Libs Libs.private}
213
234
 
214
- # Loads the information contained in +path+
215
- def load(path, preset_variables = Hash.new)
216
- @path = path
217
- @file = File.readlines(path).map(&:strip)
218
-
219
- raw_variables = preset_variables.dup
220
- raw_fields = Hash.new
235
+ # @api private
236
+ #
237
+ # Normalize a field name to be lowercase with only the first letter
238
+ # capitalized
239
+ def normalize_field_name(name)
240
+ name = name.downcase
241
+ name[0, 1] = name[0, 1].upcase
242
+ name
243
+ end
221
244
 
245
+ # Parse a pkg-config field and extracts the raw definition of variables
246
+ # and fields
247
+ #
248
+ # @return [(Hash,Hash)] the set of variables and the set of fields
249
+ def parse(path)
222
250
  running_line = nil
223
- @file = file.map do |line|
251
+ file = File.readlines(path).map do |line|
224
252
  line = line.gsub(/\s*#.*$/, '')
225
253
  line = line.strip
226
254
  next if line.empty?
@@ -239,79 +267,137 @@ module Utilrb
239
267
  end
240
268
  end.compact
241
269
 
242
-
270
+ raw_variables, raw_fields = Hash.new, Hash.new
243
271
  file.each do |line|
244
272
  case line
245
273
  when /^(#{VAR_NAME_RX})\s*=(.*)/
246
274
  raw_variables[$1] = $2.strip
247
275
  when /^(#{FIELD_NAME_RX}):\s*(.*)/
248
- raw_fields[$1] = $2.strip
276
+ field_name = normalize_field_name($1)
277
+ raw_fields[field_name] = $2.strip
249
278
  else
250
279
  raise NotImplementedError, "#{path}: cannot parse pkg-config line #{line.inspect}"
251
280
  end
252
281
  end
282
+ return raw_variables, raw_fields
283
+ end
284
+
285
+ def expand_variables(raw_variables)
286
+ raw_variables = raw_variables.dup
253
287
 
288
+ variables = Hash.new
254
289
  # Resolve the variables
255
290
  while variables.size != raw_variables.size
256
291
  raw_variables.each do |name, value|
257
- value = expand_variables(value, raw_variables, name)
292
+ value = perform_substitution(value, raw_variables, name)
258
293
  raw_variables[name] = value
259
294
  if value !~ /\$\{#{VAR_NAME_RX}\}/
260
295
  variables[name] = value
261
296
  end
262
297
  end
263
298
  end
299
+ variables
300
+ end
264
301
 
265
- # Shell-split the fields, and expand the variables in them
266
- raw_fields.each do |name, value|
267
- if SHELL_VARS.include?(name)
268
- value = Shellwords.shellsplit(value)
269
- resolved = Array.new
270
- while !value.empty?
271
- value = value.flat_map do |v|
272
- expanded = expand_variables(v, variables, name)
273
- if expanded == v
274
- resolved << v
275
- nil
276
- else
277
- Shellwords.shellsplit(expanded)
278
- end
279
- end.compact
280
- end
281
- fields[name] = resolved
282
- else
283
- fields[name] = expand_variables(value, variables, name)
302
+ def expand_field(name, field)
303
+ if SHELL_VARS.include?(name)
304
+ value = Shellwords.shellsplit(field)
305
+ resolved = Array.new
306
+ while !value.empty?
307
+ value = value.flat_map do |v|
308
+ expanded = perform_substitution(v, variables, name)
309
+ if expanded == v
310
+ resolved << v
311
+ nil
312
+ else
313
+ Shellwords.shellsplit(expanded)
314
+ end
315
+ end.compact
284
316
  end
317
+ resolved
318
+ else
319
+ perform_substitution(field, variables, name)
320
+ end
321
+ end
322
+
323
+ def builtin_variables(path)
324
+ {
325
+ "pcfiledir" => File.dirname(path),
326
+ "pc_sysrootdir" => sysrootdir
327
+ }
328
+ end
329
+
330
+ def load_variables(path, preset_variables = Hash.new)
331
+ raw_variables, raw_fields = parse(path)
332
+ raw_variables = preset_variables.merge(raw_variables)
333
+ expand_variables(
334
+ raw_variables.merge(builtin_variables(path))
335
+ )
336
+ end
337
+
338
+ def load_minimal(path, preset_variables = Hash.new)
339
+ raw_variables, raw_fields = parse(path)
340
+ raw_variables = preset_variables.merge(raw_variables)
341
+
342
+ @variables = expand_variables(
343
+ raw_variables.merge(builtin_variables(path))
344
+ )
345
+ if raw_fields['Version']
346
+ @raw_version = expand_field('Version', raw_fields['Version'])
347
+ else
348
+ @raw_version = ''
349
+ end
350
+ @version = raw_version.split('.').map { |v| Integer(v) if v =~ /^\d+$/ }.compact
351
+
352
+ # To be used in the call to #load
353
+ @raw_fields = raw_fields
354
+ @path = path
355
+ end
285
356
 
357
+ def load_fields(memo: Hash.new)
358
+ fields = Hash.new
359
+ @raw_fields.each do |name, value|
360
+ fields[name] = expand_field(name, value)
286
361
  end
362
+ @fields = fields
287
363
 
288
364
  # Initialize the main flags
289
- @raw_version = (fields['Version'] || '')
290
- @version = raw_version.split('.').map { |v| Integer(v) if v =~ /^\d+$/ }.compact
291
365
  @description = (fields['Description'] || '')
292
366
 
293
367
  # Get the requires/conflicts
294
- @requires = PkgConfig.parse_dependencies(fields['Requires'] || '')
295
- @requires_private = PkgConfig.parse_dependencies(fields['Requires.private'] || '')
296
- @conflicts = PkgConfig.parse_dependencies(fields['Conflicts'] || '')
368
+ @requires = PkgConfig.parse_dependencies(
369
+ fields['Requires'] || '', allow_loops: false, memo: memo)
370
+ @requires_private = PkgConfig.parse_dependencies(
371
+ fields['Requires.private'] || '', allow_loops: false, memo: memo)
372
+ @conflicts = PkgConfig.parse_dependencies(
373
+ fields['Conflicts'] || '', allow_loops: true, memo: memo)
297
374
 
298
375
  # And finally resolve the compilation flags
299
- @cflags = fields['Cflags'] || []
376
+ cflags = fields['Cflags'] || []
377
+ cflags.uniq!
378
+ cflags -= self.class.default_search_path_I
379
+ cflags = apply_sysrootdir(cflags, "-I")
300
380
  @requires.each do |pkg|
301
- @cflags.concat(pkg.raw_cflags)
381
+ cflags.concat(pkg.raw_cflags)
302
382
  end
303
383
  @requires_private.each do |pkg|
304
- @cflags.concat(pkg.raw_cflags)
384
+ cflags.concat(pkg.raw_cflags)
305
385
  end
306
- @cflags.uniq!
307
- @cflags.delete('-I/usr/include')
308
- @ldflags = Hash.new
309
- @ldflags[false] = fields['Libs'] || []
310
- @ldflags[false].delete('-L/usr/lib')
311
- @ldflags[false].uniq!
312
- @ldflags[true] = @ldflags[false] + (fields['Libs.private'] || [])
313
- @ldflags[true].delete('-L/usr/lib')
314
- @ldflags[true].uniq!
386
+ @cflags = cflags
387
+
388
+ ldflags_public = fields['Libs'] || []
389
+ ldflags_public.uniq!
390
+ ldflags_private = ldflags_public + (fields['Libs.private'] || [])
391
+ ldflags_private.uniq!
392
+
393
+ ldflags_public -= self.class.default_search_path_L
394
+ ldflags_public = apply_sysrootdir(ldflags_public, "-L")
395
+ ldflags_private -= self.class.default_search_path_L
396
+ ldflags_private = apply_sysrootdir(ldflags_private, "-L")
397
+ @ldflags = {
398
+ false => ldflags_public,
399
+ true => ldflags_private
400
+ }
315
401
 
316
402
  @ldflags_with_requires = {
317
403
  true => @ldflags[true].dup,
@@ -326,7 +412,15 @@ module Utilrb
326
412
  end
327
413
  end
328
414
 
329
- def self.define_pkgconfig_action(action) # :nodoc:
415
+ # Loads the information contained in +path+
416
+ def load(path, preset_variables = Hash.new)
417
+ if !@raw_fields
418
+ load_minimal(path, preset_variables)
419
+ end
420
+ load_fields
421
+ end
422
+
423
+ def self.define_pkgconfig_action(action) # :nodoc:
330
424
  class_eval <<-EOD, __FILE__, __LINE__+1
331
425
  def pkgconfig_#{action.gsub(/-/, '_')}(static = false)
332
426
  if static
@@ -336,8 +430,8 @@ module Utilrb
336
430
  end
337
431
  end
338
432
  EOD
339
- nil
340
- end
433
+ nil
434
+ end
341
435
 
342
436
  def pkgconfig_variable(varname)
343
437
  `pkg-config --variable=#{varname}`.strip
@@ -346,43 +440,79 @@ module Utilrb
346
440
  # Returns the list of include directories listed in the Cflags: section
347
441
  # of the pkgconfig file
348
442
  def include_dirs
349
- result = Shellwords.shellsplit(cflags_only_I).map { |v| v[2..-1] }
443
+ result = raw_cflags_only_I.map { |v| v[2..-1] }
350
444
  if result.any?(&:empty?)
351
445
  raise Invalid.new(name), "empty include directory (-I without argument) found in pkg-config package #{name}"
352
446
  end
447
+
353
448
  result
354
449
  end
355
450
 
356
451
  # Returns the list of library directories listed in the Libs: section
357
452
  # of the pkgconfig file
358
453
  def library_dirs
359
- result = Shellwords.shellsplit(libs_only_L).map { |v| v[2..-1] }
454
+ result = raw_libs_only_L.map { |v| v[2..-1] }
360
455
  if result.any?(&:empty?)
361
456
  raise Invalid.new(name), "empty link directory (-L without argument) found in pkg-config package #{name}"
362
457
  end
458
+
363
459
  result
364
460
  end
365
461
 
366
- ACTIONS = %w{cflags cflags-only-I cflags-only-other
462
+ # A "new root" that should be prepended to -L and -I flags
463
+ def sysrootdir
464
+ ENV["PKG_CONFIG_SYSROOT_DIR"] || "/"
465
+ end
466
+
467
+ # @api private
468
+ #
469
+ # Apply {#sysrootdir} to all the given paths flags (-I or -L)
470
+ def apply_sysrootdir(entries, flag_name)
471
+ sysrootdir = self.sysrootdir
472
+ return entries if sysrootdir == "/"
473
+
474
+ entries.map do |v|
475
+ if v.start_with?(flag_name)
476
+ "#{flag_name}#{sysrootdir}#{v[2..-1]}"
477
+ else
478
+ v
479
+ end
480
+ end
481
+ end
482
+
483
+ ACTIONS = %w{cflags cflags-only-I cflags-only-other
367
484
  libs libs-only-L libs-only-l libs-only-other}
368
- ACTIONS.each { |action| define_pkgconfig_action(action) }
485
+ ACTIONS.each { |action| define_pkgconfig_action(action) }
486
+
487
+ def conflicts
488
+ @conflicts
489
+ end
369
490
 
370
491
  def raw_cflags
371
492
  @cflags
372
493
  end
373
494
 
495
+ def raw_cflags_only_I
496
+ @cflags.grep(/^-I/)
497
+ end
498
+
499
+ def raw_cflags_only_other
500
+ @cflags.find_all { |s| s !~ /^-I/ }
501
+ end
502
+
374
503
  def cflags
375
- @cflags.join(" ")
504
+ raw_cflags.join(" ")
376
505
  end
377
506
 
378
507
  def cflags_only_I
379
- @cflags.grep(/^-I/).join(" ")
508
+ raw_cflags_only_I.join(" ")
380
509
  end
381
510
 
382
511
  def cflags_only_other
383
- @cflags.find_all { |s| s !~ /^-I/ }.join(" ")
512
+ raw_cflags_only_other.join(" ")
384
513
  end
385
514
 
515
+
386
516
  def raw_ldflags
387
517
  @ldflags
388
518
  end
@@ -391,41 +521,63 @@ module Utilrb
391
521
  @ldflags_with_requires
392
522
  end
393
523
 
524
+
525
+ def raw_libs(static = false)
526
+ @ldflags_with_requires[static]
527
+ end
528
+
529
+ def raw_libs_only_L(static = false)
530
+ @ldflags_with_requires[static].grep(/^-L/)
531
+ end
532
+
533
+ def raw_libs_only_l(static = false)
534
+ @ldflags_with_requires[static].grep(/^-l/)
535
+ end
536
+
537
+ def raw_libs_only_other(static = false)
538
+ @ldflags_with_requires[static].find_all { |s| s !~ /^-[lL]/ }
539
+ end
540
+
541
+
394
542
  def libs(static = false)
395
- @ldflags_with_requires[static].join(" ")
543
+ raw_libs(static).join(" ")
396
544
  end
397
545
 
398
546
  def libs_only_L(static = false)
399
- @ldflags_with_requires[static].grep(/^-L/).join(" ")
547
+ raw_libs_only_L(static).join(" ")
400
548
  end
401
549
 
402
550
  def libs_only_l(static = false)
403
- @ldflags_with_requires[static].grep(/^-l/).join(" ")
551
+ raw_libs_only_l(static).join(" ")
404
552
  end
405
553
 
406
554
  def libs_only_other(static = false)
407
- @ldflags_with_requires[static].find_all { |s| s !~ /^-[lL]/ }.join(" ")
555
+ raw_libs_only_other(static).join(" ")
408
556
  end
409
557
 
410
- def method_missing(varname, *args, &proc) # :nodoc:
411
- if args.empty?
558
+ def method_missing(varname, *args, &proc) # :nodoc:
559
+ if args.empty?
412
560
  variables[varname.to_s]
413
- else
414
- super(varname, *args, &proc)
415
- end
416
- end
417
-
418
- def self.each_pkgconfig_directory(&block)
419
- if path = ENV['PKG_CONFIG_PATH']
420
- path.split(':').each(&block)
561
+ else
562
+ super(varname, *args, &proc)
421
563
  end
564
+ end
565
+
566
+ def self.pkg_config_path
567
+ ENV['PKG_CONFIG_PATH']
568
+ end
569
+
570
+ def self.each_pkgconfig_directory(pkg_config_path: self.pkg_config_path, &block)
571
+ return enum_for(__method__) if !block_given?
572
+
573
+ pkg_config_path.split(':').each(&block) if pkg_config_path
422
574
  default_search_path.each(&block)
423
575
  end
424
576
 
425
577
  # Returns true if there is a package with this name
426
- def self.find_all_package_files(name)
578
+ def self.find_all_package_files(name, pkg_config_path: self.pkg_config_path)
427
579
  result = []
428
- each_pkgconfig_directory do |dir|
580
+ each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir|
429
581
  path = File.join(dir, "#{name}.pc")
430
582
  if File.exist?(path)
431
583
  result << path
@@ -434,9 +586,9 @@ module Utilrb
434
586
  result
435
587
  end
436
588
 
437
- def self.available_package_names
589
+ def self.available_package_names(pkg_config_path: self.pkg_config_path)
438
590
  result = []
439
- each_pkgconfig_directory do |dir|
591
+ each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir|
440
592
  Dir.glob(File.join(dir, "*.pc")) do |path|
441
593
  result << File.basename(path, ".pc")
442
594
  end
@@ -445,15 +597,17 @@ module Utilrb
445
597
  end
446
598
 
447
599
  # Returns true if there is a package with this name
448
- def self.has_package?(name)
449
- !find_all_package_files(name).empty?
600
+ def self.has_package?(name, pkg_config_path: self.pkg_config_path)
601
+ !find_all_package_files(name, pkg_config_path: pkg_config_path).empty?
450
602
  end
451
603
 
452
604
  # Yields the package names of available packages. If +regex+ is given,
453
605
  # lists only the names that match the regular expression.
454
- def self.each_package(regex = nil)
606
+ def self.each_package(regex = nil, pkg_config_path: self.pkg_config_path)
607
+ return enum_for(__method__) if !block_given?
608
+
455
609
  seen = Set.new
456
- each_pkgconfig_directory do |dir|
610
+ each_pkgconfig_directory(pkg_config_path: pkg_config_path) do |dir|
457
611
  Dir.glob(File.join(dir, '*.pc')) do |file|
458
612
  pkg_name = File.basename(file, ".pc")
459
613
  next if seen.include?(pkg_name)
@@ -466,34 +620,64 @@ module Utilrb
466
620
  end
467
621
 
468
622
 
469
- FOUND_PATH_RX = /Scanning directory (?:#\d+ )?'(.*\/)((?:lib|lib64|share)\/.*)'$/
470
- NONEXISTENT_PATH_RX = /Cannot open directory (?:#\d+ )?'.*\/((?:lib|lib64|share)\/.*)' in package search path:.*/
623
+ PKGCONFIG_PATH_RX = %r{.*/((?:lib|lib64|share)/.*)}.freeze
471
624
 
472
625
  # Returns the system-wide search path that is embedded in pkg-config
473
626
  def self.default_search_path
474
- if !@default_search_path
475
- output = `LANG=C PKG_CONFIG_PATH= pkg-config --debug 2>&1`.split("\n")
476
- @default_search_path = output.grep(FOUND_PATH_RX).
477
- map { |l| l.gsub(FOUND_PATH_RX, '\1\2') }
478
- end
479
- return @default_search_path
627
+ @default_search_path ||=
628
+ `LANG=C pkg-config --variable pc_path pkg-config`
629
+ .strip
630
+ .split(":")
631
+ .grep(PKGCONFIG_PATH_RX)
632
+ .to_set
480
633
  end
634
+
481
635
  @default_search_path = nil
482
636
 
637
+ def self.arch_dir
638
+ return if @arch_dir == false
639
+
640
+ unless @arch_dir
641
+ suffix_with_arch =
642
+ default_search_suffixes
643
+ .find { |p| %r{^lib/[^/]+/pkgconfig} =~ p }
644
+
645
+ @arch_dir =
646
+ if suffix_with_arch
647
+ suffix_with_arch.split("/")[1]
648
+ else
649
+ false
650
+ end
651
+ end
652
+
653
+ @arch_dir
654
+ end
655
+
656
+ def self.default_search_path_L # rubocop:disable Naming/MethodName
657
+ unless @default_search_path_L
658
+ arch_dir = self.arch_dir
659
+ @default_search_path_L = # rubocop:disable Naming/VariableName
660
+ ["-L/usr/lib", "-L/lib"]
661
+ .flat_map { |p| [p, "#{p}/#{arch_dir}"] }
662
+ end
663
+
664
+ @default_search_path_L
665
+ end
666
+
667
+ def self.default_search_path_I # rubocop:disable Naming/MethodName
668
+ @default_search_path_I ||= ["-I/usr/include"] # rubocop:disable Naming/VariableName
669
+ end
670
+
483
671
  # Returns the system-wide standard suffixes that should be appended to
484
672
  # new prefixes to find pkg-config files
485
673
  def self.default_search_suffixes
486
- if !@default_search_suffixes
487
- output = `LANG=C PKG_CONFIG_PATH= pkg-config --debug 2>&1`.split("\n")
488
- found_paths = output.grep(FOUND_PATH_RX).
489
- map { |l| l.gsub(FOUND_PATH_RX, '\2') }.
490
- to_set
491
- not_found = output.grep(NONEXISTENT_PATH_RX).
492
- map { |l| l.gsub(NONEXISTENT_PATH_RX, '\1') }.
493
- to_set
494
- @default_search_suffixes = found_paths | not_found
495
- end
496
- return @default_search_suffixes
674
+ @default_search_suffixes ||=
675
+ `LANG=C pkg-config --variable pc_path pkg-config`
676
+ .strip
677
+ .split(":")
678
+ .grep(PKGCONFIG_PATH_RX)
679
+ .map { |l| l.gsub(PKGCONFIG_PATH_RX, '\1') }
680
+ .to_set
497
681
  end
498
682
  end
499
683
  end