blob: 50e617046a821a151933c631519397c9659d51a2 [file] [log] [blame]
#!/usr/bin/perl
#
# dpkg-buildpackage
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
use strict;
use warnings;
use Cwd;
use File::Basename;
use POSIX;
use Dpkg;
use Dpkg::Gettext;
use Dpkg::ErrorHandling;
use Dpkg::BuildOptions;
use Dpkg::Compression;
use Dpkg::Version;
use Dpkg::Changelog::Parse;
use Dpkg::Path qw(find_command);
textdomain("dpkg-dev");
sub showversion {
printf _g("Debian %s version %s.\n"), $progname, $version;
print _g("
Copyright (C) 1996 Ian Jackson.
Copyright (C) 2000 Wichert Akkerman
Copyright (C) 2007 Frank Lichtenheld");
print _g("
This is free software; see the GNU General Public License version 2 or
later for copying conditions. There is NO warranty.
");
}
sub usage {
printf _g("
Usage: %s [<options> ...]
Options:
-r<gain-root-command>
command to gain root privileges (default is fakeroot).
-R<rules> rules file to execute (default is debian/rules).
-p<sign-command>
-d do not check build dependencies and conflicts.
-D check build dependencies and conflicts.
-T<target> call debian/rules <target> with the proper environment
--as-root ensure -T calls the target with root rights
-j[<number>] specify jobs to run simultaneously } passed to debian/rules
-k<keyid> the key to use for signing.
-sgpg the sign-command is called like GPG.
-spgp the sign-command is called like PGP.
-us unsigned source.
-uc unsigned changes.
-a<arch> Debian architecture we build for (implies -d).
-b binary-only, do not build source. } also passed to
-B binary-only, no arch-indep files. } dpkg-genchanges
-A binary-only, only arch-indep files. }
-S source only, no binary files. }
-F normal full build (binaries and sources).
-t<system> set GNU system type. } passed to dpkg-architecture
-v<version> changes since version <version>. }
-m<maint> maintainer for package is <maint>. }
-e<maint> maintainer for release is <maint>. } only passed
-C<descfile> changes are described in <descfile>. } to dpkg-genchanges
-si (default) src includes orig if new upstream. }
-sa uploaded src always includes orig. }
-sd uploaded src is diff and .dsc only. }
-sn force Debian native source format. }
-s[sAkurKUR] see dpkg-source for explanation. } only passed
-z<level> compression level of source } to dpkg-source
-Z<compressor> compression to use for source }
-nc do not clean source tree (implies -b).
-tc clean source tree when finished.
-ap add pause before starting signature process.
-i[<regex>] ignore diffs of files matching regex. } only passed
-I[<pattern>] filter out files when building tarballs. } to dpkg-source
--source-option=<opt>
pass option <opt> to dpkg-source
--changes-option=<opt>
pass option <opt> to dpkg-genchanges
--admindir=<directory>
change the administrative directory.
-h, --help show this help message.
--version show the version.
"), $progname;
}
my @debian_rules = ("debian/rules");
my @rootcommand = ();
my $signcommand = '';
if ( ( ($ENV{GNUPGHOME} && -e $ENV{GNUPGHOME})
|| ($ENV{HOME} && -e "$ENV{HOME}/.gnupg") )
&& find_command('gpg')) {
$signcommand = 'gpg';
} elsif (find_command('pgp')) {
$signcommand = 'pgp'
}
my ($admindir, $signkey, $forcesigninterface, $usepause, $noclean,
$cleansource, $since, $maint,
$changedby, $desc, $parallel);
my $checkbuilddep = 1;
my $signsource = 1;
my $signchanges = 1;
my $binarytarget = 'binary';
my $targetarch = my $targetgnusystem = '';
my $call_target = '';
my $call_target_as_root = 0;
my (@checkbuilddep_opts, @changes_opts, @source_opts);
use constant BUILD_DEFAULT => 1;
use constant BUILD_SOURCE => 2;
use constant BUILD_ARCH_DEP => 4;
use constant BUILD_ARCH_INDEP => 8;
use constant BUILD_BINARY => BUILD_ARCH_DEP | BUILD_ARCH_INDEP;
use constant BUILD_ALL => BUILD_BINARY | BUILD_SOURCE;
my $include = BUILD_ALL | BUILD_DEFAULT;
sub build_normal() { return ($include & BUILD_ALL) == BUILD_ALL; }
sub build_sourceonly() { return $include == BUILD_SOURCE; }
sub build_binaryonly() { return !($include & BUILD_SOURCE); }
sub build_opt() {
return (($include == BUILD_BINARY) ? '-b' :
(($include == BUILD_ARCH_DEP) ? '-B' :
(($include == BUILD_ARCH_INDEP) ? '-A' :
(($include == BUILD_SOURCE) ? '-S' :
internerr("build_opt called with include=$include")))));
}
while (@ARGV) {
$_ = shift @ARGV;
if (/^(--help|-h)$/) {
usage;
exit 0;
} elsif (/^--version$/) {
showversion;
exit 0;
} elsif (/^--admindir$/) {
$admindir = shift @ARGV;
} elsif (/^--admindir=(.*)$/) {
$admindir = $1;
} elsif (/^--source-option=(.*)$/) {
push @source_opts, $1;
} elsif (/^--changes-option=(.*)$/) {
push @changes_opts, $1;
} elsif (/^-j(\d*)$/) {
$parallel = $1 || '';
} elsif (/^-r(.*)$/) {
@rootcommand = split /\s+/, $1;
} elsif (/^-p(.*)$/) {
$signcommand = $1;
} elsif (/^-k(.*)$/) {
$signkey = $1;
} elsif (/^-([dD])$/) {
$checkbuilddep = ($1 eq 'D');
} elsif (/^-s(gpg|pgp)$/) {
$forcesigninterface = $1;
} elsif (/^-us$/) {
$signsource = 0;
} elsif (/^-uc$/) {
$signchanges = 0;
} elsif (/^-ap$/) {
$usepause = 1;
} elsif (/^-a(.*)$/) {
$targetarch = $1;
$checkbuilddep = 0;
} elsif (/^-s[iad]$/) {
push @changes_opts, $_;
} elsif (/^-(?:s[insAkurKUR]|[zZ].*|i.*|I.*)$/) {
push @source_opts, $_; # passed to dpkg-source
} elsif (/^-tc$/) {
$cleansource = 1;
} elsif (/^-t(.*)$/) {
$targetgnusystem = $1; # Order DOES matter!
} elsif (/^(--target|-T)$/) {
$call_target = shift @ARGV;
} elsif (/^(--target=|-T)(.+)$/) {
$call_target = $2;
} elsif (/^--as-root$/) {
$call_target_as_root = 1;
} elsif (/^-nc$/) {
$noclean = 1;
} elsif (/^-b$/) {
build_sourceonly && usageerr(_g("cannot combine %s and %s"), $_, "-S");
$include = BUILD_BINARY;
push @changes_opts, '-b';
@checkbuilddep_opts = ();
$binarytarget = 'binary';
} elsif (/^-B$/) {
build_sourceonly && usageerr(_g("cannot combine %s and %s"), $_, "-S");
$include = BUILD_ARCH_DEP;
push @changes_opts, '-B';
@checkbuilddep_opts = ('-B');
$binarytarget = 'binary-arch';
} elsif (/^-A$/) {
build_sourceonly && usageerr(_g("cannot combine %s and %s"), $_, "-S");
$include = BUILD_ARCH_INDEP;
push @changes_opts, '-A';
@checkbuilddep_opts = ();
$binarytarget = 'binary-indep';
} elsif (/^-S$/) {
build_binaryonly && usageerr(_g("cannot combine %s and %s"), build_opt, "-S");
$include = BUILD_SOURCE;
push @changes_opts, '-S';
@checkbuilddep_opts = ('-B');
} elsif (/^-F$/) {
!build_normal && usageerr(_g("cannot combine %s and %s"), $_, build_opt);
$include = BUILD_ALL;
@checkbuilddep_opts = ();
} elsif (/^-v(.*)$/) {
$since = $1;
} elsif (/^-m(.*)$/) {
$maint = $1;
} elsif (/^-e(.*)$/) {
$changedby = $1;
} elsif (/^-C(.*)$/) {
$desc = $1;
} elsif (m/^-[EW]$/) {
# Deprecated option
warning(_g("-E and -W are deprecated, they are without effect"));
} elsif (/^-R(.*)$/) {
@debian_rules = split /\s+/, $1;
} else {
usageerr(_g("unknown option or argument %s"), $_);
}
}
if ($noclean) {
# -nc without -b/-B/-A/-S/-F implies -b
$include = BUILD_BINARY if ($include & BUILD_DEFAULT);
}
if ($< == 0) {
warning(_g("using a gain-root-command while being root")) if (@rootcommand);
} else {
push @rootcommand, "fakeroot" unless @rootcommand;
if (!find_command($rootcommand[0])) {
if ($rootcommand[0] eq 'fakeroot') {
error(_g("fakeroot not found, either install the fakeroot\n" .
"package, specify a command with the -r option, " .
"or run this as root"));
} else {
error(_g("gain-root-commmand '%s' not found"), $rootcommand[0]);
}
}
}
unless ($signcommand) {
$signsource = 0;
$signchanges = 0;
}
my $signinterface;
if ($forcesigninterface) {
$signinterface = $forcesigninterface;
} else {
$signinterface = $signcommand;
}
if ($signcommand) {
if ($signinterface !~ /^(gpg|pgp)$/) {
warning(_g("unknown sign command, assuming pgp style interface"));
} elsif ($signinterface eq 'pgp') {
if ($signsource or $signchanges) {
warning(_g("PGP support is deprecated (see README.feature-removal-schedule)"));
}
}
}
my $build_opts = Dpkg::BuildOptions->new();
if (defined $parallel) {
$parallel = $build_opts->get("parallel") if $build_opts->has("parallel");
$ENV{MAKEFLAGS} ||= '';
$ENV{MAKEFLAGS} .= " -j$parallel";
$build_opts->set("parallel", $parallel);
$build_opts->export();
}
my $cwd = cwd();
my $dir = basename($cwd);
my $changelog = changelog_parse();
my $pkg = mustsetvar($changelog->{source}, _g('source package'));
my $version = mustsetvar($changelog->{version}, _g('source version'));
my ($ok, $error) = version_check($version);
error($error) unless $ok;
my $maintainer;
if ($changedby) {
$maintainer = $changedby;
} elsif ($maint) {
$maintainer = $maint;
} else {
$maintainer = mustsetvar($changelog->{maintainer}, _g('source changed by'));
}
open my $arch_env, '-|', 'dpkg-architecture', "-a$targetarch",
"-t$targetgnusystem", '-s', '-f' or subprocerr('dpkg-architecture');
while ($_ = <$arch_env>) {
chomp;
my @cmds = split /\s*;\s*/;
foreach (@cmds) {
/^\s*(\w+)=([\w-]*)\s*$/ && do {
$ENV{$1} = $2;
};
}
}
close $arch_env or subprocerr('dpkg-architecture');
my $arch;
unless (build_sourceonly) {
$arch = mustsetvar($ENV{'DEB_HOST_ARCH'}, _g('host architecture'));
} else {
$arch = 'source';
}
# Preparation of environment stops here
(my $sversion = $version) =~ s/^\d+://;
my $pv = "${pkg}_$sversion";
my $pva = "${pkg}_${sversion}_$arch";
if (not -x "debian/rules") {
warning(_g("debian/rules is not executable: fixing that."));
chmod(0755, "debian/rules"); # No checks of failures, non fatal
}
unless ($call_target) {
chdir('..') or syserr('chdir ..');
withecho('dpkg-source', @source_opts, '--before-build', $dir);
chdir($dir) or syserr("chdir $dir");
}
if ($checkbuilddep) {
if ($admindir) {
push @checkbuilddep_opts, "--admindir=$admindir";
}
system('dpkg-checkbuilddeps', @checkbuilddep_opts);
if (not WIFEXITED($?)) {
subprocerr('dpkg-checkbuilddeps');
} elsif (WEXITSTATUS($?)) {
warning(_g("Build dependencies/conflicts unsatisfied; aborting."));
warning(_g("(Use -d flag to override.)"));
if (build_sourceonly) {
warning(_g("This is currently a non-fatal warning with -S, but"));
warning(_g("will probably become fatal in the future."));
} else {
exit 3;
}
}
}
if ($call_target) {
if ($call_target_as_root or
$call_target =~ /^(clean|binary(|-arch|-indep))$/)
{
withecho(@rootcommand, @debian_rules, $call_target);
} else {
withecho(@debian_rules, $call_target);
}
exit 0;
}
unless ($noclean) {
withecho(@rootcommand, @debian_rules, 'clean');
}
unless (build_binaryonly) {
warning(_g("it is a bad idea to generate a source package " .
"without cleaning up first, it might contain undesired " .
"files.")) if $noclean;
chdir('..') or syserr('chdir ..');
withecho('dpkg-source', @source_opts, '-b', $dir);
chdir($dir) or syserr("chdir $dir");
}
unless (build_sourceonly) {
withecho(@debian_rules, 'build');
withecho(@rootcommand, @debian_rules, $binarytarget);
}
if ($usepause &&
($signchanges || (!build_binaryonly && $signsource))) {
print _g("Press the return key to start signing process\n");
getc();
}
my $signerrors;
unless (build_binaryonly) {
if ($signsource && signfile("$pv.dsc")) {
$signerrors = _g("Failed to sign .dsc and .changes file");
$signchanges = 0;
}
}
if (defined($maint)) { push @changes_opts, "-m$maint" }
if (defined($changedby)) { push @changes_opts, "-e$changedby" }
if (defined($since)) { push @changes_opts, "-v$since" }
if (defined($desc)) { push @changes_opts, "-C$desc" }
my $chg = "../$pva.changes";
print STDERR " dpkg-genchanges @changes_opts >$chg\n";
open CHANGES, '-|', 'dpkg-genchanges', @changes_opts
or subprocerr('dpkg-genchanges');
open OUT, '>', $chg or syserr(_g('write changes file'));
my $infiles = my $files = '';
while ($_ = <CHANGES>) {
print OUT $_ or syserr(_g('write changes file'));
chomp;
if (/^Files:/i) {
$infiles = 1;
} elsif ($infiles && /^\s+(.*)$/) {
$files .= " $1 ";
} elsif ($infiles && /^\S/) {
$infiles = 0;
}
}
close CHANGES or subprocerr(_g('dpkg-genchanges'));
close OUT or syserr(_g('write changes file'));
my $srcmsg;
sub fileomitted($) { return $files !~ /$_[0]/ }
my $ext = $compression_re_file_ext;
if (fileomitted '\.deb') {
# source only upload
if (fileomitted "\.diff\.$ext" and fileomitted "\.debian\.tar\.$ext") {
$srcmsg = _g('source only upload: Debian-native package');
} elsif (fileomitted "\.orig\.tar\.$ext") {
$srcmsg = _g('source only, diff-only upload (original source NOT included)');
} else {
$srcmsg = _g('source only upload (original source is included)');
}
} else {
$srcmsg = _g('full upload (original source is included)');
if (fileomitted '\.dsc') {
$srcmsg = _g('binary only upload (no source included)');
} elsif (fileomitted "\.diff\.$ext" and fileomitted "\.debian\.tar\.$ext") {
$srcmsg = _g('full upload; Debian-native package (full source is included)');
} elsif (fileomitted "\.orig\.tar\.$ext") {
$srcmsg = _g('binary and diff upload (original source NOT included)');
} else {
$srcmsg = _g('full upload (original source is included)');
}
}
if ($signchanges && signfile("$pva.changes")) {
$signerrors = _g("Failed to sign .changes file");
}
if ($cleansource) {
withecho(@rootcommand, @debian_rules, 'clean');
}
chdir('..') or syserr('chdir ..');
withecho('dpkg-source', @source_opts, '--after-build', $dir);
chdir($dir) or syserr("chdir $dir");
print "$progname: $srcmsg\n";
if ($signerrors) {
warning($signerrors);
exit 1;
}
sub mustsetvar {
my ($var, $text) = @_;
error(_g("unable to determine %s"), $text)
unless defined($var);
print "$progname: $text $var\n";
return $var;
}
sub withecho {
shift while !$_[0];
print STDERR " @_\n";
system(@_)
and subprocerr("@_");
}
sub signfile {
my ($file) = @_;
print STDERR " signfile $file\n";
my $qfile = quotemeta($file);
if ($signinterface eq 'gpg') {
system("(cat ../$qfile ; echo '') | ".
"$signcommand --utf8-strings --local-user "
.quotemeta($signkey||$maintainer).
" --clearsign --armor --textmode > ../$qfile.asc");
} else {
system("$signcommand -u ".quotemeta($signkey||$maintainer).
" +clearsig=on -fast <../$qfile >../$qfile.asc");
}
my $status = $?;
unless ($status) {
system('mv', '--', "../$file.asc", "../$file")
and subprocerr('mv');
} else {
system('rm', '-f', "../$file.asc")
and subprocerr('rm -f');
}
print "\n";
return $status
}