1#!/usr/bin/perl 2# $OpenBSD: pkg-config,v 1.25 2010/09/18 09:27:51 sthen 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.22; # 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