File Coverage

blib/lib/App/Prove/Dist.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             ##
2             # name: App::Prove::Dist
3             # abstract: Prove that a Perl Module Dist is OK for CPAN
4             # author: Ingy döt Net <ingy@cpan.org>
5             # license: perl
6             # copyright: 2011
7              
8 1     1   4396 use 5.010;
  1         5  
  1         45  
9              
10 1     1   1010 use local::lib 1.008004 (); # XXX Change dep to lib::core::only
  1         7148  
  1         35  
11 1     1   1296 use App::Prove 3.23 ();
  1         94251  
  1         37  
12 1     1   1247 use App::cpanminus 1.5003 ();
  1         30  
  1         28  
13 1     1   893 use Capture::Tiny 0.11 ();
  1         22320  
  1         35  
14 1     1   997 use IO::All 0.44 ();
  1         14229  
  1         40  
15 1     1   1311 use Module::ScanDeps 1.04 ();
  1         35641  
  1         33  
16 1     1   2329 use Mouse 0.97 ();
  1         57357  
  1         38  
17 1     1   540 use MouseX::App::Cmd 0.08 ();
  0            
  0            
18             use YAML::XS 0.37 ();
19              
20             #-----------------------------------------------------------------------------#
21             package App::Prove::Dist;
22             use Mouse;
23             extends 'MouseX::App::Cmd';
24             use App::Cmd::Setup -app;
25              
26             our $VERSION = '0.02';
27              
28             # XXX Doesn't seem to always work
29             use constant default_command => 'test';
30              
31             #------------------------------------------------------------------------------#
32             # Common options
33             #------------------------------------------------------------------------------#
34             package App::Prove::Dist::common_options;
35             use Mouse::Role;
36              
37             has perl => (
38             is => 'ro',
39             isa => 'ArrayRef[Str]',
40             documentation => 'Version or path of perl to use',
41             );
42              
43             #------------------------------------------------------------------------------#
44             package App::Prove::Dist::Command::test;
45             App::Prove::Dist->import( -command );
46             use Mouse;
47             extends 'App::Prove::Dist::Command';
48             with 'App::Prove::Dist::common_options';
49              
50             use constant abstract => 'Test the Perl module dist from the current directory';
51             use constant usage_desc => "prove-dist test --flags='-v' --perl=<perl-version>";
52              
53             has flags => (
54             is => 'ro',
55             isa => 'Str',
56             default => sub { '' },
57             documentation => "Commandline flags to be passed to 'prove'",
58             );
59              
60             has dirty => (
61             is => 'ro',
62             isa => 'Bool',
63             documentation => "Don't clean up after test",
64             );
65              
66             sub execute {
67             my ($self) = @_;
68             $self->setup();
69             for my $perl ($self->get_perl_list) {
70             $self->test($perl);
71             }
72             $self->cleanup() unless $self->dirty;
73             }
74              
75             #------------------------------------------------------------------------------#
76             package App::Prove::Dist::Command::make;
77             App::Prove::Dist->import( -command );
78             use Mouse;
79             extends 'App::Prove::Dist::Command';
80             with 'App::Prove::Dist::common_options';
81              
82             use constant abstract => 'Make a custom locallib for your dist/perl';
83             use constant usage_desc => 'prove-dist make --perl=<perl-version>';
84              
85             sub execute {
86             my ($self) = @_;
87             $self->setup();
88             for my $perl ($self->get_perl_list) {
89             $self->make($perl);
90             }
91             $self->cleanup();
92             }
93              
94             #------------------------------------------------------------------------------#
95             package App::Prove::Dist::Command::wipe;
96             App::Prove::Dist->import( -command );
97             use Mouse;
98             extends 'App::Prove::Dist::Command';
99             with 'App::Prove::Dist::common_options';
100              
101             use constant abstract => 'Remove the custom locallib for your dist/perl';
102             use constant usage_desc => 'prove-dist wipe --perl=<perl-version>';
103              
104             sub execute {
105             my ($self) = @_;
106             $self->setup();
107             for my $perl ($self->get_perl_list) {
108             $self->wipe($perl);
109             }
110             $self->cleanup();
111             }
112              
113             #------------------------------------------------------------------------------#
114             package App::Prove::Dist::Command::list;
115             App::Prove::Dist->import( -command );
116             use Mouse;
117             extends 'App::Prove::Dist::Command';
118              
119             use constant abstract => 'List your declared deps';
120             use constant usage_desc => 'prove-dist list';
121              
122             sub execute {
123             my ($self) = @_;
124             $self->setup();
125             print YAML::XS::Dump($self->_meta->{requires});
126             $self->cleanup();
127             }
128              
129             #------------------------------------------------------------------------------#
130             package App::Prove::Dist::Command::scan;
131             App::Prove::Dist->import( -command );
132             use Mouse;
133             extends 'App::Prove::Dist::Command';
134              
135             use constant abstract => 'Scan your dist for deps';
136             use constant usage_desc => 'prove-dist scan';
137              
138             use IO::All;
139              
140             sub execute {
141             my ($self) = @_;
142             die "Sorry. 'prove-dist scan' not yet implemented.\n";
143             $self->setup();
144             print YAML::XS::Dump(
145             Module::ScanDeps::scan_deps(
146             files => [map "$_", io('lib')->All_Files],
147             recurse => 0,
148             )
149             );
150             $self->cleanup();
151             }
152              
153             #------------------------------------------------------------------------------#
154             package App::Prove::Dist::Command::perls;
155             App::Prove::Dist->import( -command );
156             use Mouse;
157             extends 'App::Prove::Dist::Command';
158              
159             use constant abstract => 'List your available perls';
160             use constant usage_desc => 'prove-dist perls';
161              
162             use IO::All;
163              
164             sub execute {
165             my ($self) = @_;
166             $self->setup();
167             for my $perl (sort {"$a" cmp "$b"} @{io($self->perls_root)}) {
168             my $name = $perl->filename;
169             my $locallib = $self->get_locallib($name);
170             my $status = -e $locallib
171             ? " -> $locallib"
172             : '';
173             $name =~ s/^perl-// or next;
174             print "$name$status\n";
175             }
176             $self->cleanup();
177             }
178              
179             #------------------------------------------------------------------------------#
180             # Command base class.
181             #------------------------------------------------------------------------------#
182             package App::Prove::Dist::Command;
183             use App::Cmd::Setup -command;
184             use Mouse;
185             extends 'MouseX::App::Cmd::Command';
186              
187             use Cwd;
188             use IO::All;
189             use lib::core::only ();
190              
191             has debug => (
192             is => 'ro',
193             isa => 'Bool',
194             documentation => 'Print debugging info',
195             );
196              
197             has _opts => (is => 'rw');
198             has _args => (is => 'rw');
199             has _src => (is => 'rw', default => sub {'.'});
200             has _meta => (is => 'rw');
201             has _dist_dir => (is => 'rw');
202             has _dist_type => (is => 'rw');
203              
204             sub perlbrew_root {
205             return (
206             $ENV{PERLBREW_ROOT} ||
207             "$ENV{HOME}/perl5/perlbrew"
208             );
209             }
210              
211             sub prove_dist_root {
212             return (
213             $ENV{PERL_PROVE_DIST_ROOT} ||
214             (Cwd::abs_path(perlbrew_root() . "/../prove-dist"))
215             );
216             }
217              
218             sub perls_root {
219             return ( perlbrew_root() . "/perls" );
220             }
221              
222             # use XXX;
223              
224             my $num = 0;
225             sub test {
226             my ($self, $perl) = @_;
227             my $dist = $self->_dist_dir;
228             my $tarball = "$dist.tar.gz";
229             die "'$tarball' not found" unless -e $tarball;
230             my $home = Cwd::cwd();
231             $self->run_cli_cmd("tar xzf $tarball")
232             unless -d $dist;
233             chdir $dist or die "Can't chdir to $dist";
234             io('lib/lib/core/only.pm')->assert->print(io($INC{'lib/core/only.pm'})->all);
235              
236             my $flags = $self->flags;
237             (my $path = $perl) =~ s!/perl$!! or die;
238             local $ENV{PATH} = "$path:$ENV{PATH}";
239             my $locallib = $self->get_locallib($perl);
240             local $ENV{PERL5LIB} = -e $locallib
241             ? "./inc:./lib:$locallib/lib/perl5"
242             : './inc:./lib';
243             local $ENV{PERL5OPT};
244             $self->run_cli_cmd("prove $flags -Mlib::core::only t/");
245             chdir $home or die "Can't chdir '$home'";
246             $self->run_cli_cmd("rm -fr $dist") unless $self->dirty;
247             $num++;
248             print "ok $num - $dist on $perl\n";
249             }
250              
251             sub make {
252             my ($self, $perl) = @_;
253             my $locallib = $self->get_locallib($perl);
254             my $cpanm = `which cpanm`
255             or die "Can't find cpanm";
256             chomp $cpanm;
257             local $ENV{PERL_CPANM_OPT};
258             for my $module (sort keys %{$self->_meta->{requires}}) {
259             next if $module eq 'perl';
260             print "Installing $module\n";
261             my $out = $self->run_cli_cmd("$perl $cpanm -l $locallib $module");
262             print $out if $self->debug;
263             }
264             }
265              
266             sub wipe {
267             my ($self, $perl) = @_;
268             my $locallib = $self->get_locallib($perl);
269             if (not -e $locallib) {
270             warn "Can't wipe '$locallib'. No such directory";
271             return;
272             }
273             $self->run_cli_cmd("rm -fr $locallib");
274             }
275              
276             sub get_locallib {
277             my ($self, $perl) = @_;
278             my $perls_root = $self->perls_root;
279             $perl =~ s/^$perls_root//;
280             $perl =~ s/[^\w\.]+/-/g;
281             $perl =~ s/-bin-perl$//;
282             $perl =~ s/^-?(.*?)-?$/$1/;
283             my $dist = $self->_meta->{name} or die;
284             return prove_dist_root() . "/$dist/$perl";
285             }
286              
287             sub get_perl_list {
288             my ($self) = @_;
289             my $perls_root = $self->perls_root;
290             my $perls = $self->perl || do {
291             my $perl = `which perl`;
292             chomp $perl;
293             [$perl];
294             };
295             for (my $i = 0; $i < @$perls; $i++) {
296             my $perl = $perls->[$i];
297             if ($perl =~ /^\d/) {
298             $perl = "$perls_root/perl-$perl/bin/perl";
299             }
300             die "'$perl' not found" unless -e $perl;
301             $perls->[$i] = $perl;
302             }
303             return @$perls;
304             }
305              
306             sub setup {
307             my ($self) = @_;
308             my $args = $self->_args;
309             if (my $count = @$args) {
310             if ($count == 1) {
311             $self->_src($args->[0]);
312             }
313             else {
314             $self->usage();
315             exit 1;
316             }
317             }
318             my $src = $self->_src;
319             chdir $self->_src or die "Can't chdir to $src";
320             if (-e 'dist.ini') {
321             die "Distzilla not yet supported";
322             }
323             if (not -e "Makefile.PL") {
324             die "'$src' does not have a 'Makefile.PL";
325             }
326             $self->_dist_type('eumm');
327             if (-e 'Makefile') {
328             $self->run_cli_cmd("make purge");
329             }
330             $self->run_cli_cmd("perl Makefile.PL");
331             $self->run_cli_cmd("make manifest");
332             $self->run_cli_cmd("make dist");
333             my ($dist) = glob("*.tar.gz");
334             die "'make dist' seems to have failed"
335             unless $dist;
336             $self->run_cli_cmd("tar xzf $dist");
337             $dist =~ s/\.tar\.gz$// or die;
338             $self->_dist_dir($dist);
339             $self->_meta(YAML::XS::LoadFile("$dist/META.yml"));
340             }
341              
342             sub cleanup {
343             my ($self) = @_;
344             $self->run_cli_cmd("make purge");
345             }
346              
347             sub run_cli_cmd {
348             my ($self, $command) = @_;
349             print "-> $command\n" if $self->debug;
350             my $rc;
351             my $out = Capture::Tiny::capture_merged {
352             $rc = system($command);
353             };
354             die "FAIL '$command':\n$out\n" unless $rc == 0;
355             return $out;
356             }
357              
358             # Hack to suppress extra options I don't care about.
359             around usage=>sub{$a=$_[1]->{usage}{options};@$a=grep{$_->{name}ne'help'}@$a;$_[0]->($_[1])};
360              
361             sub validate_args {
362             my ($self, $opts, $args) = @_;
363             $self->_opts($opts);
364             $self->_args($args);
365             }
366              
367             1;
368              
369             =head1 SYNOPSIS
370              
371             prove-dist # make dist; unzip dist;
372             # test against core-only + custom-locallib
373             prove-dist test --perl=5.14.1 # use a specific perl
374             prove-dist test --perl=5.10.1 --perl=5.12.0 --perl=5.14.2
375             prove-dist list # list your defined dependencies
376             prove-dist scan # scan for your required dependencies
377             prove-dist make --perl=... # make a custom locallib for your dist
378             # and your perl. prove-dist will look
379             # for this lib when you test your dist
380             prove-dist wipe --perl=... # delete the custom locallib
381             prove-dist perls # list perls to test against
382              
383             =head1 STATUS
384              
385             THIS IS A ROUGH DRAFT AND PROOF OF CONCEPT RELEASE! DON'T USE IT YET!
386              
387             Currently:
388              
389             * Only likes Unix
390             * Only likes perlbrew
391             * Many hardcoded assumptions
392             * Scan not implemented
393             * Not fully configurable
394             * Will probably push your grandmother down the stairs
395              
396             Suggestions and patches welcome!
397              
398             =head1 DESCRIPTION
399              
400             When releasing a Perl module distribution, it is good to test it on a clean
401             perl installation and on muliple versions of installed perl. Many modules have
402             dependency modules, so a truly clean Perl won't work. You can use locallib to
403             work around that. You'll need to set up a locallib for each version of perl,
404             for each module you release.
405              
406             App::Prove::Dist does all this for you:
407              
408             cd your-dist-dir
409             prove-dist perls # Get a list of perls to use
410             prove-dist make --perl=5.14.2 # Create a custom locallib for a perl
411             prove-dist perls # List now shows locallib
412             prove-dist test --perl=5.14.2 # Prove against clean perl + new locallib
413              
414             C<prove-dist> will use C<lib-core-only> and your custom locallib to prove your
415             C<t/> tests, so you can be more certain it will pass cpantesters.