1#!/usr/bin/perl 2 3# mk-build-deps: make a dummy package to satisfy build-deps of a package 4# Copyright 2008 by Vincent Fourmond 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 20# Changes: 21# * (Vincent Fourmond 4/4/2008): now take Build-Depends-Indep 22# into consideration 23 24=head1 NAME 25 26mk-build-deps - build a package satisfying a package's build-dependencies 27 28=head1 SYNOPSIS 29 30B<mk-build-deps> B<--help>|B<--version> 31 32B<mk-build-deps> [I<options>] I<control file> | I<package name> ... 33 34=head1 DESCRIPTION 35 36Given a I<package name> and/or I<control file>, B<mk-build-deps> 37will use B<equivs> to generate a binary package which may be installed to 38satisfy all the build dependencies of the given package. 39 40If B<--build-dep> and/or B<--build-indep> are given, then the resulting binary 41package(s) will depend solely on the Build-Depends/Build-Depends-Indep 42dependencies, respectively. 43 44=head1 OPTIONS 45 46=over 4 47 48=item B<-i>, B<--install> 49 50Install the generated packages and its build-dependencies. 51 52=item B<-t>, B<--tool> 53 54When installing the generated package use the specified tool. 55(default: B<apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends>) 56 57=item B<-r>, B<--remove> 58 59Remove the package file after installing it. Ignored if used without 60the B<--install> switch. 61 62=item B<-a> I<foo>, B<--arch> I<foo> 63 64Set the architecture of the produced binary package to I<foo>. If this option 65is not given, fall back to the value given by B<--host-arch>. If neither this 66option nor B<--host-arch> are given but the Build-Depends contain architecture 67restrictions, use the value printed by `dpkg-architecture -qDEB_HOST_ARCH`. 68Otherwise, use I<all>. 69 70The package architecture must be equal to the host architecture except if the 71package architecture is I<all>. 72 73The package architecture cannot be I<all> if the build and host architecture 74differ. 75 76=item B<--host-arch> I<foo> 77 78Set the host architecture the binary package is built for. This defaults to 79the value printed by `dpkg-architecture -qDEB_HOST_ARCH`. Use this option to 80create a binary package that is able to satisfy crossbuild dependencies. 81 82If this option is used together with B<--arch>, then they must be equal except 83if the value of B<--arch> is I<all>. 84 85If B<--arch> is not given, then this option also sets the package architecture. 86 87=item B<--build-arch> I<foo> 88 89Set the build architecture the binary package is built for. This defaults to 90the value printed by `dpkg-architecture -qDEB_BUILD_ARCH`. Use this option to 91create a binary package that is able to satisfy crossbuild dependencies. 92 93=item B<-B>, B<--build-dep> 94 95Generate a package which only depends on the source package's Build-Depends 96dependencies. 97 98=item B<-A>, B<--build-indep> 99 100Generate a package which only depends on the source package's 101Build-Depends-Indep dependencies. 102 103=item B<-P>, B<--build-profiles> I<profile[,...]> 104 105Generate a package which only depends on build dependencies 106with the build profile(s), given as a comma-separated list. 107The default behavior is to use no specific profile. 108Setting this option will override the B<DEB_BUILD_PROFILES> 109environment variable. 110 111=item B<-h>, B<--help> 112 113Show a summary of options. 114 115=item B<-v>, B<--version> 116 117Show version and copyright information. 118 119=item B<-s>, B<--root-cmd> 120 121Use the specified tool to gain root privileges before installing. 122Ignored if used without the B<--install> switch. 123 124=back 125 126=head1 ENVIRONMENT 127 128=head2 External environment 129 130=over 4 131 132=item B<DEB_BUILD_PROFILES> 133 134If set, it will be used as the active build profile(s) for the 135build dependencies to be installed. 136It is a space separated list of profile names. 137Overridden by the B<-P> option. 138 139=back 140 141=head1 AUTHOR 142 143B<mk-build-deps> is copyright by Vincent Fourmond and was modified for the 144devscripts package by Adam D. Barratt <adam@adam-barratt.org.uk>. 145 146This program comes with ABSOLUTELY NO WARRANTY. 147You are free to redistribute this code under the terms of the GNU 148General Public License, version 2 or later. 149 150=cut 151 152use 5.01; 153use strict; 154use warnings; 155use Getopt::Long qw(:config bundling permute no_getopt_compat); 156use File::Basename; 157use Pod::Usage; 158use Dpkg::Control; 159use Dpkg::Version; 160use Dpkg::IPC; 161use Dpkg::Deps; 162use FileHandle; 163use Text::ParseWords; 164 165my $progname = basename($0); 166my $opt_install; 167my $opt_remove = 0; 168my ($opt_help, $opt_version, $opt_arch, $opt_dep, $opt_indep, $opt_hostarch, 169 $opt_buildarch, $opt_buildprofiles); 170my $control; 171my $install_tool; 172my $root_cmd; 173my @packages; 174 175my @config_files = ('/etc/devscripts.conf', '~/.devscripts'); 176my %config_vars = ( 177 'MKBUILDDEPS_TOOL' => 178'/usr/bin/apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends', 179 'MKBUILDDEPS_REMOVE_AFTER_INSTALL' => 'no', 180 'MKBUILDDEPS_ROOTCMD' => '', 181); 182my %config_default = %config_vars; 183 184my $shell_cmd; 185# Set defaults 186foreach my $var (keys %config_vars) { 187 $shell_cmd .= qq[$var="$config_vars{$var}";\n]; 188} 189$shell_cmd .= 'for file in ' . join(" ", @config_files) . "; do\n"; 190$shell_cmd .= '[ -f $file ] && . $file; done;' . "\n"; 191# Read back values 192foreach my $var (keys %config_vars) { $shell_cmd .= "echo \$$var;\n" } 193my $shell_out = `/bin/bash -c '$shell_cmd'`; 194@config_vars{ keys %config_vars } = split /\n/, $shell_out, -1; 195 196# Check validity 197$config_vars{'MKBUILDDEPS_TOOL'} =~ /./ 198 or $config_vars{'MKBUILDDEPS_TOOL'} = $config_default{'MKBUILDDEPS_TOOL'}; 199$config_vars{'MKBUILDDEPS_REMOVE_AFTER_INSTALL'} =~ /^(yes|no)$/ 200 or $config_vars{'MKBUILDDEPS_REMOVE_AFTER_INSTALL'} 201 = $config_default{'MKBUILDDEPS_REMOVE_AFTER_INSTALL'}; 202$config_vars{'MKBUILDDEPS_ROOTCMD'} =~ /./ 203 or $config_vars{'MKBUILDDEPS_ROOTCMD'} 204 = $config_default{'MKBUILDDEPS_ROOTCMD'}; 205 206$install_tool = $config_vars{'MKBUILDDEPS_TOOL'}; 207 208if ($config_vars{'MKBUILDDEPS_REMOVE_AFTER_INSTALL'} =~ /yes/) { 209 $opt_remove = 1; 210} 211 212sub usage { 213 my ($exitval) = @_; 214 215 my $verbose = $exitval ? 0 : 1; 216 pod2usage({ -exitval => 'NOEXIT', -verbose => $verbose }); 217 218 if ($verbose) { 219 my $modified_conf_msg; 220 foreach my $var (sort keys %config_vars) { 221 if ($config_vars{$var} ne $config_default{$var}) { 222 $modified_conf_msg .= " $var=$config_vars{$var}\n"; 223 } 224 } 225 $modified_conf_msg ||= " (none)\n"; 226 chomp $modified_conf_msg; 227 print <<EOF; 228Default settings modified by devscripts configuration files: 229$modified_conf_msg 230EOF 231 } 232 233 exit $exitval; 234} 235 236GetOptions( 237 "help|h" => \$opt_help, 238 "version|v" => \$opt_version, 239 "install|i" => \$opt_install, 240 "remove|r" => \$opt_remove, 241 "tool|t=s" => \$install_tool, 242 "arch|a=s" => \$opt_arch, 243 "host-arch=s" => \$opt_hostarch, 244 "build-arch=s" => \$opt_buildarch, 245 "build-dep|B" => \$opt_dep, 246 "build-indep|A" => \$opt_indep, 247 "build-profiles|P=s" => \$opt_buildprofiles, 248 "root-cmd|s=s" => \$root_cmd, 249) or usage(1); 250 251usage(0) if ($opt_help); 252 253if ($opt_version) { version(); exit 0; } 254 255if (!@ARGV) { 256 if (-r 'debian/control') { 257 push(@ARGV, 'debian/control'); 258 } 259} 260 261usage(1) unless @ARGV; 262 263system("command -v equivs-build >/dev/null 2>&1"); 264if ($?) { 265 die "$progname: You must have equivs installed to use this program.\n"; 266} 267 268while ($control = shift) { 269 my ($name, $fh, $descr, $pid); 270 if (-r $control and -f $control) { 271 open $fh, $control; 272 unless (defined $fh) { 273 warn "Unable to open $control: $!\n"; 274 next; 275 } 276 $name = 'Source'; 277 $descr = "control file `$control'"; 278 } else { 279 $fh = FileHandle->new(); 280 $pid = spawn( 281 exec => ['apt-cache', 'showsrc', $control], 282 from_file => '/dev/null', 283 to_pipe => $fh 284 ); 285 unless (defined $pid) { 286 warn "Unable to run apt-cache: $!\n"; 287 next; 288 } 289 $name = 'Package'; 290 $descr = "`apt-cache showsrc $control'"; 291 } 292 293 my (@pkgInfo, @versions); 294 until (eof $fh) { 295 my $ctrl = Dpkg::Control->new(allow_pgp => 1, type => CTRL_UNKNOWN); 296 # parse() dies if the file isn't syntactically valid and returns undef 297 # if there simply weren't any fields parsed 298 unless ($ctrl->parse($fh, $descr)) { 299 warn "$progname: Unable to find package name in $descr\n"; 300 next; 301 } 302 unless (exists $ctrl->{$name}) { 303 next; 304 } 305 my $args = ''; 306 my $arch = 'all'; 307 my ($build_deps, $build_dep, $build_dep_arch, $build_indep); 308 my ($build_conflicts, $build_conflict, $conflict_arch, 309 $conflict_indep); 310 311 if (exists $ctrl->{'Build-Depends'}) { 312 $build_dep = $ctrl->{'Build-Depends'}; 313 $build_dep =~ s/\n/ /g; 314 $build_deps = $build_dep; 315 } 316 if (exists $ctrl->{'Build-Depends-Arch'}) { 317 $build_dep_arch = $ctrl->{'Build-Depends-Arch'}; 318 $build_dep_arch =~ s/\n/ /g; 319 $build_dep .= ', ' if $build_dep; 320 $build_dep .= $build_dep_arch; 321 $build_deps .= ', ' if $build_deps; 322 $build_deps .= $build_dep_arch; 323 } 324 if (exists $ctrl->{'Build-Depends-Indep'}) { 325 $build_indep = $ctrl->{'Build-Depends-Indep'}; 326 $build_indep =~ s/\n/ /g; 327 $build_deps .= ', ' if $build_deps; 328 $build_deps .= $build_indep; 329 } 330 if (exists $ctrl->{'Build-Conflicts'}) { 331 $build_conflict = $ctrl->{'Build-Conflicts'}; 332 $build_conflict =~ s/\n/ /g; 333 $build_conflicts = $build_conflict; 334 } 335 if (exists $ctrl->{'Build-Conflicts-Arch'}) { 336 $conflict_arch = $ctrl->{'Build-Conflicts-Arch'}; 337 $conflict_arch =~ s/\n/ /g; 338 $build_conflict .= ', ' if $build_conflict; 339 $build_conflict .= $conflict_arch; 340 $build_conflicts .= ', ' if $build_conflicts; 341 $build_conflicts .= $conflict_arch; 342 } 343 if (exists $ctrl->{'Build-Conflicts-Indep'}) { 344 $conflict_indep = $ctrl->{'Build-Conflicts-Indep'}; 345 $conflict_indep =~ s/\n/ /g; 346 $build_conflicts .= ', ' if $build_conflicts; 347 $build_conflicts .= $conflict_indep; 348 } 349 350 warn "$progname: Unable to find build-deps for $ctrl->{$name}\n" 351 unless $build_deps; 352 353 if (exists $ctrl->{Version}) { 354 push(@versions, $ctrl->{Version}); 355 } elsif ($name eq 'Source') { 356 (my $changelog = $control) =~ s@control$@changelog@; 357 if (-f $changelog) { 358 require Dpkg::Changelog::Parse; 359 my $log = Dpkg::Changelog::Parse::changelog_parse( 360 file => $changelog); 361 if ($ctrl->{$name} eq $log->{$name}) { 362 $ctrl->{Version} = $log->{Version}; 363 push(@versions, $log->{Version}); 364 } 365 } 366 } 367 368 # Only build a package with both B-D and B-D-I in Depends if the 369 # B-D/B-D-I specific packages weren't requested 370 if (!($opt_dep || $opt_indep)) { 371 push( 372 @pkgInfo, 373 { 374 depends => $build_deps, 375 conflicts => $build_conflicts, 376 name => $ctrl->{$name}, 377 type => 'build-deps', 378 version => $ctrl->{Version} }); 379 next; 380 } 381 if ($opt_dep) { 382 push( 383 @pkgInfo, 384 { 385 depends => $build_dep, 386 conflicts => $build_conflict, 387 name => $ctrl->{$name}, 388 type => 'build-deps-depends', 389 version => $ctrl->{Version} }); 390 } 391 if ($opt_indep) { 392 push( 393 @pkgInfo, 394 { 395 depends => $build_indep, 396 conflicts => $conflict_indep, 397 name => $ctrl->{$name}, 398 type => 'build-deps-indep', 399 version => $ctrl->{Version} }); 400 } 401 } 402 wait_child($pid, nocheck => 1) if defined $pid; 403 # Only use the newest version. We'll only have this if processing showsrc 404 # output or a dsc file. 405 if (@versions) { 406 @versions = map { $_->[0] } 407 sort { $b->[1] <=> $a->[1] } 408 map { [$_, Dpkg::Version->new($_)] } @versions; 409 push(@packages, 410 map { build_equiv($_) } 411 grep { $versions[0] eq $_->{version} } @pkgInfo); 412 } elsif (@pkgInfo) { 413 push(@packages, build_equiv($pkgInfo[0])); 414 } else { 415 die "$progname: Unable to find package name in $descr\n"; 416 } 417} 418 419if ($opt_install) { 420 my @root; 421 if ($root_cmd) { 422 push(@root, shellwords($root_cmd)); 423 } 424 425 my (@pkg_names, @deb_files, %uniq); 426 for my $package (@packages) { 427 if ($uniq{ $package->{deb_file} }++ == 0) { 428 push @pkg_names, $package->{package}; 429 push @deb_files, $package->{deb_file}; 430 } 431 } 432 433 system @root, 'dpkg', '--unpack', @deb_files; 434 die("$progname: dpkg --unpack failed\n") if (($? >> 8) != 0); 435 system @root, shellwords($install_tool), '-f', 'install'; 436 my $err = $? >> 8; 437 if (!$err) { 438 # $install_tool succeeded. Did the packages get installed? It's 439 # possible that they didn't because $install_tool may have realized 440 # that installation was impossible, and it could have given up, 441 # successfully. 442 for (my $i = 0 ; $i < @pkg_names ; $i++) { 443 my $pkg = $pkg_names[$i]; 444 my $status; 445 spawn( 446 exec => 447 ['dpkg-query', '-W', '-f', '${db:Status-Status}', $pkg], 448 to_string => \$status, 449 error_to_file => '/dev/null', 450 nocheck => 1, 451 wait_child => 1 452 ); 453 if ($status ne 'installed' || ($? >> 8)) { 454 # Restore system to previous state, since $install_tool wasn't 455 # able to resolve a proper way to get the build-dep packages 456 # installed 457 warn "$progname: Unable to install $pkg"; 458 $err = 1; 459 } elsif ($opt_remove) { 460 unlink $deb_files[$i]; 461 } 462 } 463 if ($err) { 464 die "$progname: Unable to install all build-dep packages\n"; 465 } 466 } else { 467 # Restore system to previous state, since $install_tool wasn't able to 468 # resolve a proper way to get the build-dep packages installed 469 system @root, 'dpkg', '--remove', @pkg_names; 470 die("$progname: Unable to install all build-dep packages\n"); 471 } 472} 473 474sub version { 475 print <<"EOF"; 476This is $progname, from the Debian devscripts package, version ###VERSION### 477Copyright (C) 2008 Vincent Fourmond 478 479This program comes with ABSOLUTELY NO WARRANTY. 480You are free to redistribute this code under the terms of the 481GNU General Public License, version 2, or (at your option) any 482later version. 483EOF 484} 485 486sub build_equiv { 487 my ($opts) = @_; 488 my $args = ''; 489 490 my $packagearch = 'all'; 491 492 if (defined $opt_arch) { 493 $packagearch = $opt_arch; 494 } elsif (defined $opt_hostarch) { 495 $packagearch = $opt_hostarch; 496 } elsif ($opts->{depends} =~ m/\[|\]/) { 497 chomp($packagearch = `dpkg-architecture -qDEB_HOST_ARCH`); 498 } 499 if ($packagearch ne "all") { 500 $args .= "--arch=$packagearch "; 501 } 502 503 chomp(my $buildarch = `dpkg-architecture -qDEB_BUILD_ARCH`); 504 if (defined $opt_buildarch) { 505 $buildarch = $opt_buildarch; 506 } 507 508 chomp(my $hostarch = `dpkg-architecture -qDEB_HOST_ARCH`); 509 if (defined $opt_hostarch) { 510 $hostarch = $opt_hostarch; 511 } 512 513 if ($packagearch eq "all") { 514 if ($buildarch ne $hostarch) { 515 die 516"build architecture \"$buildarch\" is unequal host architecture \"$hostarch\" in which case the package architecture must not be \"all\" (but \"$hostarch\" instead)\n"; 517 } 518 } elsif ($packagearch ne $hostarch) { 519 die 520"The package architecture must be equal to the host architecture except if the package architecture is \"all\"\n"; 521 } 522 523 my $build_profiles = [split /\s+/, ($ENV{'DEB_BUILD_PROFILES'} // "")]; 524 if (defined $opt_buildprofiles) { 525 $build_profiles = [split /,/, $opt_buildprofiles]; 526 } 527 528 my $positive = deps_parse( 529 $opts->{depends} // "", 530 reduce_arch => 1, 531 host_arch => $hostarch, 532 build_arch => $buildarch, 533 build_dep => 1, 534 reduce_profiles => 1, 535 build_profiles => $build_profiles 536 ); 537 my $negative = deps_parse( 538 $opts->{conflicts} // "", 539 reduce_arch => 1, 540 host_arch => $hostarch, 541 build_arch => $buildarch, 542 build_dep => 1, 543 union => 1, 544 reduce_profiles => 1, 545 build_profiles => $build_profiles 546 ); 547 548 # either remove :native for native builds or replace it by the build 549 # architecture 550 my $handle_native_archqual = sub { 551 my ($dep) = @_; 552 if ($dep->{archqual} && $dep->{archqual} eq "native") { 553 if ($hostarch eq $buildarch) { 554 $dep->{archqual} = undef; 555 } else { 556 $dep->{archqual} = $buildarch; 557 } 558 } 559 return 1; 560 }; 561 deps_iterate($positive, $handle_native_archqual); 562 deps_iterate($negative, $handle_native_archqual); 563 564 my $pkgname; 565 my $buildess = "build-essential:$buildarch"; 566 if ($buildarch eq $hostarch) { 567 $pkgname = "$opts->{name}-$opts->{type}"; 568 } else { 569 $pkgname = "$opts->{name}-cross-$opts->{type}"; 570 $buildess .= ", crossbuild-essential-$hostarch:$buildarch"; 571 } 572 573 my $readme = '/usr/share/devscripts/templates/README.mk-build-deps'; 574 open EQUIVS, "| equivs-build $args-" 575 or die "$progname: Failed to execute equivs-build: $!\n"; 576 print EQUIVS "Section: devel\n" 577 . "Priority: optional\n" 578 . "Standards-Version: 3.7.3\n\n" 579 . "Package: $pkgname\n" 580 . "Architecture: $packagearch\n" 581 . "Depends: $buildess, $positive\n"; 582 583 print EQUIVS "Conflicts: $negative\n" if $negative; 584 585 # Allow the file not to exist to ease testing 586 print EQUIVS "Readme: $readme\n" if -r $readme; 587 588 my $version = '1.0'; 589 if (defined $opts->{version}) { 590 $version = $opts->{version}; 591 } 592 print EQUIVS "Version: $version\n"; 593 594 print EQUIVS "Description: build-dependencies for $opts->{name}\n" 595 . " Dependency package to build the '$opts->{name}' package\n"; 596 597 close EQUIVS; 598 599 my $v = Dpkg::Version->new($version); 600 # The version in the .deb filename will not contain the epoch 601 $version = $v->as_string(omit_epoch => 1); 602 my $deb_file = "${pkgname}_${version}_${packagearch}.deb"; 603 return { 604 package => $pkgname, 605 deb_file => $deb_file 606 }; 607} 608