On Sunday 13 December 2009 07:18:49 Roger Leigh wrote:
> On Sat, Dec 12, 2009 at 09:04:29PM -0500, Andres Mejia wrote:
> > On Saturday 12 December 2009 20:22:11 Roger Leigh wrote:
> > > On Sat, Dec 12, 2009 at 06:18:43PM -0500, Andres Mejia wrote:
> > > > On Saturday 12 December 2009 05:52:12 Roger Leigh wrote:
> > > > > On Sat, Dec 12, 2009 at 12:42:10AM -0500, Andres Mejia wrote:
> > > > > > On Friday 11 December 2009 07:17:04 Roger Leigh wrote:
> > > > > > > On Thu, Dec 10, 2009 at 09:38:08PM -0500, Andres Mejia wrote:
> > > > > > > > First I would like to say, thank you for reviewing my
> > > > > > > > patches. :)
> > > > > > > > 
> > > > > > > > Next I would like to say, please don't attach any patches
> > > > > > > > inline anymore. I spent way too much time ripping out the
> > > > > > > > patch from my email client and then had to properly format
> > > > > > > > the patch just to get it to apply.
> > > > > > > 
> > > > > > > Thanks for the updated patch.  I've attached what I currently
> > > > > > > have, but I've not yet merged all of your changes into it yet.
> > > > > > > 
> > > > > > > This applies after my first patch (it replaces the second
> > > > > > > patch).
> > > > > > > 
> > > > > > > 
> > > > > > > Regards,
> > > > > > > Roger
> > > > > > 
> > > > > > It didn't apply cleanly for me. I noticed the first hunk from the
> > > > > > two patches adds "use Sbuild::ChrootRoot;", so I figure there's
> > > > > > already some problem with the patch. I'll just wait until it's
> > > > > > applied to the git tree rather than attempt to fix the patch
> > > > > > again. I'll report problems then.
> > > > > 
> > > > > I've put all the current changes at
> > > > > 
> > > > >   git://git.debian.org/users/rleigh/sbuild.git
> > > > > 
> > > > > branch "debuild-am"
> > > > > 
> > > > > It's working for me so far.
> > > > > 
> > > > > > One thing I found wrong was the pipe_command() call for
> > > > > > dpkg-parsechangelog. The hash needs to have 'DIR => getcwd()',
> > > > > > else it will fail.
> > > > > 
> > > > > I think this was caused by something else, since getcwd() is the
> > > > > default.  It's working for me without this.
> > > > 
> > > > I don't know why, but for some reason, it wants to default to using
> > > > "/" as DIR in my system. I've even tried this on a pure checkout of
> > > > the debuild-am branch. I am running this through Debian sid, if that
> > > > helps. I've attached the patch that fixes this for me.
> > > 
> > > Are you using the libs in lib/ when testing (PERL5LIB=lib ./bin/sbuild
> > > 
> > >  ...)? Otherwise you might be using a copy of the system modules.
> > 
> > I'm using perl directly with appropriate options. As in,
> > 
> > $ perl -Isbuild/lib sbuild/bin/sbuild sbuild
> > 
> > > DIR does default to "/" in Chroot.pm, new() line 65.  However, in
> > > Build.pm,
> > > 
> > > this is overridden in run() line 253:
> > >     my $chroot_defaults = $session->get('Defaults');
> > >     $chroot_defaults->{'DIR'} =
> > >     
> > >         $session->strip_chroot_path($session->get('Build Location'));
> > 
> > Yes, this is the problem. You sure you pushed all your changes? I don't
> > see this line in the debuild-am branch and I just verified this is the
> > case via http://git.debian.org/?p=users/rleigh/sbuild.git as well.
> 
> I pushed all of those changes.
> 
> It's definitely there, line 345 of
> http://git.debian.org/?p=users/rleigh/sbuild.git;a=blob;f=lib/Sbuild/Build.
> pm;h=888d291901c6c9ca1baa0383ba7f8483ee87f30e;hb=0f9e497679774569473e451e85
> 4f93d19cddeed7
> 
> This isn't a recent change.  It's been there since the 11th of April
> this year.
> 
> 
> Regards,
> Roger

Here's an updated patch for the debuild-like functionality. I can break up the 
patch if needed. This applies to current git.

Also, the problem with DIR needing to be set as getcwd() is no longer 
relevant.

-- 
Regards,
Andres Mejia
diff --git a/bin/sbuild b/bin/sbuild
index 05a5a0e..c9222a7 100755
--- a/bin/sbuild
+++ b/bin/sbuild
@@ -79,10 +79,13 @@ sub main () {
     $SIG{'ALRM'} = \&main::shutdown;
     $SIG{'PIPE'} = \&main::shutdown;
 
+    # If no arguments are supplied, assume we want to process the current dir.
+    push @ARGV, '.' unless (@ARGV);
+
     # Create jobs
-    foreach (@ARGV) {
-	$jobs{$_} = Sbuild::Build->new($_, $conf);
-	$jobs{$_}->set('Pkg Status Trigger', \&status_trigger)
+    foreach my $job (@ARGV) {
+	$jobs{$job} = Sbuild::Build->new($job, $conf);
+	$jobs{$job}->set('Pkg Status Trigger', \&status_trigger)
     }
     write_jobs_file(); # Will now update on trigger.
 
diff --git a/configure.ac b/configure.ac
index 0ac8d48..8404c05 100644
--- a/configure.ac
+++ b/configure.ac
@@ -96,6 +96,7 @@ AC_PATH_PROG([DPKG_ARCHITECTURE], [dpkg-architecture])
 AC_PATH_PROG([DPKG_BUILDPACKAGE], [dpkg-buildpackage])
 AC_PATH_PROG([DPKG_PARSECHANGELOG], [dpkg-parsechangelog])
 AC_PATH_PROG([DPKG_SOURCE], [dpkg-source])
+AC_PATH_PROG([LINTIAN], [lintian])
 AC_PATH_PROG([DU], [du])
 AC_PATH_PROG([FAKEROOT], [fakeroot])
 AC_PATH_PROG([FIND], [find])
diff --git a/debian/rules b/debian/rules
index 55561ff..2991301 100755
--- a/debian/rules
+++ b/debian/rules
@@ -17,7 +17,8 @@ debian/build/config.status: configure
 	  SCHROOT=/usr/bin/schroot \
 	  SSH=/usr/bin/ssh \
 	  SUDO=/usr/bin/sudo \
-	  APTITUDE=/usr/bin/aptitude
+	  APTITUDE=/usr/bin/aptitude \
+	  LINTIAN=/usr/bin/lintian
 
 build: debian/build/config.status debian/build-stamp
 debian/build-stamp:  debian/build/config.status
diff --git a/etc/sbuild.conf b/etc/sbuild.conf
index bec9677..bffe8c5 100644
--- a/etc/sbuild.conf
+++ b/etc/sbuild.conf
@@ -77,6 +77,13 @@
 #
 #$build_env_cmnd = "";
 
+##
+## DPKG-SOURCE OPTIONS
+##
+
+# Options to pass to dpkg-source.  Each option is a separate arrayref
+# element.
+#$dpkg_source_opts = [];
 
 ##
 ## SBUILD BEHAVIOUR
@@ -275,6 +282,32 @@
 # Job status file (only used in batch mode)
 #$job_file = "build-progress";
 
+##
+## LINTIAN OPTIONS
+##
+
+# lintian binary
+#$lintian = "/usr/bin/lintian";
+
+# Whether to run lintian
+#$run_lintian = 0;
+
+# Options to pass to lintian.  Each option is a separate arrayref
+# element.  For example, ['-i', '-v'] to add -i and -v.
+#$lintian_opt = [];
+
+##
+## EXTERNAL COMMANDS
+##
+
+# pre build commands
+...@pre_build_commands = ();
+# post build commands
+...@post_build_commands = ();
+# Whether to log command output
+#$log_external_command_output = 0;
+# Whether to log command error
+#$log_external_command_error = 0;
 
 ##
 ## PROGRAMS USED BY SBUILD
diff --git a/lib/Sbuild/Build.pm b/lib/Sbuild/Build.pm
index 93032a9..b15aeed 100644
--- a/lib/Sbuild/Build.pm
+++ b/lib/Sbuild/Build.pm
@@ -34,17 +34,19 @@ use FileHandle;
 use GDBM_File;
 use File::Copy qw(); # copy is already exported from Sbuild, so don't export
 		     # anything.
+use Cwd qw(:DEFAULT abs_path);
 
 use Sbuild qw($devnull binNMU_version version_compare split_version copy isin send_build_log debug df);
 use Sbuild::Base;
 use Sbuild::ChrootSetup qw(clean update upgrade distupgrade);
 use Sbuild::ChrootInfoSchroot;
 use Sbuild::ChrootInfoSudo;
+use Sbuild::ChrootRoot;
 use Sbuild::Sysconfig qw($version $release_date);
 use Sbuild::Conf;
 use Sbuild::LogBase qw($saved_stdout);
 use Sbuild::Sysconfig;
-use Sbuild::Utility qw(check_url download dsc_files);
+use Sbuild::Utility qw(check_url download parse_file dsc_files);
 use Sbuild::AptitudeBuildDepSatisfier;
 use Sbuild::InternalBuildDepSatisfier;
 
@@ -65,47 +67,6 @@ sub new {
     my $self = $class->SUPER::new($conf);
     bless($self, $class);
 
-    # DSC, package and version information:
-    $self->set_dsc($dsc);
-    my $ver = $self->get('DSC Base');
-    $ver =~ s/\.dsc$//;
-    # Note, will be overwritten by Version: in DSC.
-    $self->set_version($ver);
-
-    # Do we need to download?
-    $self->set('Download', 0);
-    $self->set('Download', 1)
-	if (!($self->get('DSC Base') =~ m/\.dsc$/) || # Use apt to download
-	    check_url($self->get('DSC'))); # Valid URL
-
-    # Can sources be obtained?
-    $self->set('Invalid Source', 0);
-    $self->set('Invalid Source', 1)
-	if ((!$self->get('Download')) ||
-	    (!($self->get('DSC Base') =~ m/\.dsc$/) && # Use apt to download
-	     $self->get('DSC') ne $self->get('Package_OVersion')) ||
-	    (!defined $self->get('Version')));
-
-    debug("DSC = " . $self->get('DSC') . "\n");
-    debug("Source Dir = " . $self->get('Source Dir') . "\n");
-    debug("DSC Base = " . $self->get('DSC Base') . "\n");
-    debug("DSC File = " . $self->get('DSC File') . "\n");
-    debug("DSC Dir = " . $self->get('DSC Dir') . "\n");
-    debug("Package_Version = " . $self->get('Package_Version') . "\n");
-    debug("Package_OVersion = " . $self->get('Package_OVersion') . "\n");
-    debug("Package_OSVersion = " . $self->get('Package_OSVersion') . "\n");
-    debug("Package_SVersion = " . $self->get('Package_SVersion') . "\n");
-    debug("Package = " . $self->get('Package') . "\n");
-    debug("Version = " . $self->get('Version') . "\n");
-    debug("OVersion = " . $self->get('OVersion') . "\n");
-    debug("OSVersion = " . $self->get('OSVersion') . "\n");
-    debug("SVersion = " . $self->get('SVersion') . "\n");
-    debug("VersionEpoch = " . $self->get('VersionEpoch') . "\n");
-    debug("VersionUpstream = " . $self->get('VersionUpstream') . "\n");
-    debug("VersionDebian = " . $self->get('VersionDebian') . "\n");
-    debug("Download = " . $self->get('Download') . "\n");
-    debug("Invalid Source = " . $self->get('Invalid Source') . "\n");
-
     $self->set('Arch', undef);
     $self->set('Chroot Dir', '');
     $self->set('Chroot Build Dir', '');
@@ -131,6 +92,43 @@ sub new {
     $self->set('Have DSC Build Deps', []);
     $self->set('Log File', undef);
     $self->set('Log Stream', undef);
+    $self->set('Debian Source Dir', undef);
+
+    my $host = Sbuild::ChrootRoot->new($self->get('Config'));
+    $self->set('Host', $host);
+
+    # DSC, package and version information:
+    $self->set_dsc($dsc);
+    my $ver = $self->get('DSC Base');
+    $ver =~ s/\.dsc$//;
+    # Note, will be overwritten by Version: in DSC.
+    $self->set_version($ver);
+
+    # Do we need to download?
+    $self->set('Download', 0);
+    $self->set('Download', 1)
+	if (!($self->get('DSC Base') =~ m/\.dsc$/) || # Use apt to download
+	    check_url($self->get('DSC')) || # Valid URL
+	    ($self->get('Debian Source Dir'))); # Debianized source directory
+
+    # Can sources be obtained?
+    $self->set('Invalid Source', 0);
+    $self->set('Invalid Source', 1)
+	if ((!$self->get('Download')) ||
+	    (!($self->get('DSC Base') =~ m/\.dsc$/) && # Use apt to download
+	     $self->get('DSC') ne $self->get('Package_OVersion')) ||
+	    (!defined $self->get('Version')));
+
+    # Output certain values for debugging purposes
+    foreach ('DSC', 'Source Dir', 'DSC Base', 'DSC File', 'DSC Dir',
+             'Package_Version', 'Package_OVersion',
+             'Package_OSVersion', 'Package_SVersion', 'Package',
+             'Version', 'OVersion', 'OSVersion', 'SVersion',
+             'VersionEpoch', 'VersionUpstream', 'VersionDebian',
+             'Download', 'Invalid Source') {
+      my $val = $self->get($_);
+      debug("$_ = " . $val . "\n") if defined($val);
+    }
 
     return $self;
 }
@@ -141,9 +139,55 @@ sub set_dsc {
 
     debug("Setting DSC: $dsc\n");
 
+    # Check if the DSC given is a directory on the local system. This
+    # means we'll build the source package with dpkg-source first.
+    if (-d $dsc) {
+	my $pipe = $self->get('Host')->pipe_command(
+	    { COMMAND => [$Sbuild::Sysconfig::programs{'DPKG_PARSECHANGELOG'},
+			  "-l" . abs_path($dsc) . "/debian/changelog"],
+	      USER => $self->get_conf('USERNAME'),
+	      CHROOT => 0,
+	      PRIORITY => 0,
+	    });
+
+	if (! $pipe) {
+	    $self->log_error("Could not parse $dsc/debian/changelog: $!");
+	    $self->set('Invalid Source', 1);
+	    goto set_vars;
+	}
+
+	my $stanzas = parse_file($pipe);
+
+	my $stanza = @{$stanzas}[0];
+	my $package = ${$stanza}{'Source'};
+	my $version = ${$stanza}{'Version'};
+
+	if (!defined($package) || !defined($version)) {
+	    $self->log_error("Missing Source or Version in $dsc/debian/changelog");
+	    $self->set('Invalid Source', 1);
+	    goto set_vars;
+	}
+
+	$version = $self->strip_epoch($version);
+	my $dir = getcwd();
+	# Note: need to support cases when invoked from a subdirectory
+	# of the build directory, i.e. $dsc/foo -> $dsc/.. in addition
+	# to $dsc -> $dsc/.. as below.
+	if ($dir eq abs_path($dsc)) {
+	    # We won't attempt to build the source package from the source
+	    # directory so the source package files will go to the parent dir.
+	    $dir = abs_path("$dir/..");
+	    $self->set_conf('BUILD_DIR', $dir);
+	}
+	$self->set('Debian Source Dir', abs_path($dsc));
+
+	$self->set_version("${package}_${version}");
+	$dsc = "$dir/" . $self->get('Package_OSVersion') . ".dsc";
+    }
+
+set_vars:
     $self->set('DSC', $dsc);
     $self->set('Source Dir', dirname($dsc));
-
     $self->set('DSC Base', basename($dsc));
 }
 
@@ -154,6 +198,8 @@ sub set_version {
     debug("Setting package version: $pkgv\n");
 
     my ($pkg, $version) = split /_/, $pkgv;
+    return if (!defined($pkg) || !defined($version));
+
     # Original version (no binNMU or other addition)
     my $oversion = $version;
     # Original version with stripped epoch
@@ -205,6 +251,8 @@ sub get_status {
 sub run {
     my $self = shift;
 
+    $self->get('Host')->set('Log Stream', $self->get('Log Stream'));
+
     $self->set_status('building');
 
     $self->set('Pkg Start Time', time);
@@ -222,6 +270,58 @@ sub run {
 	goto cleanup_skip;
     }
 
+    # Acquire the architecture we're building for.
+    $self->set('Arch', $self->get_conf('ARCH'));
+
+    # TODO: Get package name from build object
+    if (!$self->open_build_log()) {
+	goto cleanup_close;
+    }
+
+    # Run pre build external commands
+    $self->run_external_commands("pre-build-commands",
+	$self->get_conf('LOG_EXTERNAL_COMMAND_OUTPUT'),
+	$self->get_conf('LOG_EXTERNAL_COMMAND_ERROR'));
+
+    # Build the source package if given a Debianized source directory
+    if ($self->get('Debian Source Dir')) {
+	$self->set('Pkg Fail Stage', 'pack-source');
+	$self->log_subsection("Build Source Package");
+
+	$self->log_subsubsection('clean');
+	$self->get('Host')->run_command(
+	    { COMMAND => [$self->get_conf('FAKEROOT'),
+			  'debian/rules',
+			  'clean'],
+	      USER => $self->get_conf('USERNAME'),
+	      CHROOT => 0,
+	      DIR => $self->get('Debian Source Dir'),
+	      PRIORITY => 0,
+	    });
+	if ($?) {
+	    $self->log_error("Failed to clean source directory");
+
+	    goto cleanup_skip;
+	}
+
+	$self->log_subsubsection('dpkg-source');
+	my @dpkg_source_command = ($self->get_conf('DPKG_SOURCE'), '-b');
+	push @dpkg_source_command, @{$self->get_conf('DPKG_SOURCE_OPTIONS')} if
+	    ($self->get_conf('DPKG_SOURCE_OPTIONS'));
+	push @dpkg_source_command, $self->get('Debian Source Dir');
+	$self->get('Host')->run_command(
+	    { COMMAND => \...@dpkg_source_command,
+	      USER => $self->get_conf('USERNAME'),
+	      CHROOT => 0,
+	      DIR => $self->get_conf('BUILD_DIR'),
+	      PRIORITY => 0,
+	    });
+	if ($?) {
+	    $self->log_error("Failed to build source package");
+	    goto cleanup_skip;
+	}
+    }
+
     my $chroot_info;
     if ($self->get_conf('CHROOT_MODE') eq 'schroot') {
 	$chroot_info = Sbuild::ChrootInfoSchroot->new($self->get('Config'));
@@ -253,11 +353,6 @@ sub run {
     # the chroot directly.
     $session->set('Build Location', $self->get('Chroot Build Dir'));
 
-    # TODO: Get package name from build object
-    if (!$self->open_build_log()) {
-	goto cleanup_close;
-    }
-
     # Needed so chroot commands log to build log
     $session->set('Log Stream', $self->get('Log Stream'));
 
@@ -399,12 +494,52 @@ sub run {
 	}
     }
     $self->get('Dependency Resolver') && $self->get('Dependency Resolver')->remove_srcdep_lock_file();
+
   cleanup_close:
     # End chroot session
     $session->end_session();
     $session = undef;
     $self->set('Session', $session);
 
+    if ($self->get('Pkg Status') eq "successful") {
+	$self->log_subsection("Post Build");
+
+	# Run lintian.
+	my $lintian = $self->get_conf('LINTIAN');
+	if (($self->get_conf('RUN_LINTIAN')) && (-x $lintian)) {
+	    $self->log_subsubsection("lintian");
+
+	    my @lintian_command = ($lintian);
+	    push @lintian_command, @{$self->get_conf('LINTIAN_OPT')} if
+		($self->get_conf('LINTIAN_OPT'));
+	    push @lintian_command, $self->get('Changes File');
+	    $self->get('Host')->run_command(
+		{ COMMAND => \...@lintian_command,
+		  USER => $self->get_conf('USERNAME'),
+		  CHROOT => 0,
+		  PRIORITY => 0,
+		});
+	    my $status = $? >> 8;
+
+	    $self->log("\n");
+	    if (! $?) {
+		$self->log_info("Lintian run was successful.\n");
+	    } else {
+		my $why = "unknown reason";
+		$why = "runtime error" if ($status == 2);
+		$why = "policy violation" if ($status == 1);
+		$why = "received signal " . $? & 127 if ($? & 127);
+		$self->log_error("Lintian run failed ($why)\n");
+	    }
+	}
+
+	# Run post build external commands
+	$self->run_external_commands("post-build-commands",
+	    $self->get_conf('LOG_EXTERNAL_COMMAND_OUTPUT'),
+	    $self->get_conf('LOG_EXTERNAL_COMMAND_ERROR'));
+
+    }
+
     $self->close_build_log();
 
   cleanup_skip:
@@ -683,6 +818,122 @@ sub fetch_source_files {
     return 1;
 }
 
+# Subroutine that runs any command through the system (i.e. not through the
+# chroot. It takes a string of a command with arguments to run along with
+# arguments whether to save STDOUT and/or STDERR to the log stream
+sub run_command {
+    my $self = shift;
+    my $command = shift;
+    my $log_output = shift;
+    my $log_error = shift;
+
+    # Duplicate output from our commands to the log stream if we are asked to
+    my ($out, $err);
+    if ($log_output) {
+	if (! open $out, ">&STDOUT") {
+	    $self->log_error("Could not duplicate STDOUT for $command: $!");
+	    return 0;
+	}
+	if (! open STDOUT, '>&', $self->get('Log Stream')) {
+	    $self->log_error("Could not duplicate STDOUT to log stream: $!");
+	    return 0;
+	}
+    }
+    if ($log_error) {
+	if (! open $err, ">&STDERR") {
+	    $self->log_error("Could not duplicate STDERR for $command: $!");
+	    return 0;
+	}
+	if (! open STDERR, '>&', $self->get('Log Stream')) {
+	    $self->log_error("Could not duplicate STDERR to log stream: $!");
+	    return 0;
+	}
+    }
+
+    # Run the command and save the exit status
+    system(@{$command});
+    my $status = ($? >> 8);
+
+    # Restore STDOUT and STDERR
+    if ($log_output) {
+	if (! open STDOUT, '>&', $out) {
+	    $self->log_error("Can't restore STDOUT: $!");
+	    return 0;
+	}
+	$out->close();
+    }
+    if ($log_error) {
+	if (! open STDERR, '>&', $err) {
+	    $self->log_error("Can't restore STDOUT: $!");
+	    return 0;
+	}
+	$err->close();
+    }
+
+    # Check if the command failed
+    if ($status != 0) {
+	return 0;
+    }
+    return 1;
+}
+
+# Subroutine that processes external commands to be run during various stages of
+# an sbuild run. We also ask if we want to log any output from the commands
+sub run_external_commands {
+    my $self = shift;
+    my $stage = shift;
+    my $log_output = shift;
+    my $log_error = shift;
+
+    # Determine which set of commands to run based on the string $commands
+    my @commands;
+    if ($stage eq "pre-build-commands") {
+	$self->log_subsection("Pre Build Commands");
+	@commands = @{$self->get_conf('PRE_BUILD_COMMANDS')};
+    } elsif ($stage eq "post-build-commands") {
+	$self->log_subsection("Post Build Commands");
+	@commands = @{$self->get_conf('POST_BUILD_COMMANDS')};
+    }
+
+    # Run each command, substituting the various percent escapes (like
+    # %SBUILD_DSC) from the commands to run with the appropriate subsitutions.
+    my $dsc = $self->get('DSC');
+    my $changes;
+    $changes = $self->get('Changes File') if ($self->get('Changes File'));
+    my %percent = (
+	"%" => "%",
+	"d" => $dsc, "SBUILD_DSC" => $dsc,
+	"c" => $changes, "SBUILD_CHANGES" => $changes,
+    );
+    # Our escapes pattern, with longer escapes first, then sorted lexically.
+    my $keyword_pat = join("|",
+	sort {length $b <=> length $a || $a cmp $b} keys %percent);
+    my $returnval = 1;
+    foreach my $command (@commands) {
+	foreach my $arg (@{$command}) {
+	  $arg =~ s{
+	      # Match a percent followed by a valid keyword
+	     \%($keyword_pat)
+	  }{
+	      # Substitute with the appropriate value only if it's defined
+	      $percent{$1} || $&
+	  }msxge;
+	}
+  my $command_str = join(" ", @{$command});
+	$self->log_subsubsection("$command_str");
+	$returnval = $self->run_command($command, $log_output, $log_error);
+	$self->log("\n");
+	if (!$returnval) {
+	    $self->log_error("Command '$command_str' failed to run.\n");
+	} else {
+	    $self->log_info("Finished running '$command_str'.\n");
+	}
+    }
+    $self->log("\nFinished processing commands.\n");
+    $self->log_sep();
+    return $returnval;
+}
+
 sub build {
     my $self = shift;
 
@@ -1006,7 +1257,8 @@ sub build {
 	    my(@do_dists, @saved_dists);
 	    $self->log("\n$changes:\n");
 	    open( F, "<$build_dir/$changes" );
-	    if (open( F2, ">$changes.new" )) {
+	    my $sys_build_dir = $self->get_conf('BUILD_DIR');
+	    if (open( F2, ">$sys_build_dir/$changes.new" )) {
 		while( <F> ) {
 		    if (/^Distribution:\s*(.*)\s*$/ and $self->get_conf('OVERRIDE_DISTRIBUTION')) {
 			$self->log("Distribution: " . $self->get_conf('DISTRIBUTION') . "\n");
@@ -1027,13 +1279,15 @@ sub build {
 		    }
 		}
 		close( F2 );
-		rename("$changes.new", "$changes")
-		    or $self->log("$changes.new could not be renamed to $changes: $!\n");
+		rename("$sys_build_dir/$changes.new", "$sys_build_dir/$changes")
+		    or $self->log("$sys_build_dir/$changes.new could not be " .
+		    "renamed to $sys_build_dir/$changes: $!\n");
+		$self->set('Changes File', "$sys_build_dir/$changes");
 		unlink("$build_dir/$changes")
 		    if $build_dir;
 	    }
 	    else {
-		$self->log("Cannot create $changes.new: $!\n");
+		$self->log("Cannot create $sys_build_dir/$changes.new: $!\n");
 		$self->log("Distribution field may be wrong!!!\n");
 		if ($build_dir) {
 		    system "mv", "-f", "$build_dir/$changes", "."
@@ -1880,6 +2134,14 @@ sub debian_files_list {
 }
 
 
+# This subroutine strips the epoch from a Debian package version.
+sub strip_epoch {
+    my $self = shift;
+    my $version = shift;
+    $version =~ s/^\d+?://;
+    return $version;
+}
+
 # Figure out chroot architecture
 sub chroot_arch {
     my $self = shift;
diff --git a/lib/Sbuild/Conf.pm b/lib/Sbuild/Conf.pm
index 3003290..8485906 100644
--- a/lib/Sbuild/Conf.pm
+++ b/lib/Sbuild/Conf.pm
@@ -177,6 +177,9 @@ sub init_allowed_keys {
 	    CHECK => $validate_program,
 	    DEFAULT => $Sbuild::Sysconfig::programs{'DPKG_SOURCE'}
 	},
+	'DPKG_SOURCE_OPTIONS'			=> {
+	    DEFAULT => []
+	},
 	'DCMD'					=> {
 	    CHECK => $validate_program,
 	    DEFAULT => $Sbuild::Sysconfig::programs{'DCMD'}
@@ -483,6 +486,28 @@ sub init_allowed_keys {
 			     qw(internal aptitude));
 	    },
 	},
+	'LINTIAN'				=> {
+	    CHECK => $validate_program,
+	    DEFAULT => $Sbuild::Sysconfig::programs{'LINTIAN'},
+	},
+	'RUN_LINTIAN'				=> {
+	    DEFAULT => 0
+	},
+	'LINTIAN_OPTIONS'			=> {
+	    DEFAULT => []
+	},
+	'PRE_BUILD_COMMANDS'			=> {
+	    DEFAULT => []
+	},
+	'POST_BUILD_COMMANDS'			=> {
+	    DEFAULT => []
+	},
+	'LOG_EXTERNAL_COMMAND_OUTPUT'		=> {
+	    DEFAULT => 0
+	},
+	'LOG_EXTERNAL_COMMAND_ERROR'		=> {
+	    DEFAULT => 0
+	}
     );
 
     $self->set_allowed_keys(\%sbuild_keys);
@@ -511,6 +536,7 @@ sub read_config {
     my $apt_cache = undef;
     my $aptitude = undef;
     my $dpkg_source = undef;
+    my $dpkg_source_opts = undef;
     my $dcmd = undef;
     my $md5sum = undef;
     my $avg_time_db = undef;
@@ -571,6 +597,15 @@ sub read_config {
     my $job_file = undef;
     my $build_dir = undef;
     my $build_dep_resolver = undef;
+    my $lintian = undef;
+    my $run_lintian = undef;
+    my $lintian_opts = undef;
+    my @pre_build_commands;
+    undef @pre_build_commands;
+    my @post_build_commands;
+    undef @post_build_commands;
+    my $log_external_command_output = undef;
+    my $log_external_command_error = undef;
 
     foreach ($Sbuild::Sysconfig::paths{'SBUILD_CONF'}, "$HOME/.sbuildrc") {
 	if (-r $_) {
@@ -599,6 +634,7 @@ sub read_config {
     $self->set('APT_CACHE', $apt_cache);
     $self->set('APTITUDE', $aptitude);
     $self->set('DPKG_SOURCE', $dpkg_source);
+    $self->set('DPKG_SOURCE_OPTIONS', $dpkg_source_opts);
     $self->set('DCMD', $dcmd);
     $self->set('MD5SUM', $md5sum);
     $self->set('AVG_TIME_DB', $avg_time_db);
@@ -678,7 +714,17 @@ sub read_config {
 	$self->get('BIN_NMU')) {
 	die "A maintainer name, uploader name or key ID must be specified in .sbuildrc,\nor use -m, -e or -k, when performing a binNMU\n";
     }
-
+    $self->set('LINTIAN', $lintian);
+    $self->set('RUN_LINTIAN', $run_lintian);
+    $self->set('LINTIAN_OPTIONS', $lintian_opts);
+    $self->set('PRE_BUILD_COMMANDS', \...@pre_build_commands)
+	if (@pre_build_commands);
+    $self->set('POST_BUILD_COMMANDS', \...@post_build_commands)
+	if (@post_build_commands);
+    $self->set('LOG_EXTERNAL_COMMAND_OUTPUT', $log_external_command_output)
+	if ($log_external_command_output);
+    $self->set('LOG_EXTERNAL_COMMAND_ERROR', $log_external_command_error)
+	if ($log_external_command_error);
 }
 
 sub check_group_membership ($) {
diff --git a/lib/Sbuild/Options.pm b/lib/Sbuild/Options.pm
index 9a2a011..05ba760 100644
--- a/lib/Sbuild/Options.pm
+++ b/lib/Sbuild/Options.pm
@@ -124,6 +124,14 @@ sub set_options {
 			   push(@{$self->get_conf('DPKG_BUILDPACKAGE_USER_OPTIONS')},
 				$_[1]);
 		       },
+		       "dpkg-source-opts=s" => sub {
+			   push(@{$self->get_conf('DPKG_SOURCE_OPTIONS')},
+				split(/\s+/, $_[1]));
+		       },
+		       "dpkg-source-opt=s" => sub {
+			   push(@{$self->get_conf('DPKG_SOURCE_OPTIONS')},
+				$_[1]);
+		       },
 		       "mail-log-to=s" => sub {
 			   $self->set_conf('MAILTO', $_[1]);
 			   $self->set_conf('MAILTO_FORCED_BY_CLI', "yes");
@@ -164,6 +172,33 @@ sub set_options {
 		       "build-dep-resolver=s" => sub {
 			   $self->set_conf('BUILD_DEP_RESOLVER', $_[1]);
 		       },
+			"run-lintian" => sub {
+			    $self->set_conf('RUN_LINTIAN', 1);
+		       },
+		       "lintian-opts=s" => sub {
+			   push(@{$self->get_conf('LINTIAN_OPTIONS')},
+				split(/\s+/, $_[1]));
+		       },
+		       "lintian-opt=s" => sub {
+			   push(@{$self->get_conf('LINTIAN_OPTIONS')},
+				$_[1]);
+		       },
+			"pre-build-commands=s" => sub {
+			   my @command = split(/\s+/, $_[1]);
+			   push(@{$self->get_conf('PRE_BUILD_COMMANDS')},
+				\...@command);
+		       },
+			"post-build-commands=s" => sub {
+			   my @command = split(/\s+/, $_[1]);
+			   push(@{$self->get_conf('POST_BUILD_COMMANDS')},
+				\...@command);
+		       },
+			"log-external-command-output" => sub {
+			    $self->set_conf('LOG_EXTERNAL_COMMAND_OUTPUT', 1);
+		       },
+			"log-external-command-error" => sub {
+			    $self->set_conf('LOG_EXTERNAL_COMMAND_ERROR', 1);
+		       }
 	);
 }
 
diff --git a/lib/Sbuild/Sysconfig.pm.in b/lib/Sbuild/Sysconfig.pm.in
index 720a639..7bd7087 100644
--- a/lib/Sbuild/Sysconfig.pm.in
+++ b/lib/Sbuild/Sysconfig.pm.in
@@ -96,6 +96,7 @@ our %programs = (
     'DPKG_BUILDPACKAGE' => '@DPKG_BUILDPACKAGE@',
     'DPKG_PARSECHANGELOG' => '@DPKG_PARSECHANGELOG@',
     'DPKG_SOURCE' => '@DPKG_SOURCE@',
+    'LINTIAN' => '@LINTIAN@',
     'DU' => '@DU@',
     'FAKEROOT' => '@FAKEROOT@',
     'GPG' => '@GPG@',
diff --git a/lib/Sbuild/Utility.pm b/lib/Sbuild/Utility.pm
index 49a6d16..a29e9e8 100644
--- a/lib/Sbuild/Utility.pm
+++ b/lib/Sbuild/Utility.pm
@@ -390,7 +390,11 @@ sub parse_file ($) {
     {
         # Attempt to open and read the file
         my $fh;
-        open $fh, '<', $file or die "Could not read $file: $!";
+        if (ref($file) eq "GLOB") {
+          $fh = $file;
+        } else {
+          open $fh, '<', $file or die "Could not read $file: $!";
+        }
 
         # Read paragraph by paragraph
         local $/ = "";
@@ -409,6 +413,10 @@ sub parse_file ($) {
                 my ($field, $field_contents);
                 $field = $1 if ($match =~ /([^:]+?):/msx);
                 $field_contents = $1 if ($match =~ /[^:]+?:(.*)/msx);
+
+                # Trim leading and trailing space from entries
+                $field_contents =~ s/^[\s]+?\b//msx;
+                $field_contents =~ s/[\s]+?$//msx;
                 $fields{$field} = $field_contents;
             }
 
diff --git a/man/sbuild.1.in b/man/sbuild.1.in
index 18ea8db..f215a7c 100644
--- a/man/sbuild.1.in
+++ b/man/sbuild.1.in
@@ -41,13 +41,22 @@ sbuild \- build debian packages from source
 .RB [ \-k \[or] \-\-keyid=\fIkey-id\fP ]
 .RB [ \-\-debbuildopt=\fIoption\fP ]
 .RB [ \-\-debbuildopts=\fIoptions\fP ]
+.RB [ \-\-dpkg-source-opt=\fIoptions\fP ]
+.RB [ \-\-dpkg-source-opts=\fIoptions\fP ]
 .RB [ \-p \[or] \-\-purge=\fPpurge-mode\fP ]
 .RB [ \-\-purge\-deps=\fPpurge-mode\fP ]
 .RB [ \-b \[or] \-\-batch]
 .RB [ \-n \[or] \-\-nolog ]
+.RB [ \-\-run\-lintian ]
+.RB [ \-\-lintian\-opt=\fIoptions\fP ]
+.RB [ \-\-lintian\-opts=\fIoptions\fP ]
+.RB [ \-\-pre\-build\-commands=\fIstring\fP ]
+.RB [ \-\-post\-build\-commands=\fIstring\fP ]
+.RB [ \-\-log\-external\-command\-output ]
+.RB [ \-\-log\-external\-command\-error ]
 .RB [ \-\-setup\-hook=\fIhook-script\fP ]
 .RB [ \-\-build\-dep\-resolver=\fIresolver\fP ]
-.BR PACKAGE1_VERSION[.dsc] " [" PACKAGE2_VERSION[.dsc] " [" PACKAGE\f[BI]n\fP_VERSION[.dsc] ]]
+.BR [PACKAGE1[.dsc] " [" PACKAGE2[.dsc] " [ " ... " " ]]]
 .SH VERSION
 This man page documents the packaged version of sbuild.  This version
 is maintained by the \fBbuildd-tools\fP project developers on Alioth
@@ -65,6 +74,22 @@ them, it knows about source dependencies.
 \fBsbuild\fR can fetch the Debian source over a network, or it can use
 locally available sources.
 .PP
+sbuild is given a list of packages to process as the arguments
+\fBPACKAGE\f[BI]i\fP[.dsc]\fR. These arguments are in the form of either
+debianized package source directories, a source package name along with a
+version in the form \fIpackage_version\fP, or a .dsc file. If no arguments are
+given, the current working directory is passed as an argument.
+.PP
+For arguments given as source directories, dpkg-source is first run to produce a
+source .dsc file. Then, the package is built using the .dsc produced. For
+arguments in the form \fIpackage_version\fP, apt is used to download the source
+package. For arguments given as a .dsc file, sbuild builds the source packages
+directly. For .dsc files in remote locations, the source packages are downloaded
+first, then built.
+.PP
+It is also possible to run external commands with sbuild. See the section
+\fBEXTERNAL COMMANDS\fR for more on this.
+.PP
 \fBsbuild\fR mails the build logs to a user.  It is configured by the
 configuration files \fI/etc/sbuild/sbuild.conf\fP and \fI~/.sbuildrc\fP.  An
 example sbuildrc is available in
@@ -177,6 +202,15 @@ Pass the specified options directly to dpkg\-buildpackage.  The options should
 be separated by spaces.  If any options contain spaces, use \-\-debbuildopt
 instead.
 .TP
+.BR \-\-dpkg\-source\-opt=\fIoptions\fP
+Pass the specified options directly to dpkg-source. This is only used when
+creating a source package from a Debianized source directory.
+.br
+\fBNOTE:\fR The '\fI-b\fP' option will always be passed to dpkg-source.
+.TP
+.BR \-\-dpkg\-source\-opts=\fIoptions\fP
+Extra options to be appended to existing options passed to dpkg-source.
+.TP
 .BR "\-\-mail\-log\-to=\fIemail-address\fP"
 Send the build log to the specified email address.  This overrides the
 \fI$mailto\fP configuration option.
@@ -236,6 +270,29 @@ This option is similar to --make-binNMU except that it allows the user to
 specify an arbitrary string to be appended to the version number (immediately
 before the '+' in the Debian revision if --make-binNMU is also provided).
 .TP
+.BR \-\-run\-lintian
+Run lintian after a successful build.
+.TP
+.BR \-\-lintian\-opt=\fIoptions\fP
+Run lintian with the specified options.
+.TP
+.BR \-\-lintian\-opts=\fIoptions\fP
+Append extra options to existing options passed to lintian.
+.TP
+.BR \-\-pre\-build\-commands=\fIstring\fP
+Run this command before a build starts. This option can be used multiple times
+to add multiple commands.
+.TP
+.BR \-\-post\-build\-commands=\fIstring\fP
+Run this command after a successful build. This option can be used multiple
+times to add multiple commands.
+.TP
+.BR \-\-log\-external\-command\-output
+Write output from external commands to the build log.
+.TP
+.BR \-\-log\-external\-command\-error
+Write error output from external commands to the build log.
+.TP
 .BR "\-\-setup\-hook=\fIhook-script\fP"
 Run the specified script inside the chroot before building.  This script may
 perform any required actions to customise or configure the chroot.  Note that
@@ -247,6 +304,65 @@ Use the specified resolver to handle selecting the build dependencies.
 Supported resolvers are \fIinternal\fP (the default) and \fIaptitude\fP.  The
 latter tries to be smarter but is also slower.  It may be of some help when
 trying to pull build dependencies from an extra suite, like \fIexperimental\fP.
+.SH EXTERNAL COMMANDS
+Support to run external commands during an sbuild run is provided. A set of
+external commands can be run before a build starts and after a successful build.
+Providing commands to run is done through the appropriate options given on the
+command line and through the use of the configuration files. In the
+configuration file, the list of commands to run are placed in an array of
+array of strings corresponding to the commands to run.
+.PP
+Here's an example of how to edit the configuration files to run "foo" and "bar"
+with arguments before a build starts.
+.PP
+\f[...@pre_build_commands = (['foo', 'arg1', 'arg2'],\fP
+.br
+\f[CB]    ['bar', 'arg1', 'arg2', 'arg3'],\fP
+.br
+\f[CB]    );\fP
+.PP
+Here's an example of how to do the same with the previous example, except using
+the \fI\-\-pre\-build\-commands\fP option.
+.PP
+\f[CB]$ sbuild \\\fP
+.br
+\f[CB]      \-\-pre\-build\-commands='foo arg1 arg2' \\\fP
+.br
+\f[CB]      \-\-pre\-build\-commands='bar arg1 arg2 arg3'\fP
+.PP
+Besides running external commands, sbuild can also detect the use of certain
+percent escapes given as arguments. These are used to allow for a command to be
+supplied with a certain argument depending on the escape given.
+For example, it could be possible to have an external command be given the
+path to a .changes file.
+.PP
+Here is a listing of keywords and a description of what it's converted to.
+.TP
+\fB%%\fR
+Used to escape a '\fI%\fP'.
+.TP
+\fB%d\fR, \fB%SBUILD_DSC\fR
+These escapes are converted to the absolute path to a package's .dsc file.
+.TP
+\fB%c\fR, \fB%SBUILD_CHANGES\fR
+These escapes are converted to the absolute path to a package's source .changes
+file.
+.PP
+Percent escapes are only substituted when an appropriate value is defined for
+them. At other times, it is left unchanged. For example, a .changes file is only
+defined at the end of a build, so using \fI%c\fR will only be substituted for
+post-build-commands.
+.PP
+Here's an example of using an escape to run piuparts on a .changes file after
+a build is done.
+.PP
+\f[CB]$ sbuild \-\-post\-build\-commands \\\fP
+.br
+\f[CB]      'sudo piuparts %SBUILD_CHANGES'\fP
+.PP
+One final note, external commands are processed in the order they are given.
+Also, the commands given in a configuration file are processed first, then the
+commands given through the command line options.
 .SH ENVIRONMENT VARIABLES
 The following environment variables are used by \fBsbuild\fR:
 .IP "DEBEMAIL"

Reply via email to