Преглед изворни кода

dpkg-buildpackage: Add shell hooks support

This adds new --hook-<hook-name> options where <hook-name> is currently
one of:

  init preclean source build binary changes postclean check sign done

that will execute the specified shell snippet with the following format
subsitutions applied:

  %%  A single % character.
  %a  If the action is being performed (0 or 1).
  %p  The source package name.
  %v  The source package version.
  %s  The source package version w/o the epoch.
  %u  The upstream version.

This implementation is based and compatible with the debuild code in
devscripts 2.13.9.

Closes: #476221
Guillem Jover пре 12 година
родитељ
комит
f1084f41e1
3 измењених фајлова са 128 додато и 23 уклоњено
  1. 2 0
      debian/changelog
  2. 65 23
      man/dpkg-buildpackage.1
  3. 61 0
      scripts/dpkg-buildpackage.pl

+ 2 - 0
debian/changelog

@@ -29,6 +29,8 @@ dpkg (1.17.6) UNRELEASED; urgency=low
   * Add support for Ignore-Blacklist-Groups field in symbols files, with the
     two available group values aeabi and gomp. Closes: #694524
   * Allow updating checksums in Dpkg::Checksums without erroring out.
+  * Add shell hooks support to dpkg-buildpackage, based on the debuild
+    implementation in devscripts 2.13.9. Closes: #476221
 
   [ Updated dpkg translations ]
   * Swedish (Peter Krefting).

+ 65 - 23
man/dpkg-buildpackage.1

@@ -33,44 +33,48 @@ is a program that automates the process of building a Debian package. It
 consists of the following steps:
 .IP \fB1.\fP 3
 It prepares the build environment by setting various environment
-variables (see \fBENVIRONMENT\fP) and calls \fBdpkg\-source
-\-\-before\-build\fP (unless \fB\-T\fP or \fB\-\-target\fP has been used).
+variables (see \fBENVIRONMENT\fP), runs the \fBinit\fP hook, and calls
+\fBdpkg\-source \-\-before\-build\fP (unless \fB\-T\fP or \fB\-\-target\fP
+has been used).
 .IP \fB2.\fP 3
 It checks that the build-dependencies and build-conflicts
 are satisfied (unless \fB\-d\fP is specified).
 .IP \fB3.\fP 3
 If a specific target has been selected with the \fB\-T\fP or \fB\-\-target\fP
-option, it calls that target and stops here. Otherwise it calls
-\fBfakeroot debian/rules clean\fP to clean the build-tree (unless
-\fB\-nc\fP is specified).
+option, it calls that target and stops here. Otherwise it runs the
+\fBpreclean\fP hook and calls \fBfakeroot debian/rules clean\fP to
+clean the build-tree (unless \fB\-nc\fP is specified).
 .IP \fB4.\fP 3
-It calls \fBdpkg\-source \-b\fP to generate the source package (unless
-a binary\-only build has been requested with \fB\-b\fP, \fB\-B\fP or
-\fB\-A\fP).
+It runs the \fBsource\fP hook and calls \fBdpkg\-source \-b\fP to generate
+the source package (unless a binary\-only build has been requested with
+\fB\-b\fP, \fB\-B\fP or \fB\-A\fP).
 .IP \fB5.\fP 3
-It calls \fBdebian/rules\fP \fIbuild-target\fP followed by
-\fBfakeroot debian/rules\fP \fIbinary-target\fP (unless a source-only
-build has been requested with \fB\-S\fP). Note that \fIbuild-target\fR
-and \fIbinary-target\fP are either \fBbuild\fP and \fBbinary\fP (default
-case, or if \fB\-b\fP is specified), or \fBbuild\-arch\fP and
-\fBbinary\-arch\fP (if \fB\-B\fP is specified), or \fBbuild\-indep\fP and
-\fBbinary\-indep\fP (if \fB\-A\fP is specified).
+It runs the \fBbuild\fP hook and calls \fBdebian/rules\fP \fIbuild-target\fP,
+then runs the \fBbinary\fP hook followed by \fBfakeroot debian/rules\fP
+\fIbinary-target\fP (unless a source-only build has been requested with
+\fB\-S\fP). Note that \fIbuild-target\fR and \fIbinary-target\fP are either
+\fBbuild\fP and \fBbinary\fP (default case, or if \fB\-b\fP is specified),
+or \fBbuild\-arch\fP and \fBbinary\-arch\fP (if \fB\-B\fP is specified),
+or \fBbuild\-indep\fP and \fBbinary\-indep\fP (if \fB\-A\fP is specified).
 .IP \fB6.\fP 3
-It calls \fBdpkg\-genchanges\fP to generate a \fB.changes\fP file.
+It runs the \fBchanges\fP hook and calls calls \fBdpkg\-genchanges\fP to
+generate a \fB.changes\fP file.
 Many \fBdpkg\-buildpackage\fP options are forwarded to
 \fBdpkg\-genchanges\fP.
 .IP \fB7.\fP 3
-If \fB\-tc\fP is specified, it will call \fBfakeroot debian/rules clean\fP
-again.
+It runs the \fBpostclean\fP and if \fB\-tc\fP is specified, it will
+call \fBfakeroot debian/rules clean\fP again.
 .IP \fB8.\fP 3
 It calls \fBdpkg\-source \-\-after\-build\fP.
 .IP \fB9.\fP 3
-It calls a package checker for the \fB.changes\fP file (if a command is
-specified in DEB_CHECK_COMMAND or with \fB\-\-check\-command\fP).
+It runs the \fBcheck\fP hook and calls a package checker for the
+\fB.changes\fP file (if a command is specified in DEB_CHECK_COMMAND or
+with \fB\-\-check\-command\fP).
 .IP \fB10.\fP 3
-It calls \fBgpg2\fP or \fBgpg\fP to sign the \fB.dsc\fP file (if any, unless
-\fB\-us\fP is specified or on UNRELEASED builds), and the \fB.changes\fP file
-(unless \fB\-uc\fP is specified or on UNRELEASED builds).
+It runs the \fBsign\fP hook and calls \fBgpg2\fP or \fBgpg\fP to sign
+the \fB.dsc\fP file (if any, unless \fB\-us\fP is specified or on UNRELEASED
+builds), and the \fB.changes\fP file (unless \fB\-uc\fP is specified or on
+UNRELEASED builds).
 .
 .SH OPTIONS
 .TP
@@ -212,6 +216,44 @@ as an argument. This command will usually be \fBlintian\fP.
 .BI \-\-check\-option= opt
 Pass option \fIopt\fP to \fIcheck-command\fP.
 .TP
+.BI \-\-hook\- hook-name = hook-command
+Set the specified shell code \fIhook-command\fP as the hook \fIhook-name\fP,
+which will run at the times specified in the run steps. The hooks will
+always be executed even if the following action is not performed (except
+for the \fBbinary\fP hook).
+
+Note: Hooks can affect the build process, and cause build failures if
+their commands fail, so watch out for unintended consequences.
+
+The current \fIhook-name\fP supported are:
+
+.B init preclean source build binary changes postclean check sign done
+
+The \fIhook-command\fP supports the following substitution format string,
+which will get applied to it before execution:
+
+.RS
+.TP
+.B %%
+A single % character.
+.TP
+.B %a
+A boolean value (0 or 1), representing whether the following action is
+being performed.
+.TP
+.B %p
+The source package name.
+.TP
+.B %v
+The source package version.
+.TP
+.B %s
+The source package version (without the epoch).
+.TP
+.B %u
+The upstream version.
+.RE
+.TP
 .BI \-p sign-command
 When \fBdpkg\-buildpackage\fP needs to execute GPG to sign a source
 control (\fB.dsc\fP) file or a \fB.changes\fP file it will run

+ 61 - 0
scripts/dpkg-buildpackage.pl

@@ -77,6 +77,10 @@ sub usage {
                  command to check the .changes file (no default).
   --check-option=<opt>
                  pass <opt> to <check-command>.
+  --hook-<hook-name>=<hook-command>
+                 set <hook-command> as the hook <hook-name>, known hooks:
+                   init preclean source build binary changes postclean
+                   check sign done
   -p<sign-command>
                  command to sign .dsc and/or .changes files
                    (default is gpg2 or gpg).
@@ -148,6 +152,11 @@ my $maint;
 my $changedby;
 my $desc;
 my @changes_opts;
+my @hook_names = qw(
+    init preclean source build binary changes postclean check sign done
+);
+my %hook;
+$hook{$_} = undef foreach @hook_names;
 
 use constant BUILD_DEFAULT    => 1;
 use constant BUILD_SOURCE     => 2;
@@ -206,6 +215,13 @@ while (@ARGV) {
 	$check_command = $1;
     } elsif (/^--check-option=(.*)$/) {
 	push @check_opts, $1;
+    } elsif (/^--hook-(.+)=(.*)$/) {
+	my ($hook_name, $hook_cmd) = ($1, $2);
+	usageerr(_g('unknown hook name %s'), $hook_name)
+	    if not exists $hook{$hook_name};
+	usageerr(_g('missing hook %s command'), $hook_name)
+	    if not defined $hook_cmd;
+	$hook{$hook_name} = $hook_cmd;
     } elsif (/^-p(.*)$/) {
 	$signcommand = $1;
     } elsif (/^-k(.*)$/) {
@@ -356,6 +372,7 @@ my ($ok, $error) = version_check($v);
 error($error) unless $ok;
 
 my $sversion = $v->as_string(omit_epoch => 1);
+my $uversion = $v->version();
 
 my $distribution = mustsetvar($changelog->{distribution}, _g('source distribution'));
 
@@ -409,6 +426,8 @@ if ($signsource && build_binaryonly) {
 # Preparation of environment stops here
 #
 
+run_hook('init', 1);
+
 if (not -x 'debian/rules') {
     warning(_g('debian/rules is not executable; fixing that'));
     chmod(0755, 'debian/rules'); # No checks of failures, non fatal
@@ -450,9 +469,14 @@ if ($call_target) {
     exit 0;
 }
 
+run_hook('preclean', ! $noclean);
+
 unless ($noclean) {
     withecho(@rootcommand, @debian_rules, 'clean');
 }
+
+run_hook('source', $include & BUILD_SOURCE);
+
 if ($include & BUILD_SOURCE) {
     warning(_g('building a source package without cleaning up as you asked; ' .
                'it might contain undesired files')) if $noclean;
@@ -461,6 +485,8 @@ if ($include & BUILD_SOURCE) {
     chdir($dir) or syserr("chdir $dir");
 }
 
+run_hook('build', $include & BUILD_BINARY);
+
 if ($buildtarget ne 'build' and scalar(@debian_rules) == 1) {
     # Verify that build-{arch,indep} are supported. If not, fallback to build.
     # This is a temporary measure to not break too many packages on a flag day.
@@ -481,9 +507,12 @@ if ($buildtarget ne 'build' and scalar(@debian_rules) == 1) {
 
 if ($include & BUILD_BINARY) {
     withecho(@debian_rules, $buildtarget);
+    run_hook('binary', 1);
     withecho(@rootcommand, @debian_rules, $binarytarget);
 }
 
+run_hook('changes', 1);
+
 push @changes_opts, "-m$maint" if defined $maint;
 push @changes_opts, "-e$changedby" if defined $changedby;
 push @changes_opts, "-v$since" if defined $since;
@@ -500,15 +529,20 @@ $changes->parse($changes_fh, _g('parse changes file'));
 $changes->save($chg);
 close $changes_fh or subprocerr(_g('dpkg-genchanges'));
 
+run_hook('postclean', $cleansource);
+
 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");
 
 printf "$Dpkg::PROGNAME: %s\n", describe_build($changes->{'Files'});
 
+run_hook('check', $check_command);
+
 if ($check_command) {
     withecho($check_command, @check_opts, $chg);
 }
@@ -518,6 +552,8 @@ if ($signpause && ($signchanges || $signsource)) {
     getc();
 }
 
+run_hook('sign', $signsource || $signchanges);
+
 if ($signsource) {
     if (signfile("$pv.dsc")) {
         error(_g('failed to sign .dsc and .changes file'));
@@ -546,6 +582,8 @@ if (not $signreleased) {
     warning(_g('not signing UNRELEASED build; use --force-sign to override'));
 }
 
+run_hook('done', 1);
+
 sub mustsetvar {
     my ($var, $text) = @_;
 
@@ -562,6 +600,29 @@ sub withecho {
 	and subprocerr("@_");
 }
 
+sub run_hook {
+    my ($name, $enabled) = @_;
+    my $cmd = $hook{$name};
+
+    return if not $cmd;
+
+    print { *STDERR } "$Dpkg::PROGNAME: running hook $name\n";
+
+    my %hook_vars = (
+        '%' => '%',
+        'a' => $enabled ? 1 : 0,
+        'p' => $pkg,
+        'v' => $version,
+        's' => $sversion,
+        'u' => $uversion,
+    );
+
+    $cmd =~ s/\%(.)/exists $hook_vars{$1} ? $hook_vars{$1} :
+        (warning(_g('unknown %% substitution in hook: %%%s'), $1), "\%$1")/eg;
+
+    withecho($cmd);
+}
+
 sub signfile {
     my ($file) = @_;
     print { *STDERR } " signfile $file\n";