Explorar o código

Dpkg::Source::Package::V3::quilt: create and use .pc directory

Instead of calling quilt to apply patches, we always apply them
by hand and we create the .pc directory used by quilt (version 2 in
.pc/.version). That way we are fully compatible with quilt and we can't
have differing behaviour whether quilt is installed or not.

Update the documentation accordingly. The option --without-quilt is gone
as is debian/patches/.dpkg-source-applied (replaced by
.pc/applied-patches).
Raphaël Hertzog %!s(int64=16) %!d(string=hai) anos
pai
achega
d881cdca55
Modificáronse 4 ficheiros con 145 adicións e 180 borrados
  1. 5 0
      debian/changelog
  2. 5 12
      man/dpkg-source.1
  3. 12 9
      scripts/Dpkg/Source/Package/V2.pm
  4. 123 159
      scripts/Dpkg/Source/Package/V3/quilt.pm

+ 5 - 0
debian/changelog

@@ -2,6 +2,11 @@ dpkg (1.15.5.4) UNRELEASED; urgency=low
 
   * Fix Dpkg::Index::get() and remove(). Thanks to Roderich Schupp
     <roderich.schupp@googlemail.com> for the patch. Closes: #558595
+  * Modify implementation of "3.0 (quilt)" source format to not be
+    behave differently depending on whether quilt is installed or not.
+    The option --without-quilt is thus gone and dpkg-source creates
+    and relies on the .pc directory to know whether patches are applied
+    or not. Closes: #557667
 
  -- Raphael Hertzog <hertzog@debian.org>  Sun, 29 Nov 2009 18:15:16 +0100
 

+ 5 - 12
man/dpkg-source.1

@@ -394,7 +394,7 @@ such options, and the build is likely to fail.
 .PP
 Similarly to quilt's default behaviour, the patches can remove files too.
 .PP
-The file \fBdebian/patches/.dpkg-source-applied\fP is created if some
+The file \fB.pc/applied-patches\fP is created if some
 patches have been applied during the extraction.
 .PP
 .B Building
@@ -428,12 +428,10 @@ This is not the case when the source tree has been obtained by unpacking a
 source package using the Format: 1.0 for instance. To mitigate the
 problem, \fBdpkg\-source\fP will apply the patches by itself if it
 believes that they have not yet been applied. To detect this situation, it
-uses the following heuristic: if a \fB.pc\fP subdirectory is detected, it
-will call \fBquilt unapplied\fP to find out if some patches are not
-applied. After that, it will take the first patch of the series (or the
-first unapplied patch returned by \fBquilt unapplied\fP, if any) and
-verify if it can be applied without errors. If the verification succeeds,
-it concludes that patches have not been applied and will apply them all.
+uses the following heuristic: it finds the list of supposedly unapplied
+patches (they are listed in the \fBseries\fP file but not in
+\fB.pc/applied-patches\fP), and if the first patch in that set can be
+applied without errors, it will apply them all.
 The option \fB\-\-no\-preparation\fP can be used to disable this
 behaviour.
 .PP
@@ -462,11 +460,6 @@ Skips extraction of the debian tarball on top of the upstream sources.
 .TP
 .B \-\-skip\-patches
 Do not apply patches at the end of the extraction.
-.TP
-.B \-\-without\-quilt
-Don't use quilt to apply patches but dpkg-source's own code. It won't be
-possible to use quilt directly on the unpacked directory but it will be
-free of quilt's temporary files as well.
 .
 .SS Format: 3.0 (custom)
 This format is particular. It doesn't represent a real source package

+ 12 - 9
scripts/Dpkg/Source/Package/V2.pm

@@ -151,7 +151,8 @@ sub do_extract {
                   @exclude_symlinks ]);
 
     # Apply patches (in a separate method as it might be overriden)
-    $self->apply_patches($newdirectory) unless $self->{'options'}{'skip_patches'};
+    $self->apply_patches($newdirectory, usage => 'unpack')
+        unless $self->{'options'}{'skip_patches'};
 }
 
 sub get_autopatch_name {
@@ -159,7 +160,8 @@ sub get_autopatch_name {
 }
 
 sub get_patches {
-    my ($self, $dir, $skip_auto) = @_;
+    my ($self, $dir, %opts) = @_;
+    $opts{"skip_auto"} = 0 unless defined($opts{"skip_auto"});
     my @patches;
     my $pd = "$dir/debian/patches";
     my $auto_patch = $self->get_autopatch_name();
@@ -168,7 +170,7 @@ sub get_patches {
         foreach my $patch (sort readdir(DIR)) {
             # patches match same rules as run-parts
             next unless $patch =~ /^[\w-]+$/ and -f "$pd/$patch";
-            next if $skip_auto and $patch eq $auto_patch;
+            next if $opts{"skip_auto"} and $patch eq $auto_patch;
             push @patches, $patch;
         }
         closedir(DIR);
@@ -177,15 +179,16 @@ sub get_patches {
 }
 
 sub apply_patches {
-    my ($self, $dir, $skip_auto) = @_;
-    my @patches = $self->get_patches($dir, $skip_auto);
+    my ($self, $dir, %opts) = @_;
+    $opts{"skip_auto"} = 0 unless defined($opts{"skip_auto"});
+    my @patches = $self->get_patches($dir, %opts);
     return unless scalar(@patches);
     my $timestamp = time();
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
     open(APPLIED, '>', $applied) || syserr(_g("cannot write %s"), $applied);
-    foreach my $patch ($self->get_patches($dir, $skip_auto)) {
+    foreach my $patch ($self->get_patches($dir, %opts)) {
         my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
-        info(_g("applying %s"), $patch) unless $skip_auto;
+        info(_g("applying %s"), $patch) unless $opts{"skip_auto"};
         my $patch_obj = Dpkg::Source::Patch->new(filename => $path);
         $patch_obj->apply($dir, force_timestamp => 1,
                           timestamp => $timestamp,
@@ -220,7 +223,7 @@ sub check_patches_applied {
     my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
     unless (-e $applied) {
         warning(_g("patches have not been applied, applying them now (use --no-preparation to override)"));
-        $self->apply_patches($dir);
+        $self->apply_patches($dir, usage => 'preparation');
     }
 }
 
@@ -287,7 +290,7 @@ sub do_build {
     subprocerr(_g("copy of the debian directory")) if $?;
 
     # Apply all patches except the last automatic one
-    $self->apply_patches($tmp, 1);
+    $self->apply_patches($tmp, skip_auto => 1, usage => 'build');
 
     # Prepare handling of binary files
     my %auth_bin_files;

+ 123 - 159
scripts/Dpkg/Source/Package/V3/quilt.pm

@@ -1,4 +1,4 @@
-# Copyright © 2008 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2008-2009 Raphaël Hertzog <hertzog@debian.org>
 #
 # 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
@@ -25,6 +25,7 @@ use Dpkg;
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling;
 use Dpkg::Source::Patch;
+use Dpkg::Source::Functions qw(erasedir);
 use Dpkg::IPC;
 use Dpkg::Vendor qw(get_current_vendor run_vendor_hook);
 use Dpkg::Control;
@@ -40,18 +41,11 @@ our $CURRENT_MINOR_VERSION = "0";
 sub init_options {
     my ($self) = @_;
     $self->SUPER::init_options();
-    # By default use quilt, unless it's not available
-    $self->{'options'}{'without_quilt'} = (-x "/usr/bin/quilt") ? 0 : 1
-        unless exists $self->{'options'}{'without_quilt'};
 }
 
 sub parse_cmdline_option {
     my ($self, $opt) = @_;
     return 1 if $self->SUPER::parse_cmdline_option($opt);
-    if ($opt =~ /^--without-quilt$/) {
-        $self->{'options'}{'without_quilt'} = 1;
-        return 1;
-    }
     return 0;
 }
 
@@ -89,63 +83,80 @@ sub get_series_file {
     return undef;
 }
 
-sub get_patches {
-    my ($self, $dir, $skip_auto) = @_;
+sub read_patch_list {
+    my ($self, $file, %opts) = @_;
+    return () if not defined $file or not -f $file;
+    $opts{"warn_options"} = 0 unless defined($opts{"warn_options"});
+    $opts{"skip_auto"} = 0 unless defined($opts{"skip_auto"});
     my @patches;
     my $auto_patch = $self->get_autopatch_name();
-    my $series = $self->get_series_file($dir);
-    if (defined($series)) {
-        open(SERIES, "<" , $series) || syserr(_g("cannot read %s"), $series);
-        while(defined($_ = <SERIES>)) {
-            chomp; s/^\s+//; s/\s+$//; # Strip leading/trailing spaces
-            s/(^|\s+)#.*$//; # Strip comment
-            next unless $_;
-            if (/^(\S+)\s+(.*)$/) {
-                $_ = $1;
-                if ($2 ne '-p1') {
-                    warning(_g("the series file (%s) contains unsupported " .
-                               "options ('%s', line %s), dpkg-source might " .
-                               "fail when applying patches."),
-                            $series, $2, $.) unless $skip_auto;
-                }
+    open(SERIES, "<" , $file) || syserr(_g("cannot read %s"), $file);
+    while(defined($_ = <SERIES>)) {
+        chomp; s/^\s+//; s/\s+$//; # Strip leading/trailing spaces
+        s/(^|\s+)#.*$//; # Strip comment
+        next unless $_;
+        if (/^(\S+)\s+(.*)$/) {
+            $_ = $1;
+            if ($2 ne '-p1') {
+                warning(_g("the series file (%s) contains unsupported " .
+                           "options ('%s', line %s), dpkg-source might " .
+                           "fail when applying patches."),
+                        $file, $2, $.) if $opts{"warn_options"};
             }
-            next if $skip_auto and $_ eq $auto_patch;
-            push @patches, $_;
         }
-        close(SERIES);
+        next if $opts{"skip_auto"} and $_ eq $auto_patch;
+        push @patches, $_;
     }
+    close(SERIES);
     return @patches;
 }
 
-sub run_quilt {
-    my ($self, $dir, $params, %more_opts) = @_;
-    $params = [ $params ] unless ref($params) eq "ARRAY";
-    my $absdir = $dir;
-    unless (File::Spec->file_name_is_absolute($absdir)) {
-        $absdir = File::Spec->rel2abs($dir);
+sub create_quilt_db {
+    my ($self, $dir) = @_;
+    my $db_dir = File::Spec->catdir($dir, ".pc");
+    if (not -d $db_dir) {
+        mkdir $db_dir or syserr(_g("cannot mkdir %s"), $db_dir);
     }
-    my $series = $self->get_series_file($dir);
-    # Use default name if no series files exist yet
-    $series = "$absdir/debian/patches/series" unless defined $series;
-    unless (File::Spec->file_name_is_absolute($series)) {
-        $series = File::Spec->rel2abs($series);
+    my $version_file = File::Spec->catfile($db_dir, ".version");
+    if (not -e $version_file) {
+        open(VERSION, ">", $version_file);
+        print VERSION "2\n";
+        close(VERSION);
     }
-    my %opts = (
-        env => { QUILT_PATCHES => "$absdir/debian/patches",
-                 QUILT_SERIES => $series,
-                 # Kept as close as possible to default patch options in
-                 # Dpkg::Source::Patch (used in without_quilt mode)
-                 QUILT_PATCH_OPTS => "-t -F 0 -N -u -V never -g0" },
-        'chdir' => $dir,
-        'exec' => [ 'quilt', '--quiltrc', '/dev/null', @$params ],
-        %more_opts
-    );
-    my $pid = fork_and_exec(%opts);
-    return $pid;
+}
+
+sub apply_quilt_patch {
+    my ($self, $dir, $patch, %opts) = @_;
+    $opts{"verbose"} = 0 unless defined($opts{"verbose"});
+    $opts{"timestamp"} = time() unless defined($opts{"timestamp"});
+    my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
+    my $obj = Dpkg::Source::Patch->new(filename => $path);
+
+    info(_g("applying %s"), $patch) if $opts{"verbose"};
+    $obj->apply($dir, timestamp => $opts{"timestamp"},
+                force_timestamp => 1, create_dirs => 1, remove_backup => 0,
+                options => [ '-s', '-t', '-F', '0', '-N', '-p1', '-u',
+                             '-V', 'never', '-g0', '-E', '-b',
+                             '-B', ".pc/$patch/" ]);
+}
+
+sub get_patches {
+    my ($self, $dir, %opts) = @_;
+    my $series = $self->get_series_file($dir);
+    return $self->read_patch_list($series, %opts);
 }
 
 sub apply_patches {
-    my ($self, $dir, $skip_auto) = @_;
+    my ($self, $dir, %opts) = @_;
+
+    if ($opts{'usage'} eq 'unpack') {
+        $opts{'verbose'} = 1;
+    } elsif ($opts{'usage'} eq 'build') {
+        $opts{'warn_options'} = 1;
+        $opts{'verbose'} = 0;
+    }
+
+    my $patches = $opts{"patches"};
 
     # Update debian/patches/series symlink if needed to allow quilt usage
     my $series = $self->get_series_file($dir);
@@ -159,45 +170,22 @@ sub apply_patches {
                 syserr(_g("can't create symlink %s"), $dest);
         }
     }
-    my @patches = $self->get_patches($dir, $skip_auto);
-    return unless scalar(@patches);
 
-    # Apply patches
-    my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
-    open(APPLIED, '>', $applied) || syserr(_g("cannot write %s"), $applied);
-    my $now = time();
-    my $pobj = {};
-    my $panalysis = {};
-    foreach my $patch (@patches) {
-        my $path = File::Spec->catfile($dir, "debian", "patches", $patch);
-        $pobj->{$patch} = Dpkg::Source::Patch->new(filename => $path);
-        if ($self->{'options'}{'without_quilt'}) {
-            info(_g("applying %s"), $patch) unless $skip_auto;
-            $pobj->{$patch}->apply($dir, timestamp => $now,
-                    force_timestamp => 1, create_dirs => 1,
-                    add_options => [ '-E' ]);
-            print APPLIED "$patch\n";
-        } else {
-            $panalysis->{$patch} = $pobj->{$patch}->analyze($dir);
-            foreach my $dir (keys %{$panalysis->{$patch}->{'dirtocreate'}}) {
-                eval { mkpath($dir); };
-                syserr(_g("cannot create directory %s"), $dir) if $@;
-            }
-        }
+    unless (defined($patches)) {
+        $patches = [ $self->get_patches($dir, %opts) ];
     }
-    if (not $self->{'options'}{'without_quilt'}) {
-        my %opts;
-        $opts{"to_file"} = "/dev/null" if $skip_auto;
-        info(_g("applying all patches with %s"), "quilt push -q " . $patches[-1]) unless $skip_auto;
-        $self->run_quilt($dir, ['push', '-q', $patches[-1]],
-                         wait_child => 1, %opts);
-        foreach my $patch (@patches) {
-            foreach my $fn (keys %{$panalysis->{$patch}->{'filepatched'}}) {
-                utime($now, $now, $fn) || $! == ENOENT ||
-                    syserr(_g("cannot change timestamp for %s"), $fn);
-            }
-            print APPLIED "$patch\n";
-        }
+    return unless scalar(@$patches);
+
+    # Apply patches
+    $self->create_quilt_db($dir);
+    my $pc_applied = File::Spec->catfile($dir, ".pc", "applied-patches");
+    my @applied = $self->read_patch_list($pc_applied);
+    my @patches = $self->read_patch_list($self->get_series_file($dir));
+    open(APPLIED, '>>', $pc_applied) || syserr(_g("cannot write %s"), $pc_applied);
+    $opts{"timestamp"} = time();
+    foreach my $patch (@$patches) {
+        $self->apply_quilt_patch($dir, $patch, %opts);
+        print APPLIED "$patch\n";
     }
     close(APPLIED);
 }
@@ -210,7 +198,6 @@ sub prepare_build {
     # stamp file created by ourselves
     my $func = sub {
         return 1 if $_[0] =~ m{^debian/patches/series$} and -l $_[0];
-        return 1 if $_[0] =~ m{^debian/patches/.dpkg-source-applied$};
         return 1 if $_[0] =~ /^.pc(\/|$)/;
         return 1 if $_[0] =~ /$self->{'options'}{'diff_ignore_regexp'}/;
         return 0;
@@ -220,97 +207,74 @@ sub prepare_build {
 
 sub check_patches_applied {
     my ($self, $dir) = @_;
-    my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
-    my $auto_patch = $self->get_autopatch_name();
-    my @patches ;
-    # First we try to get a list of patches that are probably not napplied
-    if (not $self->{'options'}{'without_quilt'}) {
-        my $pipe;
-        my $pid = $self->run_quilt($dir, ['unapplied'], error_to_file => '/dev/null',
-                                   to_pipe => \$pipe);
-        @patches = map { chomp; $_ } (<$pipe>);
-        close ($pipe) || syserr("close on 'quilt unapplied' pipe");
-        wait_child($pid, cmdline => "quilt unapplied", nocheck => 1);
-        subprocerr("quilt unapplied") unless WIFEXITED($?);
-    } else {
-        @patches = $self->get_patches($dir);
+    my $pc_applied = File::Spec->catfile($dir, ".pc", "applied-patches");
+    my @applied = $self->read_patch_list($pc_applied);
+    my @patches = $self->read_patch_list($self->get_series_file($dir));
+    my @to_apply;
+    foreach my $patch (@patches) {
+        next if scalar grep { $_ eq $patch } @applied;
+        push @to_apply, $patch;
     }
-    # Then we check if it's applicable, and if yes, we make the
-    # assumption that patches are not applied and need to be applied
-    if (scalar(@patches)) {
-        my $first_patch = File::Spec->catfile($dir, "debian", "patches", $patches[0]);
+    if (scalar(@to_apply)) {
+        my $first_patch = File::Spec->catfile($dir, "debian", "patches",
+                                              $to_apply[0]);
         my $patch_obj = Dpkg::Source::Patch->new(filename => $first_patch);
         if ($patch_obj->check_apply($dir)) {
-            warning(_g("patches have not been applied, applying them now (use --no-preparation to override)"));
-            $self->apply_patches($dir);
+            warning(_g("patches have not been applied, applying them now " .
+                       "(use --no-preparation to override)"));
+            $self->apply_patches($dir, usage => 'preparation', verbose => 1,
+                                 patches => \@to_apply);
         }
     }
 }
 
 sub register_autopatch {
     my ($self, $dir) = @_;
+
+    sub add_line {
+        my ($file, $line) = @_;
+        open(FILE, ">>", $file) || syserr(_g("cannot write %s"), $file);
+        print FILE "$line\n";
+        close(FILE);
+    }
+
+    sub drop_line {
+        my ($file, $re) = @_;
+        open(FILE, "<", $file) || syserr(_g("cannot read %s"), $file);
+        my @lines = <FILE>;
+        close(FILE);
+        open(FILE, ">", $file) || syserr(_g("cannot write %s"), $file);
+        print(FILE $_) foreach grep { not /^\Q$re\E\s*$/ } @lines;
+        close(FILE);
+    }
+
     my $auto_patch = $self->get_autopatch_name();
     my @patches = $self->get_patches($dir);
     my $has_patch = (grep { $_ eq $auto_patch } @patches) ? 1 : 0;
     my $series = $self->get_series_file($dir);
     $series ||= File::Spec->catfile($dir, "debian", "patches", "series");
-    my $applied = File::Spec->catfile($dir, "debian", "patches", ".dpkg-source-applied");
+    my $applied = File::Spec->catfile($dir, ".pc", "applied-patches");
     my $patch = File::Spec->catfile($dir, "debian", "patches", $auto_patch);
-    my $absdir = $dir;
-    unless (File::Spec->file_name_is_absolute($absdir)) {
-        $absdir = File::Spec->rel2abs($dir);
-    }
+
     if (-e $patch) {
         # Add auto_patch to series file
         if (not $has_patch) {
-            # Use quilt to register only if it's wanted/available AND :
-            # - either we have patches and quilt has been used (.pc dir exists)
-            # - or we don't have patches, hence quilt couldn't be used
-            if ((-d "$dir/.pc" or not scalar(@patches)) and
-                not $self->{'options'}{'without_quilt'})
-            {
-                # Registering the new patch with quilt requires some
-                # trickery: reverse-apply the patch, create a new quilt patch,
-                # fold the patch into the quilt-managed one
-                my $patch_obj = Dpkg::Source::Patch->new(filename => $patch);
-                $patch_obj->apply($dir, add_options => ['-R', '-E']);
-                $self->run_quilt($dir, ['new', "$auto_patch"],
-                                 wait_child => 1, to_file => '/dev/null');
-                $self->run_quilt($dir, ['fold'],
-                                 from_file => "$absdir/debian/patches/$auto_patch",
-                                 wait_child => 1, to_file => '/dev/null');
-            } else {
-                open(SERIES, ">>", $series) || syserr(_g("cannot write %s"), $series);
-                print SERIES "$auto_patch\n";
-                close(SERIES);
-            }
-        } else {
-            # If quilt was used, ensure its meta-information are
-            # synchronized with the updated patch
-            if (-d "$dir/.pc" and not $self->{'options'}{'without_quilt'}) {
-                # Some trickery needed: reverse-apply the patch, fold the
-                # new patch into the quilt-managed one
-                my $patch_obj = Dpkg::Source::Patch->new(filename => $patch);
-                $patch_obj->apply($dir, add_options => ['-R', '-E']);
-                $self->run_quilt($dir, ['fold'],
-                                 from_file => "$absdir/debian/patches/$auto_patch",
-                                 wait_child => 1, to_file => '/dev/null');
-            }
+            add_line($series, $auto_patch);
+            add_line($applied, $auto_patch);
         }
+        # Ensure quilt meta-data are created and in sync with some trickery:
+        # reverse-apply the patch, drop .pc/$patch, re-apply it
+        # with the correct options to recreate the backup files
+        my $patch_obj = Dpkg::Source::Patch->new(filename => $patch);
+        $patch_obj->apply($dir, add_options => ['-R', '-E']);
+        erasedir(File::Spec->catdir($dir, ".pc", $auto_patch));
+        $self->apply_quilt_patch($dir, $auto_patch);
     } else {
         # Remove auto_patch from series
         if ($has_patch) {
-            if ($self->{'options'}{'without_quilt'}) {
-                open(SERIES, "<", $series) || syserr(_g("cannot read %s"), $series);
-                my @lines = <SERIES>;
-                close(SERIES);
-                open(SERIES, ">", $series) || syserr(_g("cannot write %s"), $series);
-                print(SERIES $_) foreach grep { not /^\Q$auto_patch\E\s*$/ } @lines;
-                close(SERIES);
-            } else {
-                $self->run_quilt($dir, ['delete', $auto_patch],
-                                 wait_child => 1, to_file => '/dev/null');
-            }
+            drop_line($series, $auto_patch);
+            drop_line($applied, $auto_patch);
+            erasedir(File::Spec->catdir($dir, ".pc", $auto_patch));
         }
         # Clean up empty series
         unlink($series) if not -s $series;