1 package Packaging
::Tools
;
11 use CPAN
::DistnameInfo
;
14 require File
::Basename
;
15 require File
::ShareDir
;
16 require File
::Find
::Rule
;
24 Packaging::Tools - Support tools packagers packaging Perl 5 modules
28 our $VERSION = '0.001';
32 STATE_NEWER_IN_CPAN
=> 1,
33 STATE_NEWER_IN_CORE
=> 2,
34 STATE_OUT_OF_SYNC
=> 3,
35 STATE_REMOVED_FROM_INDEX
=> 4,
40 $state_remarks[STATE_OK
] = 'fine';
41 $state_remarks[STATE_NEWER_IN_CPAN
] = 'needs update';
42 $state_remarks[STATE_NEWER_IN_CORE
] = 'newer in Core';
43 $state_remarks[STATE_OUT_OF_SYNC
] = 'out of sync';
44 $state_remarks[STATE_REMOVED_FROM_INDEX
] = 'not in CPAN index';
45 $state_remarks[STATE_ERROR
] = '';
48 $state_cmpops[STATE_OK
] = '==';
49 $state_cmpops[STATE_NEWER_IN_CPAN
] = '<';
50 $state_cmpops[STATE_OUT_OF_SYNC
] = '!=';
54 =head1 SUBROUTINES/METHODS
56 =head2 new($dist_type,\%args)
58 Instantiates new Packaging::Tools instance and connects to a
59 Packaging::Tools::Plugin::$dist_type instance.
61 The plugin is installed with \%args as parameters.
67 my ( $class, $dist_type, $args ) = @_;
69 eval "require Packaging::Tools::Plugin::$dist_type" or die $@
;
71 my $dist_type_class = "Packaging::Tools::Plugin::$dist_type";
73 my $self = bless( {}, $class );
74 $self->{_dist_tools
} = $dist_type_class->new($args);
75 $self->{_pkg_dist
} = $dist_type;
80 =head2 get_state_remarks
82 Return the list of the remarks for the known states.
84 See the example templates how to use it.
88 sub get_state_remarks
{ return @state_remarks; }
90 =head2 get_state_cmpops
92 Return the list of compare operations for the known states (e.g. "<" for
95 See the example templates how to use it.
99 sub get_state_cmpops
{ return @state_cmpops; }
101 =head2 get_template_directories(;$tool)
103 Returns the directories containing the templates, either in general or for
108 sub get_template_directories
110 my ( $self, $tool ) = @_;
113 File
::ShareDir
::dist_dir
("Packaging-Tools"),
114 $self->{_dist_tools
}->get_template_directories()
117 $tool and return map { File
::Spec
->catdir( $_, $tool ) } @tt_src_dirs;
122 =head2 find_templates(;$tool)
124 Find templates (full qualified path name) for processing, either in general
125 or for specific tools.
131 my ( $self, $tool ) = @_;
133 $tool //= File
::Basename
::fileparse
( $0, qr/\.[^.]*/ );
135 my @tt_src_dirs = $self->get_template_directories($tool);
136 my @templates = File
::Find
::Rule
->file()->name("*.tt2")->maxdepth(1)->in(@tt_src_dirs);
141 =head2 load_cpan_config(;$cpan_home)
143 Loads the CPAN config. If a I<$cpan_home> is given and the is a
144 C<CPAN/MyConfig.pm> in there, this file is loaded. If a I<$cpan_home>
145 is given without a C<CPAN/MyConfig.pm> in there, I<$CPAN::Config{cpan_home}>
146 is set to given I<$cpan_home>. Otherwise, only CPAN::HandleConfig->load()
153 my ( $self, $cpan_home ) = @_;
155 if ( defined($cpan_home) and -e File
::Spec
->catfile( $cpan_home, 'CPAN', 'MyConfig.pm' ) )
157 my $file = File
::Spec
->catfile( $cpan_home, 'CPAN', 'MyConfig.pm' );
159 # XXX taken from App:Cpan::_load_config()
161 delete $INC{'CPAN/Config.pm'};
163 my $rc = eval { require $file };
164 my $err_myconfig = $@
;
165 if ( $err_myconfig and $err_myconfig !~ m
#locate \Q$file\E# )
167 croak
"Error while requiring ${file}:\n$err_myconfig";
169 elsif ($err_myconfig)
171 CPAN
::HandleConfig
->load();
172 defined( $INC{"CPAN/MyConfig.pm"} )
173 and $CPAN::Config_loaded
++;
174 defined( $INC{"CPAN/Config.pm"} )
175 and $CPAN::Config_loaded
++;
179 # CPAN::HandleConfig::require_myconfig_or_config looks for this
180 $INC{'CPAN/MyConfig.pm'} = 'fake out!';
182 # CPAN::HandleConfig::load looks for this
183 $CPAN::Config_loaded
= 'fake out';
188 CPAN
::HandleConfig
->load();
189 defined( $INC{"CPAN/MyConfig.pm"} )
190 and $CPAN::Config_loaded
++;
191 defined( $INC{"CPAN/Config.pm"} )
192 and $CPAN::Config_loaded
++;
195 and $CPAN::Config
{cpan_home
} = $cpan_home;
199 or croak
("Can't load CPAN::Config - please setup CPAN first");
202 =head2 get_cpan_versions(;$update_index)
204 (Re-)Loads the CPAN Index. When I<$update_index> is given and true, a newer
205 index is tried to fetch from configured mirror sites.
209 sub get_cpan_versions
211 my ( $self, $update_idx ) = @_;
213 defined( $self->{cpan_idx
} ) and !$update_idx and return %{ $self->{cpan_idx
} };
214 $CPAN::Config_loaded
or $self->load_cpan_config();
216 $self->{cpan_idx
} = {};
220 and $CPAN::Index
::LAST_TIME
= 0;
221 CPAN
::Index
->reload( defined($update_idx) and $update_idx );
222 $CPAN::Index
::LAST_TIME
223 or carp
("Can't reload CPAN Index");
225 my @all_dists = $CPAN::META
->all_objects("CPAN::Distribution");
227 foreach my $dist (@all_dists)
229 my $dinfo = CPAN
::DistnameInfo
->new( $dist->id() );
230 my ( $distname, $distver ) = ( $dinfo->dist(), $dinfo->version() );
231 defined($distname) or next;
232 defined($distver) or next;
234 !defined( $self->{cpan_idx
}->{$distname} )
235 || ( defined( $self->{cpan_idx
}->{$distname} )
236 && _is_gt
( $distver, $self->{cpan_idx
}->{$distname} ) )
239 $self->{cpan_idx
}->{$distname} = $distver;
246 =head2 get_modules_by_distribution
248 Builds internal data structure of cpan available distributions
249 from index of modules.
253 sub get_modules_by_distribution
257 defined( $self->{cpan_mods_by_dist
} ) and return;
259 $self->get_cpan_versions();
261 my @all_modules = $CPAN::META
->all_objects("CPAN::Module");
264 foreach my $module (@all_modules)
266 my $modname = $module->id();
267 $module->cpan_version() or next;
268 my $distfile = $module->cpan_file();
269 my $dinfo = CPAN
::DistnameInfo
->new($distfile);
270 my ( $distname, $distver ) = ( $dinfo->dist(), $dinfo->version() );
271 defined($distname) or next;
272 defined($distver) or next;
273 $modsbydist{$distname} //= [];
274 push( @
{ $modsbydist{$distname} }, $modname );
277 $self->{cpan_mods_by_dist
} = \
%modsbydist;
282 =head2 get_installed_packages
284 Returns hash of name =E<gt> version of installed packages.
288 sub get_installed_packages
292 defined( $self->{installed_packages
} )
293 and return %{ $self->{installed_packages
} };
294 $self->{installed_packages
} = { $self->{_dist_tools
}->get_installed_packages() };
296 return %{ $self->{installed_packages
} };
299 =head2 find_packaged_modules
301 Returns list of packaged perl modules. The list has no specific order.
305 sub find_packaged_modules
309 defined( $self->{packaged_modules
} )
310 and return keys %{ $self->{packaged_modules
} };
311 $self->{packaged_modules
} = { map { $_ => 1 } $self->{_dist_tools
}->find_packaged_modules() };
313 return keys %{ $self->{packaged_modules
} };
319 defined( $_[0] ) and $_[0] =~ /^v/ and $_[1] !~ /^v/ and $_[1] = "v$_[1]";
320 defined( $_[0] ) and $_[0] !~ /^v/ and $_[1] =~ /^v/ and $_[0] = "v$_[0]";
321 eval { $gt = defined( $_[0] ) && ( version
->parse( $_[0] ) > version
->parse( $_[1] ) ); };
324 $gt = defined( $_[0] ) && ( $_[0] gt $_[1] );
332 defined( $_[0] ) and $_[0] =~ /^v/ and $_[1] !~ /^v/ and $_[1] = "v$_[1]";
333 defined( $_[0] ) and $_[0] !~ /^v/ and $_[1] =~ /^v/ and $_[0] = "v$_[0]";
334 eval { $ne = defined( $_[0] )
335 && ( version
->parse( $_[0] ) != version
->parse( $_[1] ) ); };
338 $ne = defined( $_[0] ) && ( $_[0] ne $_[1] );
343 =head2 get_pkg_details($pkg_ident,@var_names)
345 Returns package details.
349 =item * I<$pkg_ident>
351 One value from the list of L</find_packaged_modules|packaged modules>.
353 =item * I<@var_names>
355 One from the variables the distribution tool plugin
356 L<Packaging::Tools::Plugin/get_pkg_details($pkg_ident,@var_names)|provides>
357 or any combination of:
377 my ( $self, $pkg_ident, @var_names ) = @_;
379 my @local_vars = grep { $_ =~ m/^(?:CPAN|CHECK)_/ } @var_names;
380 my @pkg_vars = grep { !( $_ ~~ @local_vars ) } @var_names;
383 @result{@pkg_vars} = $self->{_dist_tools
}->get_pkg_details( $pkg_ident, @pkg_vars );
385 unless ( defined( $self->{pkg_details
}->{$pkg_ident} ) )
387 my $dist_name = $result{DIST_NAME
}
388 // $self->{_dist_tools
}->get_pkg_details( $pkg_ident, 'DIST_NAME' );
389 $self->{pkg_details
}->{$pkg_ident}->{CPAN_VERSION
} = $self->{cpan_idx
}->{$dist_name};
390 $self->{pkg_details
}->{$pkg_ident}->{CPAN_NAME
} = $dist_name;
393 @result{@local_vars} = @
{ $self->{pkg_details
}->{$pkg_ident} }{@local_vars};
395 return @result{@var_names};
398 =head2 check_pkg_up2date_state($pkg_ident)
400 Returns the state of specified package.
402 Returned state is one of
408 Distribution package is up to date compared against fetched CPAN Index.
410 =item STATE_NEWER_IN_CPAN
412 There is a newer version of packaged distribution in CPAN.
414 =item STATE_NEWER_IN_CORE
416 There is a newer version of at least one module provided by the most recent
417 package in the current used Perl Core.
419 =item STATE_REMOVED_FROM_INDEX
421 Cannot find distribution in CPAN Index, most likely the module was removed
422 or never indexed for any reason.
424 =item STATE_OUT_OF_SYNC
426 The distribution packaged version is newer than newest one found in fetched
427 CPAN Index, which can have either the same reason as for
428 STATE_REMOVED_FROM_INDEX or the distribution is provided from somewhere else
433 Error getting distribution information for specified package.
439 sub check_pkg_up2date_state
441 my ( $self, $pkg_ident ) = @_;
443 defined( $self->{installed_packages
} ) or $self->get_installed_packages();
444 defined( $self->{cpan_mods_by_dist
} ) or $self->get_modules_by_distribution();
446 my @pkg_det_keys = (qw(DIST_NAME DIST_VERSION PKG_VERSION CPAN_VERSION MASTER_SITES));
447 my ( $dist_name, $dist_version, $pkg_version, $cpan_version, $master_sites ) =
448 eval { $self->get_pkg_details( $pkg_ident, @pkg_det_keys ); };
450 if ( $@
or !defined($dist_name) or !defined($dist_version) )
452 $self->{pkg_details
}->{$pkg_ident}->{CHECK_COMMENT
} = 'Error getting distribution data';
453 return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_ERROR
;
457 and return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_OK
;
459 foreach my $distmod ( @
{ $self->{cpan_mods_by_dist
}->{$dist_name} } )
461 defined( $Module::CoreList
::version
{$]}->{$distmod} ) or next;
462 my $mod = $CPAN::META
->instance( "CPAN::Module", $distmod );
463 if ( _is_gt
( $Module::CoreList
::version
{$]}->{$distmod}, $mod->cpan_version() ) )
465 $self->{pkg_details
}->{$pkg_ident}->{CORE_NEWER
}->{$distmod} =
466 [ $Module::CoreList
::version
{$]}->{$distmod}, $mod->cpan_version() ];
469 if ( defined( $self->{pkg_details
}->{$pkg_ident}->{CORE_NEWER
} ) )
471 $self->{pkg_details
}->{$pkg_ident}->{CHECK_COMMENT
} =
472 "$dist_name-$dist_version has newer modules in core: " . join(
476 . $self->{pkg_details
}->{$pkg_ident}->{CORE_NEWER
}->{$_}->[0] . " > "
477 . $self->{pkg_details
}->{$pkg_ident}->{CORE_NEWER
}->{$_}->[1]
478 } keys %{ $self->{pkg_details
}->{$pkg_ident}->{CORE_NEWER
} }
480 return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_NEWER_IN_CORE
;
483 if ( !defined($cpan_version) )
485 defined($master_sites)
486 and $master_sites !~ m/cpan/i
487 and return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_OK
;
488 return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_REMOVED_FROM_INDEX
;
490 elsif ( _is_gt
( $cpan_version, $dist_version ) )
492 return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_NEWER_IN_CPAN
;
494 elsif ( _is_ne
( $cpan_version, $dist_version ) )
496 return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_OUT_OF_SYNC
;
499 return $self->{pkg_details
}->{$pkg_ident}->{CHECK_STATE
} = STATE_OK
;
502 sub get_distribution_for_module
504 my ( $self, $module ) = @_;
507 if ( $CPAN::META
->exists( "CPAN::Module", $module ) )
509 my $cpan_mod = $CPAN::META
->instance( "CPAN::Module", $module );
510 my $cpan_dist = CPAN
::DistnameInfo
->new( $cpan_mod->cpan_file() )->dist();
511 my $pkg_ident = $self->{_dist_tools
}->find_package( DIST_NAME
=> $cpan_dist );
513 push @found, $pkg_ident;
516 defined( $Module::CoreList
::version
{$]}->{$module} ) and push @found, "Core ($])";
518 return wantarray ?
@found : join( ",", @found );
523 Jens Rehsack, C<< <rehsack at cpan.org> >>
527 Please report any bugs or feature requests to C<bug-packaging-tools at rt.cpan.org>, or through
528 the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Packaging-Tools>. I will be notified, and then you'll
529 automatically be notified of progress on your bug as I make changes.
533 You can find documentation for this module with the perldoc command.
535 perldoc Packaging::Tools
538 You can also look for information at:
542 =item * RT: CPAN's request tracker (report bugs here)
544 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Packaging-Tools>
546 =item * AnnoCPAN: Annotated CPAN documentation
548 L<http://annocpan.org/dist/Packaging-Tools>
552 L<http://cpanratings.perl.org/d/Packaging-Tools>
556 L<http://search.cpan.org/dist/Packaging-Tools/>
561 =head1 ACKNOWLEDGEMENTS
564 =head1 LICENSE AND COPYRIGHT
566 Copyright 2012 Jens Rehsack.
568 This program is free software; you can redistribute it and/or modify it
569 under the terms of either: the GNU General Public License as published
570 by the Free Software Foundation; or the Artistic License.
572 See http://dev.perl.org/licenses/ for more information.
577 1; # End of Packaging::Tools