Hi, I've uploaded a NMU 5.0.37.3 in DELAYED/1-day fixing several issues concerning dh_python:
* Update of dh_python - when buidling for a non-standard Python version, generate more reasonable Depends like "python (>= X.Y) | pythonX.Y" Closes: #375576 - fix handling of private extensions. Closes: #375948 - fix parsing of XS-Python-Version, it didn't work if only fixed versions were listed in XS-Python-Version. - fix use of unitialized value. Closes: #374776 - fix typos in POD documentation. Closes: #375936 Please find attached the cumulative patch of the last 3 uploads (5.0.37 -> 5.0.37.3). You can also find a copy of the package here: http://people.debian.org/~hertzog/python/ http://people.debian.org/~hertzog/python/debhelper_5.0.37.3_all.deb http://people.debian.org/~hertzog/python/debhelper_5.0.37.3.dsc Dear debian-python readers, please test this updated dh_python and report any problem to me. Cheers, -- Raphaël Hertzog Premier livre français sur Debian GNU/Linux : http://www.ouaza.com/livre/admin-debian/
diff -Nru debhelper-5.0.37/debian/changelog debhelper-5.0.37.3/debian/changelog --- debhelper-5.0.37/debian/changelog 2006-06-12 03:33:28.000000000 +0200 +++ debhelper-5.0.37.3/debian/changelog 2006-07-10 13:27:50.000000000 +0200 @@ -1,3 +1,52 @@ +debhelper (5.0.37.3) unstable; urgency=low + + * Non-maintainer upload. + * Update of dh_python + - when buidling for a non-standard Python version, generate more + reasonable Depends like "python (>= X.Y) | pythonX.Y" + Closes: #375576 + - fix handling of private extensions. Closes: #375948 + - fix parsing of XS-Python-Version, it didn't work if only fixed versions + were listed in XS-Python-Version. + - fix use of unitialized value. Closes: #374776 + - fix typos in POD documentation. Closes: #375936 + + -- Raphael Hertzog <[EMAIL PROTECTED]> Mon, 10 Jul 2006 13:20:06 +0200 + +debhelper (5.0.37.2) unstable; urgency=low + + * Non-maintainer upload. + * Update of dh_python + - vastly refactored, easier to understand, and the difference + between old policy and new policy is easier to grasp + - it supports an -X option which can be used to not scan some files + - uses debian/pyversions as reference source of information for + dependencies but also parse the XS-Python-Version header as fallback. + - ${python:Versions}'s default value is XS-Python-Version's value + instead of "all" when the package doesn't depend on a + specific python version. Closes: #373853 + - always generate ${python:Provides} and leave the responsibility to the + maintainer to not use ${python:Provides} if he doesn't want the + provides. + - uses debian/pycompat or DH_PYCOMPAT as reference field to run in new + policy mode. The presence of XS-Python-Version will also trigger the + new policy mode (this is for short-term compatibility, it may be removed in + the not too-distant future). + DH_PYCOMPAT=1 is the default mode and is compatible to the old policy. + DH_PYCOMPAT=2 is the new mode and is compatible with the new policy. + * Use "grep ^Version:" instead of "grep Version:" on the output of + dpkg-parsechangelog since the above changelog entry matched "Version:" and + thus made the build fail. + + -- Raphael Hertzog <[EMAIL PROTECTED]> Sat, 17 Jun 2006 20:44:29 +0200 + +debhelper (5.0.37.1) unstable; urgency=low + + * Non-maintainer upload. + * Integrate the new dh_python implementing the new Python policy. Closes: #370833 + + -- Raphael Hertzog <[EMAIL PROTECTED]> Mon, 12 Jun 2006 08:58:22 +0200 + debhelper (5.0.37) unstable; urgency=low * dh_installmodules: depmod -a is no longer run during boot, so if a module diff -Nru debhelper-5.0.37/debian/rules debhelper-5.0.37.3/debian/rules --- debhelper-5.0.37/debian/rules 2006-06-03 23:06:41.000000000 +0200 +++ debhelper-5.0.37.3/debian/rules 2006-06-19 18:31:06.000000000 +0200 @@ -26,7 +26,7 @@ }' # Figure out the `current debhelper version. -VERSION=$(shell expr "`dpkg-parsechangelog |grep Version:`" : '.*Version: \(.*\)') +VERSION=$(shell expr "`dpkg-parsechangelog |grep ^Version:`" : '.*Version: \(.*\)') PERLLIBDIR=$(shell perl -MConfig -e 'print $$Config{vendorlib}') diff -Nru debhelper-5.0.37/dh_python debhelper-5.0.37.3/dh_python --- debhelper-5.0.37/dh_python 2006-04-24 22:09:12.000000000 +0200 +++ debhelper-5.0.37.3/dh_python 2006-07-10 14:14:53.000000000 +0200 @@ -12,7 +12,7 @@ =head1 SYNOPSIS -B<dh_python> [S<I<debhelper options>>] [B<-n>] [B<-V> I<version>] [S<I<module dirs ...>>] +B<dh_python> [S<I<debhelper options>>] [B<-n>] [B<-X>I<item>] [B<-V> I<version>] [S<I<module dirs ...>>] =head1 DESCRIPTION @@ -21,7 +21,40 @@ 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. Additionally, +it will also use debian/pyversions as a list of python versions that the +package is known to support. That file contains a single line with a +comma delimited list of python versions in the format "X.Y". You can +specify ranges of python versions with "W.X-Y.Z". You can omit one side of +the range like in "X.Y-" or "-X.Y" which will respectively indicate all +python versions greater or equal to "X.Y" or smaller or equal to "X.Y". + +In order to support two different Python policies, dh_python looks for +an environment variable DH_PYCOMPAT or the file debian/pycompat. The +content of those is a single number representing the compatibility mode +that will be used by dh_python. This number can be 1 or 2. + +In both modes, the ${python:Depends} variable contains appropriate +dependencies on python or pythonX.Y. + +=head2 Compatibility level 2 (new policy) + +dh_python will only generate substitution variables, it won't take care of +the byte-compilation of modules any more. + +The ${python:Provides} variable will contain versioned provides of the +package (if the package's name starts with "python-"). A python-foo +package could provide "python2.3-foo" and "python2.4-foo" at the same +time. Python extensions have to provide those whereas it's only option +for pure python modules. + +The ${python:Versions} variable can be used to provide an optional +XB-Python-Version field listing the python versions supported by the +package. + +=head2 Compatibility level 1 (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 +65,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 +75,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,17 +87,26 @@ 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. +=item B<-X>I<item>, B<--exclude=>I<item> + +Exclude files that contain "item" anywhere in their filename from being +taken into account to generate the python dependency. You may use this +option multiple times to build up a list of things to exclude. + =back =head1 CONFORMS TO -Debian policy, version 3.5.7 +Debian policy, version 3.7.2 -Python policy, version 0.3.7 +Python policy, version 0.4.1 (2006-06-20) =cut @@ -85,10 +128,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,39 +152,96 @@ s#^/##; } +# Check the compatibilty mode to use +my $compat_mode = ""; +my $pyversions_field = ""; +my $python_header = ""; +{ + local $/ = ""; # Grab until empty line + open(CONTROL, "debian/control"); # Can't fail, dh_testdir has already been called + my $source = <CONTROL>; + close(CONTROL); + if ($source =~ m/^XS-Python-Version: \s*(.*)$/m) { + $python_header = $1; + chomp($python_header); + $pyversions_field = convert_python_header($python_header); + $compat_mode = 2 unless $compat_mode; # + } +} +if (defined($ENV{DH_PYCOMPAT})) { + $compat_mode = $ENV{DH_PYCOMPAT}; +} elsif (-e "debian/pycompat") { + open (COMPAT_IN, "debian/pycompat") || error "debian/pycompat: $!"; + my $l=<COMPAT_IN>; + chomp $l; + if ($l) { + $compat_mode = $l + } else { + warning("debian/pycompat is empty, assuming dh_python compatibility level 1"); + } +} +$compat_mode = 1 unless $compat_mode; # Default to old policy +error("Maximum dh_python compatibility mode supported is 2.") if $compat_mode > 2; + +# pyversions describes the list of python versions that this package can +# work with +if (-e "debian/pyversions") { + open(PYVERS, "debian/pyversions") || error("Can't open debian/pyversions: $!"); + $pyversions_field = <PYVERS>; + chomp($pyversions_field); + close(PYVERS); +} +verbose_print("Pyversions field: $pyversions_field"); + +# Extract a list of officially supported Python versions +my %officially_supported; +if (-e "/usr/share/python/debian_defaults") { + open(DEFAULTS, "/usr/share/python/debian_defaults") || + error("Can't open /usr/share/python/debian_defaults: $!"); + foreach (<DEFAULTS>) { + if (/^supported-versions\s*=\s*(.*)$/) { + my $supported = $1; + foreach my $version (split(/\s+/, $supported)) { + if ($version =~ /python([\d\.]+)/) { + $officially_supported{$1} = 1; + } + } + last; + } + } + close(DEFAULTS); +} + # 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; +use constant PY_PRIVATE_MODULE => 2; +use constant PY_PUBLIC_MODULE => 4; +use constant PY_OFFICIAL_MODULE => 8; +use constant PY_UNKNOWN_MODULE => 16; +use constant SO_PRIVATE_MODULE => 32; +use constant SO_PUBLIC_MODULE => 64; +use constant SO_OFFICIAL_MODULE => 128; +use constant SO_UNKNOWN_MODULE => 256; foreach my $package (@{$dh{DOPACKAGES}}) { my $tmp = tmpdir($package); - delsubstvar($package, "python:Depends"); - - 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; - # First, the case of python-foo and pythonX.Y-foo - if ($package =~ /^python-/) { - $dep_on_python = 1; - $strong_dep = 1; - 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; + # Fail early if the package use usr/lib/site-python + if (-d "$tmp/usr/lib/site-python") { + if ($compat_mode >= 2) { + # 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"; + } } @dirs = grep -d, map "$tmp/$_", @dirs; @@ -153,114 +253,514 @@ $verdeps{$_} = 0; } - # Find scripts + # Global scan + my $private_pydirs_regex = join('|', map { "\Q$_\E" } @dirs); + my $private_sodirs_regex = join('|', map { "\Q$_\E" } @dirs_so); + my %private_dirs_list; + my %pyversions_found; 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; + return unless -f; + # See if we were asked to exclude this file. + # Note that we have to test on the full filename, including directory. + my $fn="$File::Find::dir/$_"; + if (excludefile($fn)) { + verbose_print("Ignoring $fn"); + return; + } + # Find scripts + if (-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%) { + verbose_print("Found program using $2: $fn"); + if ( "python" eq $2 ) { + $deps |= PROGRAM; + } elsif(defined $verdeps{$2}) { + $verdeps{$2} |= PROGRAM; + } } + close F; } - close F; - }, $tmp; + # Continue only with .py or .so + return unless (/\.py$/ or /\.so$/); - # 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")); + # Remove any byte-compiled file + doit(("rm","-f",$_."c",$_."o")) if /\.py$/; + + # Classify the file in the right category + if (/\.py$/ and $private_pydirs_regex and $fn =~ m/(?:$private_pydirs_regex)/) { + # Private python module + verbose_print("Found private module: $fn"); + my $dir; + foreach $dir (@dirs) { + if ($fn =~ m/\Q$dir\E/) { + $dir =~ s/^$tmp//; + verbose_print("Memorizing dir to byte-compile: $dir"); + $private_dirs_list{"$dir"} = 1; + } + } + if ($dh{V_FLAG_SET}) { + $verdeps{$usepython} |= PY_PRIVATE_MODULE; + } else { + $deps |= PY_PRIVATE_MODULE; + } + } elsif (/\.so$/ and $private_sodirs_regex and $fn =~ m/(?:$private_sodirs_regex)/) { + # Private python extension + verbose_print("Found private extension: $fn"); + if ($dh{V_FLAG_SET}) { + $verdeps{$usepython} |= SO_PRIVATE_MODULE; + } else { + $deps |= SO_PRIVATE_MODULE; + } + } elsif ($fn =~ m|$tmp/usr/lib/python([\d\.]+)/site-packages/|) { + $pyversions_found{$1} = 1; + my $v = $1; + if (/\.py$/) { + verbose_print("Found public module: $fn"); + $verdeps{"python$v"} |= PY_PUBLIC_MODULE; + } else { + verbose_print("Found public extension: $fn"); + $verdeps{"python$v"} |= SO_PUBLIC_MODULE; + } + } elsif ($fn =~ m|$tmp/usr/lib/python([\d\.]+)/|) { + $pyversions_found{$1} = 1; + my $v = $1; + if (/\.py$/) { + verbose_print("Found official module: $fn"); + $verdeps{"python$v"} |= PY_OFFICIAL_MODULE; + } else { + verbose_print("Found official extension: $fn"); + $verdeps{"python$v"} |= SO_OFFICIAL_MODULE; + } + } elsif ($fn =~ m|$tmp/usr/lib/python-support/[^/]+/python([\d\.]+)/|) { + $pyversions_found{$1} = 1; + my $v = $1; + if (/\.py$/) { + verbose_print("Found public module: $fn"); + $verdeps{"python$v"} |= PY_PUBLIC_MODULE; + } else { + verbose_print("Found public extension: $fn"); + $verdeps{"python$v"} |= SO_PUBLIC_MODULE; + } + } elsif ($fn =~ m{$tmp(?:/usr/share/pycentral/|/usr/share/python-support/)}) { + if (/\.py$/) { + verbose_print("Found public module: $fn"); + $deps |= PY_PUBLIC_MODULE; + } # No extensions here + } elsif ($fn =~ m|$tmp/usr/lib/python([\d\.]+)/site-packages/|) { + + } elsif ($fn =~ m|$tmp/usr/share/doc/|) { + # Ignore .py files in doc directory + } else { + # Unknown pyfiles + if (/\.py$/) { + verbose_print("Found unclassified module: $fn"); + if ($dh{V_FLAG_SET}) { + $verdeps{$usepython} |= PY_UNKNOWN_MODULE; + } else { + $deps |= PY_UNKNOWN_MODULE; } - }, "$tmp/$curdir" ; - if ($has_module) { + } else { + verbose_print("Found unclassified extension: $fn"); if ($dh{V_FLAG_SET}) { - $verdeps{$usepython} |= PY_MODULE_NONSTANDARD; + $verdeps{$usepython} |= SO_UNKNOWN_MODULE; } else { - $deps |= PY_MODULE; + $deps |= SO_UNKNOWN_MODULE; } - $dirlist="$dirlist /$curdir"; } } + }, $tmp; + + # Common dependency handling + foreach my $pyver (keys %verdeps) { + # Always add pythonX.Y dependency if a script uses that interpreter + if ($verdeps{$pyver} & PROGRAM) { + addsubstvar($package, "python:Depends", $pyver); + } + # Always add pythonX.Y dependency if some private modules are + # byte-compiled with it (or if extensions are + # byte-compiled with it) + if ($verdeps{$pyver} & (PY_PRIVATE_MODULE|SO_PRIVATE_MODULE)) { + addsubstvar($package, "python:Depends", $pyver); + } } - 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; + + if ($compat_mode == 2) { + # + # NEW POLICY + # + # Generate the depends to accept all python + # versions that this package effectively provides + my ($min_version, $max_version, @versions) = analyze_pyversions($pyversions_field); + my $stop_version = ""; + $stop_version = next_minor_version($max_version) if $max_version; + my $versions_field = ""; + verbose_print("Pyversions analysis gives: min=$min_version, max=$max_version (@versions)"); + + # Reset again, analysis using new policy follows + $dep_on_python = 0; + + # Private extensions, must be rebuilt for each python version + if ($deps & SO_PRIVATE_MODULE) { + $dep_on_python++; + # Packages using a private extension can only + # support one version + # Unless min/max are the same we put $python_version + if ($min_version and ($min_version eq $max_version)) { + $versions_field = $min_version; + } else { + if (compare_version($min_version, $python_version) > 0) { + # The current version is unsupported, use the min version instead + $versions_field = $min_version; + } else { + # Use the current version by default + $versions_field = $python_version; + $min_version = $python_version; } - else { - $deps |= SO_MODULE; + $stop_version = next_minor_version($versions_field); + } + } + + # Private modules + if ($deps & PY_PRIVATE_MODULE) { + # Package with private modules can only support one version at a time + # (even if the modules can be automatically byte-compiled for any new + # python version). + unless ($versions_field) { + # Unless min/max are the same we put "current" + if ($min_version and ($min_version eq $max_version)) { + $versions_field = $min_version; + } else { + $versions_field = "current"; } } } - } - - # Dependencies on current python - $dep_on_python = 1 if $deps; - $strong_dep = 1 if($deps & (PY_MODULE|SO_MODULE)); - if ($dep_on_python) { - addsubstvar($package, "python:Depends", $python, ">= $python_version"); - if ($strong_dep) { - addsubstvar($package, "python:Depends", $python, "<< $python_nextversion"); + # Python scripts & public modules + if ($deps & (PROGRAM|PY_PUBLIC_MODULE)) { + $dep_on_python++; + } + + # 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++; + if (scalar grep { $python_version eq $_ } keys %pyversions_found) { + # Current versions is supported by the package + # It's safe to depends on the corresponding + # interval of python versions + $min_version = min(keys %pyversions_found); + unless ($stop_version) { + $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 { - addsubstvar($package, "python:Depends", $python, "<< $python_nextmajor"); + # Still try to describe the versions that the package support + $versions_field = $python_header unless $versions_field; + $versions_field = "all" unless $versions_field; + addsubstvar($package, "python:Versions", $versions_field); + + # Generate provides for all python versions supported + if ($package =~ /^python-/) { + foreach (grep { $officially_supported{$_} } @versions) { + my $virtual = $package; + $virtual =~ s/^python-/$python$_-/; + addsubstvar($package, "python:Provides", $virtual); + } + } + } + + if ($dep_on_python) { + # At least a script has been detected + if ($min_version) { + if (compare_version($min_version, $python_version) <= 0 ) { + # Min-version is less or equal to current version + addsubstvar($package, "python:Depends", $python, ">= $min_version"); + } else { + # Min version is greater to current version + # Supposition: new stuff working only with the latest python, + # depend on that specific python version + addsubstvar($package, "python:Depends", + "python (>= $min_version) | python$min_version"); + } + } + # If a stronger dependency is needed + if ($stop_version) { + if (compare_version($stop_version, $python_version) > 0 ) { + # Stop version is strictly bigger than current version + addsubstvar($package, "python:Depends", $python, "<< $stop_version"); + } else { + # Only works with old versions, + # package is mostly deprecated, + # no need for a python dependency + } + } + # Let's depend on python anyway + addsubstvar($package, "python:Depends", $python) unless ($min_version or $stop_version); } - } - my $need_prerm = 0; + } elsif ($compat_mode == 1) { + # + # OLD POLICY + # + + # 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 + my $pack = $package; + $pack =~ s/^python/python$python_version/; + if (grep { "$_" eq "$pack" } getpackages()) { + addsubstvar($package, "python:Depends", $pack); + } + } - # Look for specific pythonX.Y modules - 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"; - } - } - - # Go for the dependencies - addsubstvar($package, "python:Depends", $pyver) if $verdeps{$pyver}; - - # And now, the postinst and prerm stuff - if ($pyver eq "$usepython") { - if ($verdeps{$pyver} & PY_MODULE) { - $pydir = $pydir.$dirlist; + # Dependencies on current python + $dep_on_python = 1 if ($deps & + (PROGRAM|PY_PUBLIC_MODULE|SO_PUBLIC_MODULE|PY_PRIVATE_MODULE|SO_PRIVATE_MODULE)); + $strong_dep = 1 if ($deps & (PY_PUBLIC_MODULE|SO_PUBLIC_MODULE)); + + # Add python dependencies + if ($dep_on_python) { + addsubstvar($package, "python:Depends", $python, ">= $python_version"); + if ($strong_dep) { + addsubstvar($package, "python:Depends", $python, "<< $python_nextversion"); } else { - $pydir = $dirlist; + addsubstvar($package, "python:Depends", $python, "<< $python_nextmajor"); } - $verdeps{$pyver} |= PY_MODULE if($deps & PY_MODULE); } - if ($verdeps{$pyver} & (PY_MODULE|PY_MODULE_NONSTANDARD) && ! $dh{NOSCRIPTS}) { - autoscript($package,"postinst","postinst-python","s%#PYVER#%$pyver%;s%#DIRLIST#%$pydir%"); - $need_prerm = 1; + + # Add postinst/preinst + my $need_prerm = 0; + foreach my $pyver (@python_allversions) { + my $pydir="/usr/lib/$pyver/site-packages"; + my @dirlist = (); # List of directories to byte-compile + + # If we have public modules, byte-compile /usr/lib/pythonX.Y/site-packages/ + if ($verdeps{$pyver} & PY_PUBLIC_MODULE) { + push @dirlist, $pydir; + } + + # Byte-compile private modules with current python + # (or the one indicated by -V) + if (($pyver eq "$usepython") and scalar(keys %private_dirs_list)) { + push @dirlist, keys %private_dirs_list; + } + + # If we have something to byte-compile, add the + # corresponding postinst snippet + if (scalar(@dirlist) && ! $dh{NOSCRIPTS}) { + autoscript($package,"postinst","postinst-python","s%#PYVER#%$pyver%;[EMAIL PROTECTED]"); + $need_prerm = 1; + } + } + if ($need_prerm && ! $dh{NOSCRIPTS}) { + autoscript($package,"prerm","prerm-python","s%#PACKAGE#%$package%"); } } - if ($need_prerm && ! $dh{NOSCRIPTS}) { - autoscript($package,"prerm","prerm-python","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; +} + +# Extract min, max and list of versions from +# a string like this "-1.2,1.4-1.6,2.0,2.3-" +sub analyze_pyversions { + my $field = shift; + my ($min, $max, @versions); + + my @all_versions = ("0.0"); + foreach (@python_allversions) { + if (m/python([\d\.]+)/) { + push @all_versions, $1; + } + } + push @all_versions, "100.0"; + + foreach my $range (split /,/, $field) { + if ($range =~ /^([\d\.]+)?-([\d\.]+)?$/) { + $min = defined($1) && $1 ? $1 : "0.0"; + $max = defined($2) && $2 ? $2 : "100.0"; + push @versions, grep { + compare_version($_, $min) >= 0 and + compare_version($_, $max) <= 0 } @all_versions; + } else { + push @versions, $range if (grep { $range eq $_ } @all_versions); + } + } + + $min = min(@versions); + $max = max(@versions); + $min = "" if (!defined($min) or $min eq "0.0"); + $max = "" if (!defined($max) or $max eq "100.0"); + + @versions = grep { $_ ne "0.0" and $_ ne "100.0" } @versions; + + return ($min, $max, @versions); +} + +# Convert the Python-Version field in a standardized "pyversions" field +sub convert_python_header { + my $header = shift; + my %pyversions_expected; + my %pyversions_additional; + my $use_additional = 0; + my @all_versions = (); + foreach (@python_allversions) { + if (m/python([\d\.]+)/) { + $pyversions_additional{$1} = 1; + push @all_versions, $1; + } + } + # Add two fakes minima, maxima to check if we have limits + $pyversions_additional{"0.0"} = 1; + $pyversions_additional{"100.0"} = 1; + + # Compute the list of versions that are supported + foreach my $check (split(/,\s*/, $header)) { + if ($check =~ /^=?\s*([\d\.]+)/) { + # Add any fixed version + $pyversions_expected{$1} = 1 if (grep { $_ eq $1 } @all_versions); + next; + } + # Deactivate versions which do not match the other + # criteria + if ($check =~ /^<<\s*([\d\.]+)/) { + my $v = $1; + $use_additional = 1; + foreach (keys %pyversions_additional) { + $pyversions_additional{$_} = 0 unless (compare_version($_, $v) < 0); + } + } elsif ($check =~ /^<=\s*([\d\.]+)/) { + my $v = $1; + $use_additional = 1; + foreach (keys %pyversions_additional) { + $pyversions_additional{$_} = 0 unless (compare_version($_, $v) <= 0); + } + } elsif ($check =~ /^>=\s*([\d\.]+)/) { + my $v = $1; + $use_additional = 1; + foreach (keys %pyversions_additional) { + $pyversions_additional{$_} = 0 unless (compare_version($_, $v) >= 0); + } + } + } + if ($use_additional) { + foreach (grep { $pyversions_additional{$_} } keys %pyversions_additional) { + $pyversions_expected{$_} = 1; + } + } + + my @supported = sort { compare_version($a, $b) } + grep { $pyversions_expected{$_} == 1 } keys %pyversions_expected; + + verbose_print("List of versions supported according to XS-Python-Version: @supported"); + + # Generate the corresponding information in standardized "pyversions" format + # XXX: I go to great length to generate the shortest pyversions + # syntax but it may not be necessary for the usage that we make of it. + my ($result, $index, $range_is_open, $last_real_version) = ("", 0, 0, ""); + foreach (@supported) { + if ($_ eq "0.0") { + $result .= "-"; # No lower limit + $range_is_open = 1; + } elsif ($_ eq "100.0") { + $result .= "-" unless $range_is_open; # No upper limit + $range_is_open = 0; # Proper end + } else { + $last_real_version = $_; + if ($range_is_open) { + # Range is open + if ($index <= $#all_versions and $all_versions[$index] eq $_) { + # This version still in the range + } else { + # Previous version ended the range + $result .= $all_versions[$index-1] . ",$_"; + $range_is_open = 0; + } + # Find the current version in all_versions + while ($index <= $#all_versions) { + last if ($all_versions[$index] eq $_); + $index++; + } + } else { + # There's no range yet + if ($result) { + if ($index <= $#all_versions and $all_versions[$index] eq $_) { + # This version still match, we can start a range + $result .= "-"; + $range_is_open = 1; + } else { + # Next version doesn't match, no range but a list + $result .= ",$_"; + } + } else { + # Empty field, start with something! + $result = "$_"; + } + while ($index <= $#all_versions) { + last if ($all_versions[$index] eq $_); + $index++; + } + } + $index++; } + } + if ($range_is_open) { + # Close the range properly + $result .= $last_real_version; + } + return $result; } =head1 SEE ALSO @@ -269,9 +769,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]>