File Coverage

blib/lib/Debian/AptContents.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Debian::AptContents;
2              
3 13     13   897084 use strict;
  13         47  
  13         398  
4 13     13   79 use warnings;
  13         30  
  13         627  
5              
6             our $VERSION = '0.96';
7              
8             =head1 NAME
9              
10             Debian::AptContents - parse/search through apt-file's Contents files
11              
12             =head1 SYNOPSIS
13              
14             my $c = Debian::AptContents->new( { homedir => '~/.dh-make-perl' } );
15             my @pkgs = $c->find_file_packages('/usr/bin/foo');
16             my $dep = $c->find_perl_module_package('Foo::Bar');
17              
18             =head1 TODO
19              
20             This needs to really work not only for Perl modules.
21              
22             A module specific to Perl modules is needed by dh-make-perl, but it can
23             subclass Debian::AptContents, which needs to become more generic.
24              
25             =cut
26              
27 13     13   84 use base qw(Class::Accessor);
  13         35  
  13         2606  
28             __PACKAGE__->mk_accessors(
29             qw(
30             cache homedir cache_file contents_files verbose
31             source dist
32             )
33             );
34              
35 13     13   3060 use Config;
  13         38  
  13         943  
36 13     13   4052 use Debian::Dependency;
  0            
  0            
37             use DhMakePerl::Utils qw(find_core_perl_dependency is_core_perl_package);
38             use File::Spec::Functions qw( catfile catdir splitpath );
39             use IO::Uncompress::Gunzip;
40             use List::MoreUtils qw(uniq);
41             use Module::CoreList ();
42             use Storable;
43             use AptPkg::Config;
44              
45             $AptPkg::Config::_config->init();
46              
47             our $oldstable_perl = '5.14.2';
48              
49             =head1 CONSTRUCTOR
50              
51             =over
52              
53             =item new
54              
55             Constructs new instance of the class. Expects at least C option.
56              
57             =back
58              
59             =head1 FIELDS
60              
61             =over
62              
63             =item homedir
64              
65             (B) Directory where the object stores its cache.
66              
67             =item dist
68              
69             Used for filtering on the C part of the repository paths listed in
70             L. Default is empty, meaning no filtering.
71              
72             =item contents_files
73              
74             Arrayref of F file names. Default is to let B find them.
75              
76             =item cache_file
77              
78             Path to the file with cached parsed information from all F files.
79             Default is F under C.
80              
81             =item cache
82              
83             Filled by C. Used by C and (obviously)
84             C
85              
86             =item verbose
87              
88             Verbosity level. 0 means silent, the bigger the more the jabber. Default is 1.
89              
90             =back
91              
92             =cut
93              
94             sub new {
95             my $class = shift;
96             $class = ref($class) if ref($class);
97             my $self = $class->SUPER::new(@_);
98              
99             # required options
100             $self->homedir
101             or die "No homedir given";
102              
103             # some defaults
104             $self->contents_files( $self->get_contents_files )
105             unless $self->contents_files;
106             $self->cache_file( catfile( $self->homedir, 'Contents.cache' ) )
107             unless $self->cache_file;
108             $self->verbose(1) unless defined( $self->verbose );
109              
110             $self->read_cache();
111              
112             return $self;
113             }
114              
115             =head1 OBJECT METHODS
116              
117             =over
118              
119             =item warning
120              
121             Used internally. Given a verbosity level and a message, prints the message to
122             STDERR if the verbosity level is greater than or equal of the value of
123             C.
124              
125             =cut
126              
127             sub warning {
128             my ( $self, $level, $msg ) = @_;
129              
130             warn "$msg\n" if $self->verbose >= $level;
131             }
132              
133             =item get_contents_files
134              
135             Reads F, gives the repository paths to
136             C and returns an arrayref of file names of
137             Contents files.
138              
139             =cut
140              
141             sub get_contents_files {
142             my $self = shift;
143              
144             my $archspec = `dpkg --print-architecture`;
145             chomp($archspec);
146              
147             my @res;
148              
149             # stolen from apt-file, contents_file_paths()
150             my @cmd = (
151             'apt-get', 'indextargets',
152             '--format', '$(CREATED_BY) $(ARCHITECTURE) $(SUITE) $(FILENAME)'
153             );
154             open( my $fd, '-|', @cmd )
155             or die "Cannot execute apt-get indextargets: $!\n";
156             while ( my $line = <$fd> ) {
157             chomp($line);
158             next unless $line =~ m/^Contents-deb/;
159             my ( $index_name, $arch, $suite, $filename ) = split( ' ', $line, 4 );
160             next unless $arch eq $archspec;
161             if ( $self->dist ) {
162             next unless $suite eq $self->dist;
163             }
164             push @res, $filename;
165             }
166             close($fd);
167              
168             return [ uniq sort @res ];
169             }
170              
171             =item read_cache
172              
173             Reads the cached parsed F files. If there are F files with
174             more recent mtime than that of the cache (or if there is no cache at all),
175             parses all F and stores the cache via C for later
176             invocation.
177              
178             =cut
179              
180             sub read_cache {
181             my $self = shift;
182              
183             my $cache;
184              
185             if ( -r $self->cache_file ) {
186             $cache = eval { Storable::retrieve( $self->cache_file ) };
187             undef($cache) unless ref($cache) and ref($cache) eq 'HASH';
188             }
189              
190             # see if the cache is stale
191             if ( $cache and $cache->{stamp} and $cache->{contents_files} ) {
192             undef($cache)
193             unless join( '><', @{ $self->contents_files } ) eq
194             join( '><', @{ $cache->{contents_files} } );
195              
196             # file lists are the same?
197             # see if any of the files has changed since we
198             # last read it
199             if ($cache) {
200             for ( @{ $self->contents_files } ) {
201             if ( ( stat($_) )[9] > $cache->{stamp} ) {
202             undef($cache);
203             last;
204             }
205             }
206             }
207             }
208             else {
209             undef($cache);
210             }
211              
212             unless ($cache) {
213             if ( scalar @{ $self->contents_files } ) {
214             $self->source('parsed files');
215             $cache->{stamp} = time;
216             $cache->{contents_files} = [];
217             $cache->{apt_contents} = {};
218              
219             push @{ $cache->{contents_files} }, @{ $self->contents_files };
220             my @cat_cmd = (
221             '/usr/lib/apt/apt-helper', 'cat-file', @{ $self->contents_files }
222             );
223             open( my $f, "-|", @cat_cmd )
224             or die
225             "Can't run '/usr/lib/apt/apt-helper cat-file' on Contents files: $!\n";
226              
227             $self->warning( 1,
228             "Parsing Contents files:\n\t"
229             . join( "\n\t", @{ $self->contents_files } ) );
230             my $line;
231             while ( defined( $line = $f->getline ) ) {
232             my ( $file, $packages ) = split( /\s+/, $line );
233             next unless $file =~ s{
234             ^usr/
235             (?:share|lib)/
236             (?:perl\d+/ # perl5/
237             | perl/(?:\d[\d.]+)/ # or perl/5.10/
238             | \S+-\S+-\S+/perl\d+/(?:\d[\d.]+)/ # x86_64-linux-gnu/perl5/5.22/
239             )
240             }{}x;
241             $cache->{apt_contents}{$file} = exists $cache->{apt_contents}{$file}
242             ? $cache->{apt_contents}{$file}.','.$packages
243             : $packages;
244              
245             # $packages is a comma-separated list of
246             # section/package items. We'll parse it when a file
247             # matches. Otherwise we'd parse thousands of entries,
248             # while checking only a couple
249             }
250             close($f);
251              
252             if ( %{ $cache->{apt_contents} } ) {
253             $self->cache($cache);
254             $self->store_cache;
255             }
256             }
257             }
258             else {
259             $self->source('cache');
260             $self->warning( 1,
261             "Using cached Contents from " . localtime( $cache->{stamp} ) );
262              
263             $self->cache($cache);
264             }
265             }
266              
267             =item store_cache
268              
269             Writes the contents of the parsed C to the C.
270              
271             Storable is used to stream the data. Along with the information from
272             F files, a time stamp is stored.
273              
274             =cut
275              
276             sub store_cache {
277             my $self = shift;
278              
279             my ( $vol, $dir, $file ) = splitpath( $self->cache_file );
280              
281             $dir = catdir( $vol, $dir );
282             unless ( -d $dir ) {
283             mkdir $dir
284             or die "Error creating directory '$dir': $!\n";
285             }
286              
287             Storable::nstore( $self->cache, $self->cache_file . '-new' );
288             rename( $self->cache_file . '-new', $self->cache_file );
289             }
290              
291             =item find_file_packages
292              
293             Returns a list of packages where the given file was found.
294              
295             F files store the package section together with package name. That is
296             stripped.
297              
298             Returns an empty list of the file is not found in any package.
299              
300             =cut
301              
302             sub find_file_packages {
303             my ( $self, $file ) = @_;
304              
305             my $packages = $self->cache->{apt_contents}{$file};
306              
307             return () unless $packages;
308              
309             my @packages = split( /,/, $packages ); # Contents contains a
310             # comma-delimited list
311             # of packages
312              
313             s{.+/}{} for @packages; # remove section. Greedy on purpose
314             # otherwise it won't strip enough off Ubuntu's
315             # usr/share/perl5/Config/Any.pm universe/perl/libconfig-any-perl
316              
317             # in-core dependencies are given by find_core_perl_dependency
318             @packages = grep { !is_core_perl_package($_) } @packages;
319              
320             return uniq @packages;
321             }
322              
323             =item find_perl_module_package( $module, $version )
324              
325             Given Perl module name (e.g. Foo::Bar), returns a L object
326             representing the required Debian package and version. If the module is a core
327             one, suitable dependency on perl is returned.
328              
329             If the package is also available in a separate package, an alternative
330             dependency is returned.
331              
332             In case the version of the currently running Perl interpreter is lower than the
333             version in which the wanted module is available in core, the separate package
334             is preferred. Otherwise the perl dependency is the first alternative.
335              
336             =cut
337              
338             sub find_perl_module_package {
339             my ( $self, $module, $version ) = @_;
340              
341             # see if the module is included in perl core
342             my $core_dep = find_core_perl_dependency( $module, $version );
343              
344             # try module packages
345             my $module_file = $module;
346             $module_file =~ s|::|/|g;
347              
348             my @matches = $self->find_file_packages("$module_file.pm");
349              
350             # rank non -perl packages lower
351             @matches = sort {
352             if ( $a !~ /-perl$/ ) { return 1; }
353             elsif ( $b !~ /-perl$/ ) { return -1; }
354             else { return $a cmp $b; } # or 0?
355             } @matches;
356              
357             # we don't want perl packages here
358             @matches = grep { !is_core_perl_package($_) } @matches;
359              
360             my $direct_dep;
361             $direct_dep = Debian::Dependency->new(
362             ( @matches > 1 )
363             ? [ map ( { pkg => $_, rel => '>=', ver => $version }, @matches ) ]
364             : ( $matches[0], $version )
365             ) if @matches;
366              
367             my $running_perl = $Config::Config{version};
368              
369             if ($core_dep) {
370              
371             # the core dependency is satisfied by oldstable?
372             if ( $core_dep->ver <= $oldstable_perl ) {
373             # drop the direct dependency and remove the version
374             undef($direct_dep);
375              
376             $core_dep->ver(undef);
377             $core_dep->rel(undef);
378             }
379              
380             if ($direct_dep) {
381             # both in core and in a package.
382             if( $running_perl >= $core_dep->ver ) {
383             return Debian::Dependency->new("$core_dep | $direct_dep");
384             }
385             else {
386             return Debian::Dependency->new("$direct_dep | $core_dep");
387             }
388             }
389             else {
390             # only in core
391             return $core_dep;
392             }
393             }
394             else {
395             # maybe in a package
396             return $direct_dep;
397             }
398             }
399              
400             1;
401              
402             =back
403              
404             =head1 AUTHOR
405              
406             =over 4
407              
408             =item Damyan Ivanov
409              
410             =item gregor herrmann
411              
412             =back
413              
414             =head1 COPYRIGHT & LICENSE
415              
416             =over 4
417              
418             =item Copyright (C) 2008, 2009, 2010 Damyan Ivanov
419              
420             =item Copyright (C) 2016, gregor herrmann
421              
422             =back
423              
424             This program is free software; you can redistribute it and/or modify it under
425             the terms of the GNU General Public License version 2 as published by the Free
426             Software Foundation.
427              
428             This program is distributed in the hope that it will be useful, but WITHOUT ANY
429             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
430             PARTICULAR PURPOSE. See the GNU General Public License for more details.
431              
432             You should have received a copy of the GNU General Public License along with
433             this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
434             Street, Fifth Floor, Boston, MA 02110-1301 USA.
435              
436             =cut