On Thu, 08 Jun 2006, Raphael Hertzog wrote:
> This dh_python will use python-central for any binary package with a
> XB-Python-Version: field. There's no optional python-support either.
> 
> So anyone unhappy with python-central should really come up with another
> dh_python implementation RSN.

OK I discussed with Josselin and Matthias and came up with the attached
dh_python (patch attached as well).

This dh_python depends neither on python-central nor on python-support but it
can work with both.

It will use python-central if it appears in a build-dependency.
It will use python-support if it detects /usr/share/python-support/$package.

If the XS-Python-Version field is present, it will work following the new
policy otherwise it should follow the old policy.

At least that's the theory. I did some tests on two recent packages with
python-central and python-support. I'd like people to try to recompile
some actual python-related packages modules with that dh_python and check
if it's really enough backwards compatible.

I think we'll be able to move forward when this dh_python is integrated in
debhelper.

Cheers,
-- 
Raphaël Hertzog

Premier livre français sur Debian GNU/Linux :
http://www.ouaza.com/livre/admin-debian/
--- dh_python.orig      2006-06-08 10:57:59.000000000 +0200
+++ dh_python   2006-06-09 04:07:56.000000000 +0200
@@ -21,7 +21,42 @@
 will also add a postinst and a prerm script if required.
 
 The program will look at python scripts and modules in your package, and
-will use this information to generate a dependency on python, with the
+will use this information to generate adequate dependencies. There is two
+scenarios: if the package uses the XS-Python-Version field then
+the new policy will be applied, otherwise the old policy will be used.
+
+If dh_python detects "python-central" in one of the build-dependencies,
+then it will automatically use it: it will be added in the dependencies
+and corresponding postinst/postrm scripts are generated.
+
+If dh_python detects that you have a directory
+/usr/share/python-support/$PACKAGE, then it will automatically generate the
+required postinst/postrm scripts.
+
+=head2 New policy
+
+The XS-Python-Version field (on the source package) defines which Python
+versions are supported by the package. It can be "all", "current",
+"current, >= X.Y", a single version ("2.3") or a list of versions with
+optional comparison operators (ex: "2.3, 2.4" or ">= 2.2, << 2.5" or 
+">= 2.4").
+
+The binary packages should have a "XB-Python-Version: ${python:Versions}"
+field and dh_python will generate the right substvar for that. The
+resulting value can be "all" for public modules which work with all python
+versions, "current" for private modules which are always byte-compiled
+with the current python version or a list of of all versions for which the
+extensions have been compiled (ex: "2.3, 2.4"). The dependencies are
+adjusted accordingly as well.
+
+Packages with public extensions should also have a "Provides:
+${python:Provides}" field. The corresponding substvar will indicate
+"pythonX.Y-foo, pythonX.Z-foo" according to all the extensions
+effectively available in the package.
+
+=head2 Old policy
+
+It looks at scripts and modules in your package and adds a dependency on 
python, with the
 current major version, or on pythonX.Y if your scripts or modules need a
 specific python version. The dependency will be substituted into your
 package's control file wherever you place the token "${python:Depends}".
@@ -32,6 +67,8 @@
 
 If you use this program, your package should build-depend on python.
 
+Note: in the old policy, /usr/lib/site-python is also scanned for modules.
+
 =head1 OPTIONS
 
 =over 4
@@ -40,11 +77,10 @@
 
 If your package installs python modules in non-standard directories, you
 can make dh_python check those directories by passing their names on the
-command line. By default, it will check /usr/lib/site-python,
-/usr/lib/$PACKAGE, /usr/share/$PACKAGE, /usr/lib/games/$PACKAGE,
+command line. By default, it will check /usr/lib/$PACKAGE, 
/usr/share/$PACKAGE, /usr/lib/games/$PACKAGE,
 /usr/share/games/$PACKAGE and /usr/lib/python?.?/site-packages.
 
-Note: only /usr/lib/site-python, /usr/lib/python?.?/site-packages and the
+Note: only /usr/lib/python?.?/site-packages and the
 extra names on the command line are searched for binary (.so) modules.
 
 =item B<-V> I<version>
@@ -53,6 +89,9 @@
 pythonX.Y version, you can use this option to specify the desired version,
 such as 2.3. Do not use if you ship modules in /usr/lib/site-python.
 
+With the new policy, this option is mostly deprecated. Use the
+XS-Python-Field to indicate that you're using a specific python version.
+
 =item B<-n>, B<--noscripts>
 
 Do not modify postinst/postrm scripts.
@@ -85,10 +124,10 @@
 }
 
 # The next python version
-my $python_nextversion = $python_version + 0.1;
+my $python_nextversion = next_minor_version($python_version);
 my $python_nextmajor = $python_major + 1;
 
-my @python_allversions = ('1.5','2.1','2.2','2.3','2.4');
+my @python_allversions = ('1.5','2.1','2.2','2.3','2.4','2.5');
 foreach (@python_allversions) {
        s/^/python/;
 }
@@ -109,6 +148,19 @@
        s#^/##;
 }
 
+# Check if we have an XS-Python-Version and extract it from the control
+# file, also check if we use python-central
+my $python_header = "";
+my $use_python_central = 0;
+{
+       local $/ = ""; # Grab until empty line
+       open(CONTROL, "debian/control"); # Can't fail, dh_testdir has already 
been called
+       my $source = <CONTROL>;
+       $python_header = $1 if ($source =~ m/XS-Python-Version: \s*(.*)$/m);
+       chomp($python_header);
+       $use_python_central = 1 if ($source =~ m/Build-Depends(-Indep)?: 
.*\bpython-central\b$/m);
+}
+
 # dependency types
 use constant PROGRAM   => 1;
 use constant PY_MODULE => 2;
@@ -120,29 +172,46 @@
        my $tmp = tmpdir($package);
 
        delsubstvar($package, "python:Depends");
+       delsubstvar($package, "python:Provides");
+       delsubstvar($package, "python:Versions");
 
-       my @dirs = ("usr/lib/site-python", "usr/lib/$package", 
"usr/share/$package", "usr/lib/games/$package", "usr/share/games/$package", 
@ARGV );
-       my @dirs_so = ("usr/lib/site-python", @ARGV );
+       my @dirs = ("usr/lib/$package", "usr/share/$package", 
"usr/lib/games/$package", "usr/share/games/$package", @ARGV );
+       my @dirs_so = (@ARGV);
 
        my $dep_on_python = 0;
        my $strong_dep = 0;
-       my $look_for_pythonXY = 1;
+
+       # Fail early if the package use usr/lib/site-python
+       if (-d "$tmp/usr/lib/site-python") {
+               if ($python_header) {
+                       # Error only on new policy
+                       error("The package $package puts files in 
/usr/lib/site-python: forbidden by policy");
+               } else {
+                       # Old policy allowed that directory, so scan it
+                       push @dirs, "usr/lib/site-python";
+                       push @dirs_so, "usr/lib/site-python";
+               }
+       }
+
+       if ($use_python_central) {
+               # Python-central will move files, that's why it's called
+               # so early in the loop
+               doit("pycentral debhelper --stdout $package $tmp >/dev/null");
+       }
 
        # First, the case of python-foo and pythonX.Y-foo
        if ($package =~ /^python-/) {
                $dep_on_python = 1;
                $strong_dep = 1;
+               # This adds a dependency to python<current>-foo in python-foo
+               # This is old policy but is done only if pythonX.Y-foo exists
+               # it does no harm and assure backwards compatibility
                my $pack = $package;
                $pack =~ s/^python/python$python_version/;
                if (grep { "$_" eq "$pack" } getpackages()) {
                        addsubstvar($package, "python:Depends", $pack);
                }
        }
-       if ($package !~ /^python[0-9].[0-9]-/) {
-               push @dirs, "usr/lib/$usepython/site-packages";
-               push @dirs_so, "usr/lib/$usepython/site-packages";
-               $look_for_pythonXY = 0;
-       }
 
        @dirs = grep -d, map "$tmp/$_", @dirs;
        @dirs_so = grep -d, map "$tmp/$_", @dirs_so;
@@ -202,8 +271,7 @@
                        if ($has_module) {
                                if ($dh{V_FLAG_SET}) {
                                        $verdeps{$usepython} |= 
SO_MODULE_NONSTANDARD;
-                               }
-                               else {
+                               } else {
                                        $deps |= SO_MODULE;
                                }
                        }
@@ -212,9 +280,10 @@
 
        # Dependencies on current python
        $dep_on_python = 1 if $deps;
-       $strong_dep = 1 if($deps & (PY_MODULE|SO_MODULE));
+       $strong_dep = 1 if ($deps & (PY_MODULE|SO_MODULE));
 
-       if ($dep_on_python) {
+       if ($dep_on_python and not $python_header) {
+               # Generate dependencies following old policy
                addsubstvar($package, "python:Depends", $python, ">= 
$python_version");
                if ($strong_dep) {
                        addsubstvar($package, "python:Depends", $python, "<< 
$python_nextversion");
@@ -223,35 +292,54 @@
                }
        }
 
+       # At this point we have scanned everything for executables scripts
+       # and directories for private modules. 
+
        my $need_prerm = 0;
 
        # Look for specific pythonX.Y modules
+       my %pyversions_found;
        foreach my $pyver (@python_allversions) {
                my $pydir="/usr/lib/$pyver/site-packages";
-               if ($look_for_pythonXY) {
-                       if (grep -d,"$tmp$pydir") {
-                               find sub {
-                                       return unless -f;
-                                       if (/\.py$/) {
-                                               $verdeps{$pyver} |= PY_MODULE;
-                                               doit(("rm","-f",$_."c",$_."o"));
-                                       }
-                                       $verdeps{$pyver} |= SO_MODULE if 
/\.so$/;
-                               }, "$tmp$pydir";
-                       }
+               if (grep -d,"$tmp$pydir") {
+                       find sub {
+                               return unless -f;
+                               if (/\.py$/) {
+                                       $verdeps{$pyver} |= PY_MODULE;
+                                       doit(("rm","-f",$_."c",$_."o"));
+                               }
+                               $verdeps{$pyver} |= SO_MODULE if /\.so$/;
+                       }, "$tmp$pydir";
                }
        
+               if ($verdeps{$pyver} & (PY_MODULE|SO_MODULE)) {
+                   # Remember that files specific to this version have
+                   # been found
+                   $pyver =~ /^python([\d\.]+)/ and $pyversions_found{$1} = 1;
+               }
+
+               # Stop here when using new policy
+               next if $python_header;
+
                # Go for the dependencies
                addsubstvar($package, "python:Depends", $pyver) if 
$verdeps{$pyver};
 
                # And now, the postinst and prerm stuff
+               # It takes care of byte-compiling modules
                if ($pyver eq "$usepython") {
+                       # Current version of python, also byte-compile the
+                       # private directories (stored in $dirlist)
                        if ($verdeps{$pyver} & PY_MODULE) {
                                $pydir = $pydir.$dirlist;
                        } else {
+                               # Only private directories
                                $pydir = $dirlist;
                        }
                        $verdeps{$pyver} |= PY_MODULE if($deps & PY_MODULE);
+               } else {
+                       # Non-current python versions: we only have to
+                       # byte-compile stuff in /usr/lib/python2.X
+                       # Leave $pydir as is
                }
                if ($verdeps{$pyver} & (PY_MODULE|PY_MODULE_NONSTANDARD) && ! 
$dh{NOSCRIPTS}) {
                        
autoscript($package,"postinst","postinst-python","s%#PYVER#%$pyver%;s%#DIRLIST#%$pydir%");
@@ -261,6 +349,173 @@
        if ($need_prerm && ! $dh{NOSCRIPTS}) {
                
autoscript($package,"prerm","prerm-python","s%#PACKAGE#%$package%");
        }
+
+       if ($python_header) {
+               # New policy: generate the depends to accept all python
+               # versions that this package effectively provides
+               my $min_version = $python_version;
+               my $stop_version = "";
+               my $versions_field = "";
+               
+               # Reset again, analysis using new policy follows
+               $dep_on_python = 0;
+
+               # Package supporting only the current version
+               if ($python_header =~ /^current(?:,\s*>=\s*([\d\.]+))/) {
+                       if (defined $1) {
+                               $min_version = $1;
+                       }
+                       $versions_field = "current";
+               }
+
+               # Private extensions, must be rebuilt for each python version
+               if ($deps & SO_MODULE) {
+                       $dep_on_python = 1;
+                       $stop_version = next_minor_version($python_version);
+                       # Packages using a private extension can only
+                       # support one version and they indicate which one
+                       # in XS-Python-Version
+                       if ($versions_field eq "current") {
+                               $versions_field = $python_version;
+                       }
+               } 
+
+               # Private modules (already handled by python-central)
+               if ($deps & PY_MODULE) {
+                       if (! $use_python_central) {
+                               # Use python-support to ensure that it's always
+                               # byte-compiled for the current version
+                               mkdir("$tmp/usr/share/python-support");
+                               $dirlist =~ s/^\s*//;
+                               $dirlist =~ s/\s*$//;
+                               open(DIRLIST, "> 
$tmp/usr/share/python-support/$package.dirs") ||
+                                   error("Can't create 
$tmp/usr/share/python-support/$package.dirs: $!");
+                               print DIRLIST join("\n", split(/\s+/, 
$dirlist));
+                               close(DIRLIST);
+                               $versions_field = "current" unless 
$versions_field;
+                       }
+               }
+
+               # Python scripts
+               if ($deps & PROGRAM) {
+                       $dep_on_python = 1;
+               }
+               
+               # Private extensions compiled with non-current python
+               # or scripts using non-current python
+               foreach my $pyver (keys %verdeps) {
+                       if ($verdeps{$pyver} & (SO_MODULE_NONSTANDARD|PROGRAM)) 
{
+                               addsubstvar($package, "python:Depends", $pyver);
+                       }
+               }
+
+               # Public extensions
+               if (scalar keys %pyversions_found) {
+                       # Extensions will always trigger this (as well as public
+                       # modules not handled by python-support/python-central)
+                       $dep_on_python = 1;
+                       $min_version = min(keys %pyversions_found);
+                       unless ($stop_version) {
+                           my $max_version = max(keys %pyversions_found);
+                           $stop_version = next_minor_version($max_version);
+                       }
+                       # Generate the Python-Version field
+                       foreach (keys %pyversions_found) {
+                               addsubstvar($package, "python:Versions", $_);
+                       }
+                       # Generate provides for the python2.X-foo packages that 
we emulate
+                       if ($package =~ /^python-/) {
+                               foreach (keys %pyversions_found) {
+                                       my $virtual = $package;
+                                       $virtual =~ s/^python-/$python$_-/;
+                                       addsubstvar($package, 
"python:Provides", $virtual);
+                               }
+                       }
+               } else {
+                       # Still try to describe the versions that the package 
support
+                       $versions_field = "all" unless $versions_field;
+                       addsubstvar($package, "python:Versions", 
$versions_field);
+               }
+
+               if ($dep_on_python) {
+                       # At least a script has been detected
+                       addsubstvar($package, "python:Depends", $python, ">= 
$min_version");
+                       # If a stronger dependency is needed
+                       addsubstvar($package, "python:Depends", $python, "<< 
$stop_version") if $stop_version;
+               }
+       
+       }
+
+       # Let's check for python-support
+       if (-d "$tmp/usr/share/python-support/$package") {
+               # Handle public modules
+               my $ps_dir = "/usr/share/python-support/$package";
+               addsubstvar($package, "python:Depends", "python-support");
+               if (! $dh{NOSCRIPTS}) {
+                       autoscript($package, "postinst", 
"postinst-python-support", "s,#OPTIONS#,-i,;s,#DIRS#,$ps_dir,");
+                       autoscript($package, "prerm", "prerm-python-support", 
"s,#OPTIONS#,-i,;s,#DIRS#,$ps_dir,");
+               }
+               # TODO: we should generate
+               # /usr/share/python-support/$package/version from
+               # $python_header
+       }
+       if (-f "$tmp/usr/share/python-support/$package.dirs") {
+               # Handle private modules
+               my $ps_dir = "/usr/share/python-support/$package.dirs";
+               addsubstvar($package, "python:Depends", "python-support");
+               if (! $dh{NOSCRIPTS}) {
+                       autoscript($package, "postinst", 
"postinst-python-support", "s,#OPTIONS#,-b,;s,#DIRS#,$ps_dir,");
+                       autoscript($package, "prerm", "prerm-python-support", 
"s,#OPTIONS#,-b,;s,#DIRS#,$ps_dir,");
+               }
+       }
+       
+       # Let's check for python-central
+       if ($use_python_central and ($deps & (PY_MODULE|SO_MODULE))) {
+               addsubstvar($package, "python:Depends", "python-central");
+               if (! $dh{NOSCRIPTS}) {
+                       
autoscript($package,"postinst","postinst-pycentral","s%#PACKAGE#%$package%");
+                       
autoscript($package,"prerm","prerm-pycentral","s%#PACKAGE#%$package%");
+               }
+       }
+
+}
+
+sub next_minor_version {
+    my $version = shift;
+    # Handles 2.10 -> 2.11 gracefully
+    my @items = split(/\./, $version);
+    $items[1] += 1;
+    $version = join(".", @items);
+    return $version;
+}
+
+sub compare_version {
+    my ($a, $b) = @_;
+    my @A = split(/\./, $a);
+    my @B = split(/\./, $b);
+    my $diff = 0;
+    for (my $i = 0; $i <= $#A; $i++) {
+       $diff = $A[$i] - $B[$i];
+       return $diff if $diff; 
+    }
+    # They are the same
+    return 0;
+}
+
+sub max {
+    my $max = shift;
+    foreach (@_) {
+       $max = $_ if (compare_version($_, $max) > 0);
+    }
+    return $max;
+}
+
+sub min {
+    my $min = shift;
+    foreach (@_) {
+       $min = $_ if (compare_version($_, $min) < 0);
+    }
+    return $min;
 }
 
 =head1 SEE ALSO
@@ -269,9 +524,10 @@
 
 This program is a part of debhelper.
 
-=head1 AUTHOR
+=head1 AUTHORS
 
 Josselin Mouette <[EMAIL PROTECTED]>
+Raphael Hertzog <[EMAIL PROTECTED]>
 
 most ideas stolen from Brendan O'Dea <[EMAIL PROTECTED]>
 
#!/usr/bin/perl -w

=head1 NAME

dh_python - calculates python dependencies and adds postinst and prerm python 
scripts

=cut

use strict;
use File::Find;
use Debian::Debhelper::Dh_Lib;

=head1 SYNOPSIS

B<dh_python> [S<I<debhelper options>>] [B<-n>] [B<-V> I<version>] [S<I<module 
dirs ...>>]

=head1 DESCRIPTION

dh_python is a debhelper program that is responsible for generating the
${python:Depends} substitutions and adding them to substvars files. It
will also add a postinst and a prerm script if required.

The program will look at python scripts and modules in your package, and
will use this information to generate adequate dependencies. There is two
scenarios: if the package uses the XS-Python-Version field then
the new policy will be applied, otherwise the old policy will be used.

If dh_python detects "python-central" in one of the build-dependencies,
then it will automatically use it: it will be added in the dependencies
and corresponding postinst/postrm scripts are generated.

If dh_python detects that you have a directory
/usr/share/python-support/$PACKAGE, then it will automatically generate the
required postinst/postrm scripts.

=head2 New policy

The XS-Python-Version field (on the source package) defines which Python
versions are supported by the package. It can be "all", "current",
"current, >= X.Y", a single version ("2.3") or a list of versions with
optional comparison operators (ex: "2.3, 2.4" or ">= 2.2, << 2.5" or 
">= 2.4").

The binary packages should have a "XB-Python-Version: ${python:Versions}"
field and dh_python will generate the right substvar for that. The
resulting value can be "all" for public modules which work with all python
versions, "current" for private modules which are always byte-compiled
with the current python version or a list of of all versions for which the
extensions have been compiled (ex: "2.3, 2.4"). The dependencies are
adjusted accordingly as well.

Packages with public extensions should also have a "Provides:
${python:Provides}" field. The corresponding substvar will indicate
"pythonX.Y-foo, pythonX.Z-foo" according to all the extensions
effectively available in the package.

=head2 Old policy

It looks at scripts and modules in your package and adds a dependency on 
python, with the
current major version, or on pythonX.Y if your scripts or modules need a
specific python version. The dependency will be substituted into your
package's control file wherever you place the token "${python:Depends}".

If some modules need to be byte-compiled at install time, appropriate
postinst and prerm scripts will be generated. If already byte-compiled
modules are found, they are removed.

If you use this program, your package should build-depend on python.

Note: in the old policy, /usr/lib/site-python is also scanned for modules.

=head1 OPTIONS

=over 4

=item I<module dirs>

If your package installs python modules in non-standard directories, you
can make dh_python check those directories by passing their names on the
command line. By default, it will check /usr/lib/$PACKAGE, /usr/share/$PACKAGE, 
/usr/lib/games/$PACKAGE,
/usr/share/games/$PACKAGE and /usr/lib/python?.?/site-packages.

Note: only /usr/lib/python?.?/site-packages and the
extra names on the command line are searched for binary (.so) modules.

=item B<-V> I<version>

If the .py files your package ships are meant to be used by a specific
pythonX.Y version, you can use this option to specify the desired version,
such as 2.3. Do not use if you ship modules in /usr/lib/site-python.

With the new policy, this option is mostly deprecated. Use the
XS-Python-Field to indicate that you're using a specific python version.

=item B<-n>, B<--noscripts>

Do not modify postinst/postrm scripts.

=back

=head1 CONFORMS TO

Debian policy, version 3.5.7

Python policy, version 0.3.7

=cut

init();

my $python = 'python';

# The current python major version
my $python_major;
my $python_version = `$python -V 2>&1`;
if (! defined $python_version || $python_version eq "") {
        error("Python is not installed, aborting. (Probably forgot to 
Build-Depend on python.)");
}
elsif ($python_version =~ m/^Python\s+(\d+)\.(\d+)(\.\d+)*/) {
        $python_version = "$1.$2" ;
        $python_major = $1 ;
} else { 
        error("Unable to parse python version out of \"$python_version\".");
}

# The next python version
my $python_nextversion = next_minor_version($python_version);
my $python_nextmajor = $python_major + 1;

my @python_allversions = ('1.5','2.1','2.2','2.3','2.4','2.5');
foreach (@python_allversions) {
        s/^/python/;
}

# Check for -V
my $usepython = "python$python_version";
if($dh{V_FLAG_SET}) {
        $usepython = $dh{V_FLAG};
        $usepython =~ s/^/python/;
        if (! grep { $_ eq $usepython } @python_allversions) {
                error("Unknown python version $dh{V_FLAG}");
        }
}

# Cleaning the paths given on the command line
foreach (@ARGV) {
        s#/$##;
        s#^/##;
}

# Check if we have an XS-Python-Version and extract it from the control
# file, also check if we use python-central
my $python_header = "";
my $use_python_central = 0;
{
        local $/ = ""; # Grab until empty line
        open(CONTROL, "debian/control"); # Can't fail, dh_testdir has already 
been called
        my $source = <CONTROL>;
        $python_header = $1 if ($source =~ m/XS-Python-Version: \s*(.*)$/m);
        chomp($python_header);
        $use_python_central = 1 if ($source =~ m/Build-Depends(-Indep)?: 
.*\bpython-central\b$/m);
}

# dependency types
use constant PROGRAM   => 1;
use constant PY_MODULE => 2;
use constant PY_MODULE_NONSTANDARD => 4;
use constant SO_MODULE => 8;
use constant SO_MODULE_NONSTANDARD => 16;

foreach my $package (@{$dh{DOPACKAGES}}) {
        my $tmp = tmpdir($package);

        delsubstvar($package, "python:Depends");
        delsubstvar($package, "python:Provides");
        delsubstvar($package, "python:Versions");

        my @dirs = ("usr/lib/$package", "usr/share/$package", 
"usr/lib/games/$package", "usr/share/games/$package", @ARGV );
        my @dirs_so = (@ARGV);

        my $dep_on_python = 0;
        my $strong_dep = 0;

        # Fail early if the package use usr/lib/site-python
        if (-d "$tmp/usr/lib/site-python") {
                if ($python_header) {
                        # Error only on new policy
                        error("The package $package puts files in 
/usr/lib/site-python: forbidden by policy");
                } else {
                        # Old policy allowed that directory, so scan it
                        push @dirs, "usr/lib/site-python";
                        push @dirs_so, "usr/lib/site-python";
                }
        }

        if ($use_python_central) {
                # Python-central will move files, that's why it's called
                # so early in the loop
                doit("pycentral debhelper --stdout $package $tmp >/dev/null");
        }

        # First, the case of python-foo and pythonX.Y-foo
        if ($package =~ /^python-/) {
                $dep_on_python = 1;
                $strong_dep = 1;
                # This adds a dependency to python<current>-foo in python-foo
                # This is old policy but is done only if pythonX.Y-foo exists
                # it does no harm and assure backwards compatibility
                my $pack = $package;
                $pack =~ s/^python/python$python_version/;
                if (grep { "$_" eq "$pack" } getpackages()) {
                        addsubstvar($package, "python:Depends", $pack);
                }
        }

        @dirs = grep -d, map "$tmp/$_", @dirs;
        @dirs_so = grep -d, map "$tmp/$_", @dirs_so;

        my $deps = 0;
        my %verdeps = ();
        foreach (@python_allversions) {
                $verdeps{$_} = 0;
        }

        # Find scripts
        find sub {
                return unless -f and (-x or /\.py$/);
                local *F;
                return unless open F, $_;
                if (read F, local $_, 32 and 
m%^#!\s*/usr/bin/(env\s+)?(python(\d+\.\d+)?)\s%) {
                        if ( "python" eq $2 ) {
                                $deps |= PROGRAM;
                        } elsif(defined $verdeps{$2}) {
                                $verdeps{$2} |= PROGRAM;
                        }
                }
                close F;
        }, $tmp;

        # Look for python modules
        my $dirlist="";
        if (@dirs) {
                foreach my $curdir (@dirs) {
                        my $has_module = 0;
                        $curdir =~ s%^$tmp/%%;
                        find sub {
                                return unless -f;
                                if (/\.py$/) {
                                        $has_module = 1;
                                        doit(("rm","-f",$_."c",$_."o"));
                                }
                        }, "$tmp/$curdir" ;
                        if ($has_module) {
                                if ($dh{V_FLAG_SET}) {
                                        $verdeps{$usepython} |= 
PY_MODULE_NONSTANDARD;
                                } else {
                                        $deps |= PY_MODULE;
                                }
                                $dirlist="$dirlist /$curdir";
                        }
                }
        }
        if (@dirs_so) {
                foreach my $curdir (@dirs_so) {
                        my $has_module = 0;
                        $curdir =~ s%^$tmp/%%;
                        find sub {
                                return unless -f;
                                $has_module = 1 if /\.so$/;
                        }, "$tmp/$curdir" ;
                        if ($has_module) {
                                if ($dh{V_FLAG_SET}) {
                                        $verdeps{$usepython} |= 
SO_MODULE_NONSTANDARD;
                                } else {
                                        $deps |= SO_MODULE;
                                }
                        }
                }
        }

        # Dependencies on current python
        $dep_on_python = 1 if $deps;
        $strong_dep = 1 if ($deps & (PY_MODULE|SO_MODULE));

        if ($dep_on_python and not $python_header) {
                # Generate dependencies following old policy
                addsubstvar($package, "python:Depends", $python, ">= 
$python_version");
                if ($strong_dep) {
                        addsubstvar($package, "python:Depends", $python, "<< 
$python_nextversion");
                } else {
                        addsubstvar($package, "python:Depends", $python, "<< 
$python_nextmajor");
                }
        }

        # At this point we have scanned everything for executables scripts
        # and directories for private modules. 

        my $need_prerm = 0;

        # Look for specific pythonX.Y modules
        my %pyversions_found;
        foreach my $pyver (@python_allversions) {
                my $pydir="/usr/lib/$pyver/site-packages";
                if (grep -d,"$tmp$pydir") {
                        find sub {
                                return unless -f;
                                if (/\.py$/) {
                                        $verdeps{$pyver} |= PY_MODULE;
                                        doit(("rm","-f",$_."c",$_."o"));
                                }
                                $verdeps{$pyver} |= SO_MODULE if /\.so$/;
                        }, "$tmp$pydir";
                }
        
                if ($verdeps{$pyver} & (PY_MODULE|SO_MODULE)) {
                    # Remember that files specific to this version have
                    # been found
                    $pyver =~ /^python([\d\.]+)/ and $pyversions_found{$1} = 1;
                }

                # Stop here when using new policy
                next if $python_header;

                # Go for the dependencies
                addsubstvar($package, "python:Depends", $pyver) if 
$verdeps{$pyver};

                # And now, the postinst and prerm stuff
                # It takes care of byte-compiling modules
                if ($pyver eq "$usepython") {
                        # Current version of python, also byte-compile the
                        # private directories (stored in $dirlist)
                        if ($verdeps{$pyver} & PY_MODULE) {
                                $pydir = $pydir.$dirlist;
                        } else {
                                # Only private directories
                                $pydir = $dirlist;
                        }
                        $verdeps{$pyver} |= PY_MODULE if($deps & PY_MODULE);
                } else {
                        # Non-current python versions: we only have to
                        # byte-compile stuff in /usr/lib/python2.X
                        # Leave $pydir as is
                }
                if ($verdeps{$pyver} & (PY_MODULE|PY_MODULE_NONSTANDARD) && ! 
$dh{NOSCRIPTS}) {
                        
autoscript($package,"postinst","postinst-python","s%#PYVER#%$pyver%;s%#DIRLIST#%$pydir%");
                        $need_prerm = 1;
                }
        }
        if ($need_prerm && ! $dh{NOSCRIPTS}) {
                
autoscript($package,"prerm","prerm-python","s%#PACKAGE#%$package%");
        }

        if ($python_header) {
                # New policy: generate the depends to accept all python
                # versions that this package effectively provides
                my $min_version = $python_version;
                my $stop_version = "";
                my $versions_field = "";
                
                # Reset again, analysis using new policy follows
                $dep_on_python = 0;

                # Package supporting only the current version
                if ($python_header =~ /^current(?:,\s*>=\s*([\d\.]+))/) {
                        if (defined $1) {
                                $min_version = $1;
                        }
                        $versions_field = "current";
                }

                # Private extensions, must be rebuilt for each python version
                if ($deps & SO_MODULE) {
                        $dep_on_python = 1;
                        $stop_version = next_minor_version($python_version);
                        # Packages using a private extension can only
                        # support one version and they indicate which one
                        # in XS-Python-Version
                        if ($versions_field eq "current") {
                                $versions_field = $python_version;
                        }
                } 

                # Private modules (already handled by python-central)
                if ($deps & PY_MODULE) {
                        if (! $use_python_central) {
                                # Use python-support to ensure that it's always
                                # byte-compiled for the current version
                                mkdir("$tmp/usr/share/python-support");
                                $dirlist =~ s/^\s*//;
                                $dirlist =~ s/\s*$//;
                                open(DIRLIST, "> 
$tmp/usr/share/python-support/$package.dirs") ||
                                    error("Can't create 
$tmp/usr/share/python-support/$package.dirs: $!");
                                print DIRLIST join("\n", split(/\s+/, 
$dirlist));
                                close(DIRLIST);
                                $versions_field = "current" unless 
$versions_field;
                        }
                }

                # Python scripts
                if ($deps & PROGRAM) {
                        $dep_on_python = 1;
                }
                
                # Private extensions compiled with non-current python
                # or scripts using non-current python
                foreach my $pyver (keys %verdeps) {
                        if ($verdeps{$pyver} & (SO_MODULE_NONSTANDARD|PROGRAM)) 
{
                                addsubstvar($package, "python:Depends", $pyver);
                        }
                }

                # Public extensions
                if (scalar keys %pyversions_found) {
                        # Extensions will always trigger this (as well as public
                        # modules not handled by python-support/python-central)
                        $dep_on_python = 1;
                        $min_version = min(keys %pyversions_found);
                        unless ($stop_version) {
                            my $max_version = max(keys %pyversions_found);
                            $stop_version = next_minor_version($max_version);
                        }
                        # Generate the Python-Version field
                        foreach (keys %pyversions_found) {
                                addsubstvar($package, "python:Versions", $_);
                        }
                        # Generate provides for the python2.X-foo packages that 
we emulate
                        if ($package =~ /^python-/) {
                                foreach (keys %pyversions_found) {
                                        my $virtual = $package;
                                        $virtual =~ s/^python-/$python$_-/;
                                        addsubstvar($package, 
"python:Provides", $virtual);
                                }
                        }
                } else {
                        # Still try to describe the versions that the package 
support
                        $versions_field = "all" unless $versions_field;
                        addsubstvar($package, "python:Versions", 
$versions_field);
                }

                if ($dep_on_python) {
                        # At least a script has been detected
                        addsubstvar($package, "python:Depends", $python, ">= 
$min_version");
                        # If a stronger dependency is needed
                        addsubstvar($package, "python:Depends", $python, "<< 
$stop_version") if $stop_version;
                }
        
        }

        # Let's check for python-support
        if (-d "$tmp/usr/share/python-support/$package") {
                # Handle public modules
                my $ps_dir = "/usr/share/python-support/$package";
                addsubstvar($package, "python:Depends", "python-support");
                if (! $dh{NOSCRIPTS}) {
                        autoscript($package, "postinst", 
"postinst-python-support", "s,#OPTIONS#,-i,;s,#DIRS#,$ps_dir,");
                        autoscript($package, "prerm", "prerm-python-support", 
"s,#OPTIONS#,-i,;s,#DIRS#,$ps_dir,");
                }
                # TODO: we should generate
                # /usr/share/python-support/$package/version from
                # $python_header
        }
        if (-f "$tmp/usr/share/python-support/$package.dirs") {
                # Handle private modules
                my $ps_dir = "/usr/share/python-support/$package.dirs";
                addsubstvar($package, "python:Depends", "python-support");
                if (! $dh{NOSCRIPTS}) {
                        autoscript($package, "postinst", 
"postinst-python-support", "s,#OPTIONS#,-b,;s,#DIRS#,$ps_dir,");
                        autoscript($package, "prerm", "prerm-python-support", 
"s,#OPTIONS#,-b,;s,#DIRS#,$ps_dir,");
                }
        }
        
        # Let's check for python-central
        if ($use_python_central and ($deps & (PY_MODULE|SO_MODULE))) {
                addsubstvar($package, "python:Depends", "python-central");
                if (! $dh{NOSCRIPTS}) {
                        
autoscript($package,"postinst","postinst-pycentral","s%#PACKAGE#%$package%");
                        
autoscript($package,"prerm","prerm-pycentral","s%#PACKAGE#%$package%");
                }
        }

}

sub next_minor_version {
    my $version = shift;
    # Handles 2.10 -> 2.11 gracefully
    my @items = split(/\./, $version);
    $items[1] += 1;
    $version = join(".", @items);
    return $version;
}

sub compare_version {
    my ($a, $b) = @_;
    my @A = split(/\./, $a);
    my @B = split(/\./, $b);
    my $diff = 0;
    for (my $i = 0; $i <= $#A; $i++) {
        $diff = $A[$i] - $B[$i];
        return $diff if $diff; 
    }
    # They are the same
    return 0;
}

sub max {
    my $max = shift;
    foreach (@_) {
        $max = $_ if (compare_version($_, $max) > 0);
    }
    return $max;
}

sub min {
    my $min = shift;
    foreach (@_) {
        $min = $_ if (compare_version($_, $min) < 0);
    }
    return $min;
}

=head1 SEE ALSO

L<debhelper(7)>

This program is a part of debhelper.

=head1 AUTHORS

Josselin Mouette <[EMAIL PROTECTED]>
Raphael Hertzog <[EMAIL PROTECTED]>

most ideas stolen from Brendan O'Dea <[EMAIL PROTECTED]>

=cut
if [ "$1" = "configure" ] && which update-python-modules >/dev/null 2>&1; then
        update-python-modules #OPTIONS# #DIRS#
fi
if which update-python-modules >/dev/null 2>&1; then
        update-python-modules -c #OPTIONS# #DIRS#
fi
if which pycentral >/dev/null 2>&1; then
        pycentral pkginstall #PACKAGE#
fi
if which python >/dev/null 2>&1 && which pycentral >/dev/null 2>&1; then
        pycentral pkgremove #PACKAGE#
else
        flist=$(tempfile)
        find /usr/share/pycentral/#PACKAGE# -depth -mindepth 2 \
          \( -name '*.py' -printf 'p %P\n' -o -printf '%y %P\n' \) \
          > $flist || true
        if [ -s $flist ]; then
            for d in /usr/lib/python[0-9].[0-9]; do
                while read t n; do
                    case "$t" in
                        p) rm -f $d/$n $d/${n}[co];;
                        d) rmdir --ignore-fail-on-non-empty $d/$n 2>/dev/null;;
                        *) rm -f $d/$n
                    esac
                done < $flist
            done
        fi
        rm -f $flist
        dpkg -L #PACKAGE# \
            | awk '/\/usr\/share\/pycentral/ {next} /\.py$/ {print $0"c\n" 
$0"o"}' \
            | xargs rm -f >&2
fi

Reply via email to