xref: /openbsd/usr.bin/pkg-config/pkg-config (revision 404b540a)
1#!/usr/bin/perl
2# $OpenBSD: pkg-config,v 1.24 2009/06/21 21:30:46 ckuethe Exp $
3
4#$CSK: pkgconfig.pl,v 1.39 2006/11/27 16:26:20 ckuethe Exp $
5# Copyright (c) 2006 Chris Kuethe <ckuethe@openbsd.org>
6#
7# Permission to use, copy, modify, and distribute this software for any
8# purpose with or without fee is hereby granted, provided that the above
9# copyright notice and this permission notice appear in all copies.
10#
11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
19use strict;
20use warnings;
21use Getopt::Long;
22use File::Basename;
23use OpenBSD::PkgConfig;
24
25my @PKGPATH = qw(/usr/local/lib/pkgconfig /usr/X11R6/lib/pkgconfig );
26
27if (defined($ENV{PKG_CONFIG_LIBDIR}) && $ENV{PKG_CONFIG_LIBDIR}) {
28	@PKGPATH = split /:/, $ENV{PKG_CONFIG_LIBDIR};
29} elsif (defined($ENV{PKG_CONFIG_PATH}) && $ENV{PKG_CONFIG_PATH}) {
30	unshift(@PKGPATH, split /:/, $ENV{PKG_CONFIG_PATH});
31}
32
33my $logfile = '';
34if (defined($ENV{PKG_CONFIG_LOGFILE}) && $ENV{PKG_CONFIG_LOGFILE}) {
35	$logfile = $ENV{PKG_CONFIG_LOGFILE};
36}
37
38my $allow_uninstalled =
39	defined $ENV{PKG_CONFIG_DISABLE_UNINSTALLED} ? 0 : 1;
40my $found_uninstalled = 0;
41
42my $version = 0.21; # pretend to be this version of pkgconfig
43
44my %configs = ();
45my %mode = ();
46my $variables = {};
47my $D = 0; # debug flag
48
49{
50    my $d = $ENV{PKG_CONFIG_TOP_BUILD_DIR};
51    if (defined $d) {
52	    $variables->{pc_top_builddir} = $d;
53    } else {
54	    $variables->{pc_top_builddir} = '$(top_builddir)';
55    }
56}
57
58if ($logfile) {
59	open my $L, ">>" . $logfile;
60	print $L '[' . join('] [', $0, @ARGV) . "]\n";
61	close $L;
62}
63
64# combo arg-parsing and dependency resolution loop. Hopefully when the loop
65# terminates, we have a full list of packages upon which we depend, and the
66# right set of compiler and linker flags to use them.
67#
68# as each .pc file is loaded, it is stored in %configs, indexed by package
69# name. this makes it possible to then pull out flags or do substitutions
70# without having to go back and reload the files from disk
71
72Getopt::Long::Configure('no_ignore_case');
73GetOptions(	'debug' => \$D,
74		'help' => \&help, #does not return
75		'usage' => \&help, #does not return
76		'list-all' => \$mode{list},
77		'version' => sub { print "$version\n" ; exit(0);} ,
78		'errors-to-stdout' => sub { $mode{estdout} = 1},
79		'print-errors' => sub { $mode{printerr} = 1},
80		'silence-errors' => sub { $mode{printerr} = 0},
81		'short-errors' => sub { $mode{printerr} = 0},
82		'atleast-pkgconfig-version=s' => \$mode{myminvers},
83
84		'cflags' => sub { $mode{cflags} = 3},
85		'cflags-only-I' => sub { $mode{cflags} |= 1},
86		'cflags-only-other' => sub { $mode{cflags} |= 2},
87		'libs' => sub { $mode{libs} = 7},
88		'libs-only-l' => sub { $mode{libs} |= 1},
89		'libs-only-L' => sub { $mode{libs} |= 2},
90		'libs-only-other' => sub { $mode{libs} |= 4},
91		'exists' => sub { $mode{exists} = 1} ,
92		'static' => sub { $mode{static} = 1},
93		'uninstalled' => sub { $mode{uninstalled} = 1},
94		'atleast-version=s' => \$mode{minversion},
95		'exact-version=s' => \$mode{exactversion},
96		'max-version=s' => \$mode{maxversion},
97		'modversion' => \$mode{modversion},
98		'variable=s' => \$mode{variable},
99		'define-variable=s' => $variables,
100	);
101
102# Initial value of printerr depends on the options...
103if (!defined $mode{printerr}) {
104	if (defined $mode{libs} || defined $mode{cflags}
105	    || defined $mode{version} || defined $mode{list}) {
106		$mode{printerr} = 1;
107	} else {
108		$mode{printerr} = 0;
109	}
110}
111
112print STDERR "\n[" . join('] [', $0, @ARGV) . "]\n" if $D;
113
114my $rc = 0;
115
116# XXX pkg-config is a bit weird
117{
118my $p = join(' ', @ARGV);
119$p =~ s/^\s+//;
120@ARGV = split /\s+/, $p;
121}
122
123if ($mode{myminvers}) {
124	exit self_version($mode{myminvers});
125}
126
127if ($mode{list}) {
128	exit do_list();
129}
130
131my $cfg_full_list = [];
132my $top_config = [];
133
134while (@ARGV){
135	my $p = shift @ARGV;
136	my $op = undef;
137	my $v = undef;
138	if (@ARGV >= 2  && $ARGV[0] =~ /[<=>]+/ &&
139	    $ARGV[1] =~ /[0-9\.]+/) {
140	    	$op = shift @ARGV;
141		$v = shift @ARGV;
142	}
143	$p =~ s/,//g;
144	handle_config($p, $op, $v, $cfg_full_list);
145	push(@$top_config, $p);
146}
147
148if ($mode{exists}) {
149	exit $rc;
150}
151
152if ($mode{uninstalled}) {
153	$rc = 1 unless $found_uninstalled;
154	exit $rc;
155}
156
157if ($mode{modversion}) {
158	for my $pkg (@$top_config) {
159		do_modversion($pkg);
160	}
161}
162
163if ($mode{minversion}) {
164	my $v = $mode{minversion};
165	for my $pkg (@$top_config) {
166		$rc = 1 unless versionmatch($configs{$pkg}, '>=', $v);
167	}
168	exit $rc;
169}
170
171if ($mode{exactversion}) {
172	my $v = $mode{exactversion};
173	for my $pkg (@$top_config) {
174		$rc = 1 unless versionmatch($configs{$pkg}, '=', $v);
175	}
176	exit $rc;
177}
178
179if ($mode{minversion}) {
180	my $v = $mode{maxversion};
181	for my $pkg (@$top_config) {
182		$rc = 1 unless versionmatch($configs{$pkg}, '<=', $v);
183	}
184	exit $rc;
185}
186
187my @vlist = ();
188
189if ($mode{variable}) {
190	for my $pkg (@$top_config) {
191		do_variable($pkg, $mode{variable});
192	}
193}
194
195my $dep_cfg_list = simplify_and_reverse($cfg_full_list);
196
197if ($mode{cflags} || $mode{libs} || $mode{variable}) {
198    push @vlist, do_cflags($dep_cfg_list) if $mode{cflags};
199    push @vlist, do_libs($dep_cfg_list) if $mode{libs};
200    print join(' ', @vlist), "\n" if $rc == 0;
201}
202
203exit $rc;
204
205###########################################################################
206
207sub handle_config
208{
209	my ($p, $op, $v, $list) = @_;
210
211
212	my $cfg = cache_find_config($p);
213
214	unshift @$list, $p if defined $cfg;
215
216	if (!defined $cfg) {
217		$rc = 1;
218		return undef;
219	}
220
221	if (defined $op) {
222		if (!versionmatch($cfg, $op, $v)) {
223			mismatch($p, $cfg, $op, $v) if $mode{printerr};
224			$rc = 1;
225			return undef;
226		}
227	}
228
229	my $deps = $cfg->get_property('Requires', $variables);
230	if (defined $deps) {
231		for my $dep (@$deps) {
232			if ($dep =~ m/^(.*?)\s*([<=>]+)\s*([\d\.]+)$/) {
233				handle_config($1, $2, $3, $list);
234			} else {
235				handle_config($dep, undef, undef, $list);
236			}
237		}
238		print STDERR "package $p requires ",
239		    join(',', @$deps), "\n" if $D;
240	}
241
242	$deps = $cfg->get_property('Requires.private', $variables);
243	if (defined $deps) {
244		for my $dep (@$deps) {
245			if ($dep =~ m/^(.*?)\s*([<=>]+)\s*([\d\.]+)$/) {
246				handle_config($1, $2, $3, $list);
247			} else {
248				handle_config($dep, undef, undef, $list);
249			}
250		}
251		print STDERR "package $p requires (private)",
252			join(',', @$deps), "\n" if $D;
253	}
254}
255
256# look for the .pc file in each of the PKGPATH elements. Return the path or
257# undef if it's not there
258sub pathresolve
259{
260	my ($p) = @_;
261
262	if ($allow_uninstalled && $p !~ m/\-uninstalled$/) {
263		foreach my $d (@PKGPATH) {
264			my $f = "$d/$p-uninstalled.pc";
265			print STDERR "pathresolve($p) looking in $f\n" if $D;
266			if (-f $f) {
267				$found_uninstalled = 1;
268				return $f;
269			}
270		}
271	}
272
273	foreach my $d (@PKGPATH) {
274		my $f = "$d/$p.pc";
275		print STDERR "pathresolve($p) looking in $f\n" if $D;
276		return $f if -f $f;
277	}
278	return undef;
279}
280
281sub get_config
282{
283	my ($f) = @_;
284
285	my $cfg;
286	eval {
287	    $cfg = OpenBSD::PkgConfig->read_file($f);
288	};
289	if (!$@) {
290		return $cfg;
291	} else {
292		print STDERR $@, "\n" if $D;
293	}
294	return undef;
295}
296
297sub cache_find_config
298{
299	my $name = shift;
300
301	print STDERR "processing $name\n" if $D;
302
303	if (exists $configs{$name}) {
304		return $configs{$name};
305	} else {
306	    	return $configs{$name} = find_config($name);
307	}
308}
309
310sub find_config
311{
312	my ($p) = @_;
313	my $f = pathresolve($p);
314	if (defined $f) {
315		return get_config($f);
316	}
317	if ($mode{printerr}) {
318	    print STDERR
319	    	"Package $p was not found in the pkg-config search path\n";
320	}
321	return undef;
322}
323
324sub stringize
325{
326	my $list = shift;
327	my $sep = shift || ',';
328
329	if (defined $list) {
330		return join($sep, @$list)
331	} else {
332		return '';
333	}
334}
335
336#if the variable option is set, pull out the named variable
337sub do_variable
338{
339	my ($p, $v) = @_;
340
341	my $cfg = cache_find_config($p);
342
343	if (defined $cfg) {
344		my $value = $cfg->get_variable($v, $variables);
345		if (defined $value) {
346			push(@vlist, $value);
347		}
348		return undef;
349	}
350	$rc = 1;
351}
352
353#if the modversion option is set, pull out the compiler flags
354sub do_modversion
355{
356	my ($p) = @_;
357
358	my $cfg = cache_find_config($p);
359
360	if (defined $cfg) {
361		my $value = $cfg->get_property('Version', $variables);
362		if (defined $value) {
363			print stringize($value), "\n";
364			return undef;
365		}
366	}
367	$rc = 1;
368}
369
370#if the cflags option is set, pull out the compiler flags
371sub do_cflags
372{
373	my $list = shift;
374
375	my $cflags = [];
376
377	foreach my $pkg (@$list) {
378		my $l = $configs{$pkg}->get_property('Cflags', $variables);
379		push(@$cflags, @$l) if defined $l;
380	}
381	return OpenBSD::PkgConfig->compress($cflags,
382		sub {
383			local $_ = shift;
384			if (($mode{cflags} & 1) && /^-I/ ||
385			    ($mode{cflags} & 2) && !/^-I/) {
386			    return 1;
387			} else {
388			    return 0;
389			}
390		});
391	return undef;
392}
393
394#if the lib option is set, pull out the linker flags
395sub do_libs
396{
397	my $list = shift;
398
399	my $libs = [];
400
401	foreach my $pkg (@$list) {
402		my $l = $configs{$pkg}->get_property('Libs', $variables);
403		push(@$libs, @$l) if defined $l;
404	}
405	my $a = OpenBSD::PkgConfig->compress($libs,
406		sub {
407			local $_ = shift;
408			if (($mode{libs} & 2) && /^-L/ ||
409			    ($mode{libs} & 4) && !/^-[lL]/) {
410			    return 1;
411			} else {
412			    return 0;
413			}
414		});
415	if ($mode{libs} & 1) {
416		my $b = OpenBSD::PkgConfig->rcompress($libs,
417			sub { shift =~ m/^-l/; });
418		return ($a, $b);
419	} else {
420		return $a;
421	}
422}
423
424#list all packages
425sub do_list
426{
427	my ($p, $x, $y, @files, $fname, $name);
428	my $error = 0;
429
430	foreach my $p (@PKGPATH) {
431		push(@files, <$p/*.pc>);
432	}
433
434	# Scan the lengths of the package names so I can make a format
435	# string to line the list up just like the real pkgconfig does.
436	$x = 0;
437	foreach my $f (@files) {
438		$fname = basename($f, '.pc');
439		$y = length $fname;
440		$x = (($y > $x) ? $y : $x);
441	}
442	$x *= -1;
443
444	foreach my $f (@files) {
445		my $cfg = get_config($f);
446		if (!defined $cfg) {
447			print STDERR "Problem reading file $f\n";
448			$error = 1;
449			next;
450		}
451		$fname = basename($f, '.pc');
452		printf("%${x}s %s - %s\n", $fname,
453		    stringize($cfg->get_property('Name', $variables)),
454		    stringize($cfg->get_property('Description', $variables),
455		    ' '));
456	}
457	return $error;
458}
459
460sub help
461{
462	print <<EOF
463Usage: $0 [options]
464--debug	- turn on debugging output
465--help - this message
466--usage - this message
467--list-all - show all packages that $0 can find
468--version - print version of pkgconfig
469--errors-to-stdout - direct error messages to stdout rather than stderr
470--print-errors - print error messages in case of error
471--silence-errors - don't print error messages in case of error
472--atleast-pkgconfig-version [version] - require a certain version of pkgconfig
473--cflags package [versionspec] [package [versionspec]]
474--cflags-only-I - only output -Iincludepath flags
475--cflags-only-other - only output flags that are not -I
476--define-variable=NAME=VALUE - define variables
477--libs package [versionspec] [package [versionspec]]
478--libs-only-l - only output -llib flags
479--libs-only-L - only output -Llibpath flags
480--libs-only-other - only output flags that are not -l or -L
481--exists package [versionspec] [package [versionspec]]
482--uninstalled - allow for uninstalled versions to be used
483--static - adjust output for static linking
484--atleast-version [version] - require a certain version of a package
485--modversion [package] - query the version of a package
486--variable var package - return the definition of <var> in <package>
487EOF
488;
489	exit 0;
490}
491
492# do we meet/beat the version the caller requested?
493sub self_version
494{
495	my ($v) = @_;
496	my (@a, @b);
497
498	@a = split /\./, $v;
499	@b = split /\./, $version;
500
501	if (($b[0] >= $a[0]) && ($b[1] >= $a[1])) {
502		return 0;
503	} else {
504		return 1;
505	}
506}
507
508sub compare
509{
510	my ($a, $b) = @_;
511
512	if ($a eq $b) {
513		return 0;
514	}
515
516	my @a = split /\./, $a;
517	my @b = split /\./, $b;
518
519	while (@a && @b) { #so long as both lists have something
520		return 1 if $a[0] > $b[0];
521		return -1 if $a[0] < $b[0];
522		shift @a; shift @b;
523	}
524	return 1 if @a;
525	return -1 if @b;
526	return 0;
527}
528
529# got a package meeting the requested specific version?
530sub versionmatch
531{
532	my ($cfg, $op, $want) = @_;
533
534	# can't possibly match if we can't find the file
535	return 0 if !defined $cfg;
536
537	my $inst = stringize($cfg->get_property('Version', $variables));
538
539	# can't possibly match if we can't find the version string
540	return 0 if $inst eq '';
541
542	print "comparing $want (wanted) to $inst (installed)\n" if $D;
543	my $value = compare($inst, $want);
544	if ($op eq '>=') {
545		return $value >= 0;
546	}
547	elsif ($op eq '=') {
548		return $value == 0;
549	} elsif ($op eq '!=') {
550		return $value != 0;
551	} elsif ($op eq '<') {
552		return $value < 0;
553	} elsif ($op eq '>') {
554		return $value > 0;
555	} elsif ($op eq '<=') {
556		return $value <= 0;
557	}
558}
559
560sub mismatch
561{
562	my ($p, $cfg, $op, $v) = @_;
563	print STDERR "Requested '$p $op $v' but version of ",
564	    stringize($cfg->get_property('Name')), " is ",
565	    stringize($cfg->get_property('Version')), "\n";
566}
567
568sub simplify_and_reverse
569{
570	my $reqlist = shift;
571	my $dejavu = {};
572	my $result = [];
573
574	for my $item (@$reqlist) {
575		if (!$dejavu->{$item}) {
576			unshift @$result, $item;
577			$dejavu->{$item} = 1;
578		}
579	}
580	return $result;
581}
582