1 # This module is part of debbugs, and is released
2 # under the terms of the GPL version 2, or any later
3 # version at your option.
4 # See the file README and COPYING for more information.
6 # [Other people have contributed to this file; their copyrights should
8 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
10 package Debbugs::Packages;
15 use base qw(Exporter);
16 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @EXPORT);
20 use Debbugs::Config qw(:config :globals);
26 %EXPORT_TAGS = (versions => [qw(getversions get_versions make_source_versions)],
27 mapping => [qw(getpkgsrc getpkgcomponent getsrcpkgs),
28 qw(binarytosource sourcetobinary makesourceversions)
32 Exporter::export_ok_tags(qw(versions mapping));
33 $EXPORT_TAGS{all} = [@EXPORT_OK];
36 use Fcntl qw(O_RDONLY);
37 use MLDBM qw(DB_File Storable);
38 use Storable qw(dclone);
39 use Params::Validate qw(validate_with :types);
40 use Debbugs::Common qw(make_list globify_scalar);
42 use List::Util qw(min max);
46 $MLDBM::DumpMeth = 'portable';
47 $MLDBM::RemoveTaint = 1;
51 Debbugs::Packages - debbugs binary/source package handling
55 The Debbugs::Packages module provides support functions to map binary
56 packages to their corresponding source packages and vice versa. (This makes
57 sense for software distributions, where developers may work on a single
58 source package which produces several binary packages for use by users; it
59 may not make sense in other contexts.)
65 Returns a reference to a hash of binary package names to their corresponding
74 return $_pkgsrc if $_pkgsrc;
75 return {} unless defined $Debbugs::Packages::gPackageSource;
80 my $fh = IO::File->new($config{package_source},'r')
81 or die("Unable to open $config{package_source} for reading: $!");
83 next unless m/^(\S+)\s+(\S+)\s+(\S.*\S)\s*$/;
84 my ($bin,$cmp,$src)=($1,$2,$3);
87 push @{$srcpkg{$src}}, $bin;
88 $pkgcomponent{$bin}= $cmp;
92 $_pkgcomponent = \%pkgcomponent;
97 =head2 getpkgcomponent
99 Returns a reference to a hash of binary package names to the component of
100 the archive containing those binary packages (e.g. "main", "contrib",
105 sub getpkgcomponent {
106 return $_pkgcomponent if $_pkgcomponent;
108 return $_pkgcomponent;
113 Returns a list of the binary packages produced by a given source package.
119 getpkgsrc() if not defined $_srcpkg;
120 return () if not defined $src or not exists $_srcpkg->{$src};
121 return @{$_srcpkg->{$src}};
124 =head2 binarytosource
126 Returns a reference to the source package name and version pair
127 corresponding to a given binary package name, version, and architecture.
129 If undef is passed as the architecture, returns a list of references
130 to all possible pairs of source package names and versions for all
131 architectures, with any duplicates removed.
133 If the binary version is not passed either, returns a list of possible
134 source package names for all architectures at all versions, with any
139 our %_binarytosource;
141 my ($binname, $binver, $binarch) = @_;
143 # TODO: This gets hit a lot, especially from buggyversion() - probably
144 # need an extra cache for speed here.
145 return () unless defined $gBinarySourceMap;
147 if (not tied %_binarytosource) {
148 tie %_binarytosource, MLDBM => $gBinarySourceMap, O_RDONLY or
149 die "Unable to open $gBinarySourceMap for reading";
152 # avoid autovivification
153 my $binary = $_binarytosource{$binname};
154 return () unless defined $binary;
155 my %binary = %{$binary};
156 if (not defined $binver) {
158 for my $ver (keys %binary) {
159 for my $ar (keys %{$binary{$ver}}) {
160 my $src = $binary{$ver}{$ar};
161 next unless defined $src;
162 $uniq{$src->[0]} = 1;
167 elsif (exists $binary{$binver}) {
168 if (defined $binarch) {
169 my $src = $binary{$binver}{$binarch};
170 return () unless defined $src; # not on this arch
171 # Copy the data to avoid tiedness problems.
174 # Get (srcname, srcver) pairs for all architectures and
175 # remove any duplicates. This involves some slightly tricky
176 # multidimensional hashing; sorry. Fortunately there'll
177 # usually only be one pair returned.
179 for my $ar (keys %{$binary{$binver}}) {
180 my $src = $binary{$binver}{$ar};
181 next unless defined $src;
182 $uniq{$src->[0]}{$src->[1]} = 1;
185 for my $sn (sort keys %uniq) {
186 push @uniq, [$sn, $_] for sort keys %{$uniq{$sn}};
192 # No $gBinarySourceMap, or it didn't have an entry for this name and
197 =head2 sourcetobinary
199 Returns a list of references to triplets of binary package names, versions,
200 and architectures corresponding to a given source package name and version.
201 If the given source package name and version cannot be found in the database
202 but the source package name is in the unversioned package-to-source map
203 file, then a reference to a binary package name and version pair will be
204 returned, without the architecture.
208 our %_sourcetobinary;
210 my ($srcname, $srcver) = @_;
212 if (not tied %_sourcetobinary) {
213 tie %_sourcetobinary, MLDBM => $gSourceBinaryMap, O_RDONLY or
214 die "Unable top open $gSourceBinaryMap for reading";
219 # avoid autovivification
220 my $source = $_sourcetobinary{$srcname};
221 return () unless defined $source;
222 my %source = %{$source};
223 if (exists $source{$srcver}) {
224 my $bin = $source{$srcver};
225 return () unless defined $bin;
228 # No $gSourceBinaryMap, or it didn't have an entry for this name and
229 # version. Try $gPackageSource (unversioned) instead.
230 my @srcpkgs = getsrcpkgs($srcname);
231 return map [$_, $srcver], @srcpkgs;
236 Returns versions of the package in a distribution at a specific
242 my ($pkg, $dist, $arch) = @_;
243 return get_versions(package=>$pkg,
245 defined $arch ? (arch => $arch):(),
253 get_versions(package=>'foopkg',
258 Returns a list of the versions of package in the distributions and
259 architectures listed. This routine only returns unique values.
263 =item package -- package to return list of versions
265 =item dist -- distribution (unstable, stable, testing); can be an
268 =item arch -- architecture (i386, source, ...); can be an arrayref
270 =item time -- returns a version=>time hash at which the newest package
271 matching this version was uploaded
273 =item source -- returns source/version instead of just versions
275 =item no_source_arch -- discards the source architecture when arch is
276 not passed. [Used for finding the versions of binary packages only.]
277 Defaults to 0, which does not discard the source architecture. (This
278 may change in the future, so if you care, please code accordingly.)
280 =item return_archs -- returns a version=>[archs] hash indicating which
281 architectures are at which versions.
285 When called in scalar context, this function will return hashrefs or
286 arrayrefs as appropriate, in list context, it will return paired lists
287 or unpaired lists as appropriate.
295 my %param = validate_with(params => \@_,
296 spec => {package => {type => SCALAR|ARRAYREF,
298 dist => {type => SCALAR|ARRAYREF,
299 default => 'unstable',
301 arch => {type => SCALAR|ARRAYREF,
304 time => {type => BOOLEAN,
307 source => {type => BOOLEAN,
310 no_source_arch => {type => BOOLEAN,
313 return_archs => {type => BOOLEAN,
320 return () if not defined $gVersionTimeIndex;
321 unless (tied %_versions_time) {
322 tie %_versions_time, 'MLDBM', $gVersionTimeIndex, O_RDONLY
323 or die "can't open versions index $gVersionTimeIndex: $!";
325 $versions = \%_versions_time;
328 return () if not defined $gVersionIndex;
329 unless (tied %_versions) {
330 tie %_versions, 'MLDBM', $gVersionIndex, O_RDONLY
331 or die "can't open versions index $gVersionIndex: $!";
333 $versions = \%_versions;
336 for my $package (make_list($param{package})) {
337 my $version = $versions->{$package};
338 next unless defined $version;
339 for my $dist (make_list($param{dist})) {
340 for my $arch (exists $param{arch}?
341 make_list($param{arch}):
342 (grep {not $param{no_source_arch} or
344 } keys %{$version->{$dist}})) {
345 next unless defined $version->{$dist}{$arch};
346 for my $ver (ref $version->{$dist}{$arch} ?
347 keys %{$version->{$dist}{$arch}} :
348 $version->{$dist}{$arch}
351 if ($param{source}) {
352 ($f_ver) = make_source_versions(package => $package,
355 next unless defined $f_ver;
358 $versions{$f_ver} = max($versions{$f_ver}||0,$version->{$dist}{$arch}{$ver});
361 push @{$versions{$f_ver}},$arch;
367 if ($param{time} or $param{return_archs}) {
368 return wantarray?%versions :\%versions;
370 return wantarray?keys %versions :[keys %versions];
374 =head2 makesourceversions
376 @{$cgi_var{found}} = makesourceversions($cgi_var{package},undef,@{$cgi_var{found}});
378 Canonicalize versions into source versions, which have an explicitly
379 named source package. This is used to cope with source packages whose
380 names have changed during their history, and with cases where source
381 version numbers differ from binary version numbers.
385 our %_sourceversioncache = ();
386 sub makesourceversions {
387 my ($package,$arch,@versions) = @_;
388 die "Package $package is multiple packages; split on , and call makesourceversions multiple times"
390 return make_source_versions(package => $package,
391 (defined $arch)?(arch => $arch):(),
392 versions => \@versions
396 =head2 make_source_versions
398 make_source_versions(package => 'foo',
403 warnings => \$warnings,
406 An extended version of makesourceversions (which calls this function
407 internally) that allows for multiple packages, architectures, and
408 outputs warnings and debugging information to provided SCALARREFs or
411 The guess_source option determines whether the source package is
412 guessed at if there is no obviously correct package. Things that use
413 this function for non-transient output should set this to false,
414 things that use it for transient output can set this to true.
415 Currently it defaults to true, but that is not a sane option.
420 sub make_source_versions {
421 my %param = validate_with(params => \@_,
422 spec => {package => {type => SCALAR|ARRAYREF,
424 arch => {type => SCALAR|ARRAYREF,
427 versions => {type => SCALAR|ARRAYREF,
430 guess_source => {type => BOOLEAN,
433 source_version_cache => {type => HASHREF,
436 debug => {type => SCALARREF|HANDLE,
439 warnings => {type => SCALARREF|HANDLE,
444 my ($warnings) = globify_scalar(exists $param{warnings}?$param{warnings}:undef);
445 my ($debug) = globify_scalar(exists $param{debug} ?$param{debug} :undef);
448 my @packages = grep {defined $_ and length $_ } make_list($param{package});
449 my @archs = grep {defined $_ } make_list ($param{archs});
453 if (not exists $param{source_version_cache}) {
454 $param{source_version_cache} = \%_sourceversioncache;
456 if (grep {/,/} make_list($param{package})) {
457 croak "Package names contain ,; split on /,/ and call make_source_versions with an arrayref of packages"
460 for my $version (make_list($param{versions})) {
461 if ($version =~ m{(.+)/([^/]+)$}) {
462 # check to see if this source version is even possible
463 my @bin_versions = sourcetobinary($1,$2);
464 if (not @bin_versions or
465 @{$bin_versions[0]} != 3) {
466 print {$warnings} "The source $1 and version $2 do not appear to match any binary packages\n";
468 # Already a source version.
469 $sourceversions{$version} = 1;
472 croak "You must provide at least one package if the versions are not fully qualified";
474 for my $pkg (@packages) {
475 for my $arch (@archs) {
476 my $cachearch = (defined $arch) ? $arch : '';
477 my $cachekey = "$pkg/$cachearch/$version";
478 if (exists($param{source_version_cache}{$cachekey})) {
479 for my $v (@{$param{source_version_cache}{$cachekey}}) {
480 $sourceversions{$v} = 1;
484 elsif ($param{guess_source} and
485 exists$param{source_version_cache}{$cachekey.'/guess'}) {
486 for my $v (@{$param{source_version_cache}{$cachekey}}) {
487 $sourceversions{$v} = 1;
491 my @srcinfo = binarytosource($pkg, $version, $arch);
493 # We don't have explicit information about the
494 # binary-to-source mapping for this version
496 print {$warnings} "There is no source info for the package '$pkg' at version '$version' with architecture '$arch'\n";
497 if ($param{guess_source}) {
499 my $pkgsrc = getpkgsrc();
500 if (exists $pkgsrc->{$pkg}) {
501 @srcinfo = ([$pkgsrc->{$pkg}, $version]);
502 } elsif (getsrcpkgs($pkg)) {
503 # If we're looking at a source package
504 # that doesn't have a binary of the
505 # same name, just try the same
507 @srcinfo = ([$pkg, $version]);
511 # store guesses in a slightly different location
512 $param{source_version_cache}{$cachekey.'/guess'} = [ map { "$_->[0]/$_->[1]" } @srcinfo ];
516 # only store this if we didn't have to guess it
517 $param{source_version_cache}{$cachekey} = [ map { "$_->[0]/$_->[1]" } @srcinfo ];
519 $sourceversions{"$_->[0]/$_->[1]"} = 1 foreach @srcinfo;
524 return sort keys %sourceversions;