[BACK]Return to pkg-config CVS log [TXT][DIR] Up to [local] / src / usr.bin / pkg-config

File: [local] / src / usr.bin / pkg-config / pkg-config (download)

Revision 1.13, Mon Dec 4 22:05:41 2006 UTC (17 years, 6 months ago) by espie
Branch: MAIN
Changes since 1.12: +119 -62 lines

do the proper dance to order libary dependencies, let modversion do
what it should, start at proper version checking.

Also displays more diagnostic messages like the real pkg-config does.

okay ckuethe@, matthieu@, fries

(and a small extra addition of a mismatch diagnostic).

Make sure PkgConfig.pm is synch'ed! won't work otherwise.

#!/usr/bin/perl
# $OpenBSD: pkg-config,v 1.13 2006/12/04 22:05:41 espie Exp $

#$CSK: pkgconfig.pl,v 1.39 2006/11/27 16:26:20 ckuethe Exp $
# Copyright (c) 2006 Chris Kuethe <ckuethe@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use strict;
use warnings;
use Getopt::Long;
use File::Basename;
use OpenBSD::PkgConfig;

my @PKGPATH = qw(/usr/local/lib/pkgconfig /usr/X11R6/lib/pkgconfig );

if (defined($ENV{'PKG_CONFIG_PATH'}) && $ENV{'PKG_CONFIG_PATH'}) {
	push(@PKGPATH, split /:/, $ENV{'PKG_CONFIG_PATH'});
}

my $logfile = '';
if (defined($ENV{'PKG_CONFIG_LOGFILE'}) && $ENV{'PKG_CONFIG_LOGFILE'}) {
	$logfile = $ENV{'PKG_CONFIG_LOGFILE'};
}

my $version = 0.19; # pretend to be this version of pkgconfig

my %configs = ();
my %mode = ();
my $variables = {};
my $D = 0; # debug flag

# defaults
$mode{printerr} = 1;

if ($logfile) {
	open my $L, ">>" . $logfile;
	print $L '[' . join('] [', $0, @ARGV) . "]\n";
	close $L;
}

# combo arg-parsing and dependency resolution loop. Hopefully when the loop
# terminates, we have a full list of packages upon which we depend, and the
# right set of compiler and linker flags to use them.
#
# as each .pc file is loaded, it is stored in %configs, indexed by package
# name. this makes it possible to then pull out flags or do substitutions
# without having to go back and reload the files from disk

Getopt::Long::Configure('no_ignore_case');
GetOptions(	'debug' => \$D,
		'help' => \&help, #does not return
		'usage' => \&help, #does not return
		'list-all' => \&do_list, #does not return
		'version' => sub { print "$version\n" ; exit(0);} ,
		'errors-to-stdout' => sub { $mode{estdout} = 1},
		'print-errors' => sub { $mode{printerr} = 1},
		'silence-errors' => sub { $mode{printerr} = 0},
		'atleast-pkgconfig-version=s' => \$mode{minvers},

		'cflags' => sub { $mode{cflags} = 3},
		'cflags-only-I' => sub { $mode{cflags} |= 1},
		'cflags-only-other' => sub { $mode{cflags} |= 2},
		'libs' => sub { $mode{libs} = 7},
		'libs-only-l' => sub { $mode{libs} |= 1},
		'libs-only-L' => sub { $mode{libs} |= 2},
		'libs-only-other' => sub { $mode{libs} |= 4},
		'exists' => sub { $mode{exists} = 1} ,
		'static' => sub { $mode{static} = 1},
		'uninstalled' => sub { $mode{uninstalled} = 1},
		'atleast-version=s' => \$mode{'atleast-version'},
		'modversion' => \$mode{modversion},
		'variable=s' => \$mode{variable},
		'define-variable=s' => $variables,
	);

print STDERR "\n[" . join('] [', $0, @ARGV) . "]\n" if $D;
self_version($mode{minvers}) if $mode{minvers}; #does not return

my $rc = 0;

{
my $p = join(' ', @ARGV);
$p =~ s/\s+/ /g;
$p =~ s/^\s//g;
@ARGV = split /\s+/, $p;
}

if (defined $mode{exists}) {
	while (@ARGV) {
		my $p = shift @ARGV;
		my $cfg = cache_find_config($p);
		exit 1 if !defined $cfg;
		if (@ARGV >= 2  && $ARGV[0] =~ /[<=>]+/ &&
		    $ARGV[1] =~ /[0-9\.]+/) {
			$rc = 1 unless versionmatch($cfg, @ARGV);
			shift @ARGV; shift @ARGV;
		}
	}
	exit $rc;
}

my $cfg_full_list = [];
my @vlist = ();

while (@ARGV){
	my $p = shift @ARGV;
	my $op = undef;
	my $v = undef;
	if (@ARGV >= 2  && $ARGV[0] =~ /[<=>]+/ &&
	    $ARGV[1] =~ /[0-9\.]+/) {
	    	$op = shift @ARGV;
		$v = shift @ARGV;
	}
	$p =~ s/,//g;
	handle_config($p, $op, $v, $cfg_full_list);
	do_modversion($p) if defined $mode{modversion};
	do_variable($p, $mode{variable}) if $mode{variable};
}

my $cfg_list = simplify_and_reverse($cfg_full_list);

if ($mode{cflags} || $mode{libs}|| $mode{variable}) {
    push @vlist, do_cflags() if $mode{cflags};
    push @vlist, do_libs() if $mode{libs};
    print join(' ', @vlist), "\n";
}

exit $rc;

###########################################################################

sub handle_config
{
	my ($p, $op, $v, $list) = @_;


	my $cfg = cache_find_config($p);

	unshift @$list, $p if defined $cfg;

	if (defined $op) {
		if (!versionmatch($cfg, $op, $v)) {
			mismatch($p, $cfg, $op, $v) if $mode{printerr};
			$rc = 1;
			return undef;
		}
	}

	return undef if !defined $cfg;

	my $deps = $cfg->get_property('Requires', $variables);
	if (defined $deps) {
		for my $dep (@$deps) {
			if ($dep =~ m/^(.*?)\s*([<=>]+)\s*([\d\.]+)$/) {
				handle_config($1, $2, $3, $list);
			} else {
				handle_config($dep, undef, undef, $list);
			}
		}
		print STDERR "package $p requires ",
		    join(',', @$deps), "\n" if $D;
	}

	$deps = $cfg->get_property('Requires.private', $variables);
	if (defined $deps) {
		for my $dep (@$deps) {
			if ($dep =~ m/^(.*?)\s*([<=>]+)\s*([\d\.]+)$/) {
				handle_config($1, $2, $3, $list);
			} else {
				handle_config($dep, undef, undef, $list);
			}
		}
		print STDERR "package $p requires (private)",
			join(',', @$deps), "\n" if $D;
	}
}

# look for the .pc file in each of the PKGPATH elements. Return the path or
# undef if it's not there
sub pathresolve
{
	my ($p) = @_;

	foreach my $d (@PKGPATH) {
		my $f = "$d/$p.pc";
		print STDERR "pathresolve($p) looking in $f\n" if $D;
		return $f if -f $f;
	}
	return undef;
}

sub get_config
{
	my ($f) = @_;

	my $cfg;
	eval { 
	    $cfg = OpenBSD::PkgConfig->read_file($f);
	};
	if (!$@) {
		return $cfg;
	} else {
		print STDERR $@, "\n" if $D;
	}
	return undef;
}

sub cache_find_config
{
	my $name = shift;

	print STDERR "processing $name\n" if $D;

	if (exists $configs{$name}) {
		return $configs{$name};
	} else {
	    	return $configs{$name} = find_config($name);
	}
}

sub find_config
{
	my ($p) = @_;
	my $f = pathresolve($p);
	if (defined $f) {
		return get_config($f);
	}
	if ($mode{printerr}) {
	    print STDERR 
	    	"Package $p was not found in the pkg-config search path\n";
	}
	return undef;
}

sub stringize
{
	my $list = shift;

	if (defined $list) {
		return join(',', @$list)
	} else {
		return '';
	}
}

#if the variable option is set, pull out the named variable
sub do_variable
{
	my ($p, $v) = @_;

	my $cfg = cache_find_config($p);

	if (defined $cfg) {
		my $value = $cfg->get_variable($v, $variables);
		if (defined $value) {
			push(@vlist, $value);
			return undef;
		}
	}
	$rc = 1;
}

#if the modversion option is set, pull out the compiler flags
sub do_modversion
{
	my ($p) = @_;

	my $cfg = cache_find_config($p);

	if (defined $cfg) {
		my $value = $cfg->get_property('Version', $variables);
		if (defined $value) {
			print stringize($value), "\n";
			return undef;
		}
	}
	$rc = 1;
}

#if the cflags option is set, pull out the compiler flags
sub do_cflags
{
	my $cflags = [];

	foreach my $pkg (@$cfg_list) {
		my $l = $configs{$pkg}->get_property('Cflags', $variables);
		push(@$cflags, @$l) if defined $l;
	}
	return OpenBSD::PkgConfig->compress($cflags,
		sub {
			local $_ = shift;
			if (($mode{cflags} & 1) && /^-I/ ||
			    ($mode{cflags} & 2) && !/^-I/) {
			    return 1;
			} else {
			    return 0;
			}
		});
}

#if the lib option is set, pull out the linker flags
sub do_libs
{
	my $libs = [];

	foreach my $pkg (@$cfg_list) {
		my $l = $configs{$pkg}->get_property('Libs', $variables);
		push(@$libs, @$l) if defined $l;
	}
	my $a = OpenBSD::PkgConfig->compress($libs,
		sub {
			local $_ = shift;
			if (($mode{libs} & 2) && /^-L/ ||
			    ($mode{libs} & 4) && !/^-[lL]/) {
			    return 1;
			} else {
			    return 0;
			}
		});
	if ($mode{libs} & 1) {
		my $b = OpenBSD::PkgConfig->rcompress($libs,
			sub { shift =~ m/^-l/; });
		return ($a, $b);
	} else {
		return $a;
	}
}

#list all packages
sub do_list
{
	my ($p, $x, $y, @files, $fname, $name);
	foreach my $p (@PKGPATH) { 
		push(@files, <$p/*.pc>); 
	}

	# Scan the lengths of the package names so I can make a format
	# string to line the list up just like the real pkgconfig does.
	$x = 0;
	foreach my $f (@files) {
		$fname = basename($f, '.pc');
		$y = length $fname;
		$x = (($y > $x) ? $y : $x);
	}
	$x *= -1;

	foreach my $f (@files) {
		my $cfg = get_config($f);
		$fname = basename($f, '.pc');
		printf("%${x}s %s - %s\n", $fname, 
		    stringize($cfg->get_property('Name', $variables)), 
		    stringize($cfg->get_property('Description', $variables)));
	}
	exit 0;
}

sub help
{
	print <<EOF
Usage: $0 [options]
--debug	- turn on debugging output
--help - this message
--usage - this message
--list-all - show all packages that $0 can find
--version - print version of pkgconfig
--errors-to-stdout - direct error messages to stdout rather than stderr
--print-errors - print error messages in case of error
--silence-errors - don't print error messages in case of error
--atleast-pkgconfig-version [version] - require a certain version of pkgconfig
--cflags package [versionspec] [package [versionspec]]
--cflags-only-I - only output -Iincludepath flags
--cflags-only-other - only output flags that are not -I
--define-variable=NAME=VALUE - define variables
--libs package [versionspec] [package [versionspec]]
--libs-only-l - only output -llib flags
--libs-only-L - only output -Llibpath flags
--libs-only-other - only output flags that are not -l or -L
--exists package [versionspec] [package [versionspec]]
--uninstalled - allow for uninstalled versions to be used
--static - adjust output for static linking
--atleast-version [version] - require a certain version of a package
--modversion [package] - query the version of a package
--variable var package - return the definition of <var> in <package>
EOF
;
	exit 1;
}

# do we meet/beat the version the caller requested?
sub self_version
{
	my ($v) = @_;
	my (@a, @b);

	@a = split /\./, $v;
	@b = split /\./, $version;

	if (($b[0] >= $a[0]) && ($b[1] >= $a[1])) {
		exit 0;
	} else {
		exit 1;
	}
}

# got a package meeting the requested specific version?
sub versionmatch
{
	my ($cfg, $op, $ver) = @_;
	
	# XXX assumes op is >= for now.

	# can't possibly match if we can't find the file
	return 0 if !defined $cfg;

	my $v = stringize($cfg->get_property('Version', $variables));

	# can't possibly match if we can't find the version string
	return 0 if $v eq '';

	print "comparing $ver (wanted) to $v (installed)\n" if $D;
	my @inst = split /\./, $v;
	my @want = split /\./, $ver;

	while (@inst && @want) { #so long as both lists have something
		# bail if the requested version element beats existing
		return 1 if $inst[0] > $want[0];
		return 0 if $inst[0] < $want[0];
		shift @inst; shift @want;
	}
	# the version at least equals the requested. if the requested
	# version has some micropatchlevel beyond the existing version,
	# return failure
	return 0 if @want;
	# and after all that, the version is good enough
	return 1;
}

sub mismatch
{
	my ($p, $cfg, $op, $v) = @_;
	print STDERR "Requested '$p $op $v' but version of ",
	    stringize($cfg->get_property('Name')), " is ",
	    stringize($cfg->get_property('Version')), "\n";
}

sub simplify_and_reverse
{
	my $reqlist = shift;
	my $dejavu = {};
	my $result = [];

	for my $item (@$reqlist) {
		if (!$dejavu->{$item}) {
			unshift @$result, $item;
			$dejavu->{$item} = 1;
		}
	}
	return $result;
}