File Coverage

blib/lib/App/MechaCPAN/Deploy.pm
Criterion Covered Total %
statement 127 181 70.1
branch 21 50 42.0
condition 6 13 46.1
subroutine 16 19 84.2
pod 1 4 25.0
total 171 267 64.0


line stmt bran cond sub pod time code
1             package App::MechaCPAN::Deploy;
2              
3 26     26   185 use strict;
  26         56  
  26         907  
4 26     26   146 use warnings;
  26         56  
  26         766  
5 25     25   117 use autodie;
  25         45  
  25         167  
6 25     25   107666 use Carp;
  25         51  
  25         2207  
7 25     25   148 use CPAN::Meta;
  25         52  
  25         676  
8 25     25   118 use List::Util qw/first reduce/;
  25         49  
  25         1346  
9 25     25   134 use File::Temp qw/tempdir/;
  25         50  
  25         1373  
10 25     25   574 use App::MechaCPAN qw/:go/;
  25         55  
  25         31911  
11              
12             our @args = (
13             'skip-perl!',
14             'update!',
15             );
16              
17             sub munge_args
18             {
19 5     5 0 36 my $class = shift;
20 5         29 my $opts = shift;
21 5   100     78 my $file = shift || '.';
22              
23 5 50       74 if ( $file =~ git_re )
24             {
25 0         0 my ( $git_url, $branch ) = $file =~ git_extract_re;
26              
27 0 0       0 if ( !eval { run(qw/git --version/); 1; } )
  0         0  
  0         0  
28             {
29 0         0 croak "Was given a git-looking URL, but could not run git";
30             }
31              
32 0         0 my $remote = 'origin';
33 0         0 my $needs_clone = 1;
34              
35             # Determine if we're in a git directory
36 0 0 0     0 if ( -d '.git' || eval { run(qw/git rev-parse --git-dir/); 1 } )
  0         0  
  0         0  
37             {
38 0     0   0 my $remote_line = first {m/\t $git_url \s/xms} run(qw/git remote -v/);
  0         0  
39 0 0       0 if ($remote_line)
    0          
40             {
41 0         0 ($remote) = $remote_line =~ m/^ ([^\t]*) \t/xms;
42              
43 0         0 success "Found git checkout of of $git_url";
44              
45 0         0 $needs_clone = 0;
46             }
47             elsif ( -d '.git' )
48             {
49             # Only croak if there is a .git here which means we can't clone here
50 0         0 croak "Found git checkout but could not find remote URL $git_url";
51             }
52             }
53              
54 0 0       0 if ($needs_clone)
55             {
56 0         0 info 'git-clone', "Cloning $git_url";
57              
58 0         0 my $dir = tempdir(
59             TEMPLATE => File::Spec->tmpdir . '/mechacpan_XXXXXXXX',
60             CLEANUP => 1
61             );
62              
63             # We use a temp directory and --seperate-git-dir since byt his point
64             # local exists because we're created it and started logging. These
65             # options, plus the git config below, allow us to clone a git repo
66             # without a clean current directory.
67 0         0 run qw/git clone/, '--separate-git-dir=.git', '-n', '-o', $remote,
68             $git_url, $dir;
69 0         0 run qw/git config --unset core.worktree/;
70 0   0     0 $branch //= 'master';
71 0         0 success 'git-clone', "Cloned $git_url";
72             }
73              
74 0 0       0 if ($branch)
75             {
76 0         0 info 'git-branch', "Checking out $branch";
77 0         0 run qw/git checkout/, $branch;
78 0         0 run qw/git fetch/, $remote, $branch;
79 0         0 info 'git-branch', "Merging with remote branch $remote/$branch";
80 0         0 run qw/git merge --ff-only FETCH_HEAD/;
81 0         0 success 'git-branch', "Switched branch to $remote/$branch";
82             }
83              
84 0 0       0 if ( !-f 'cpanfile' )
85             {
86 0         0 my @cpanfiles = glob '*/cpanfile';
87 0 0       0 if ( scalar @cpanfiles == 1 )
88             {
89 0         0 my $dir = $cpanfiles[0];
90 0         0 $dir =~ s[/cpanfile$][]xms;
91 0         0 chdir $dir;
92 0         0 $file = 'cpanfile';
93             }
94             }
95             }
96              
97 5         53 return ($file);
98             }
99              
100             sub go
101             {
102 5     5 1 27 my $class = shift;
103 5         17 my $opts = shift;
104 5   50     39 my $file = shift || '.';
105              
106 5 100       100 if ( -d $file )
107             {
108 3         14 $file = "$file/cpanfile";
109             }
110              
111 5 50       84 if ( !-e $file )
112             {
113 0         0 croak "Could not find cpanfile ($file)";
114             }
115              
116 5 50       87 if ( !-f $file )
117             {
118 0         0 croak "cpanfile must be a regular file";
119             }
120              
121 5         44 my $prereq = parse_cpanfile($file);
122 5         49 my @phases = qw/configure build test runtime/;
123              
124 5         22 my @acc = map {%$_} map { values %{ $prereq->{$_} } } @phases;
  7         39  
  20         37  
  20         112  
125 5         15 my @reqs;
126 5         30 while (@acc)
127             {
128 7         48 push @reqs, [ splice( @acc, 0, 2 ) ];
129             }
130              
131 5 100       97 if ( -f "$file.snapshot" )
132             {
133 2         31 my $snapshot_info = parse_snapshot("$file.snapshot");
134 2         9 my %srcs;
135             my %reqs;
136 2         16 foreach my $dist ( values %$snapshot_info )
137             {
138 2         9 my $src = $dist->{pathname};
139 2         11 foreach my $provide ( keys %{ $dist->{provides} } )
  2         16  
140             {
141 2 50       11 if ( exists $srcs{$provide} )
142             {
143 0         0 error "Found duplicate distribution for $provide in $file.snapshot";
144 0         0 info " $src and $srcs{$provide} both provide the same module";
145 0         0 info " This will cause an error if it is used as a dependency";
146 0         0 $srcs{$provide} = undef;
147 0         0 next;
148             }
149 2         13 $srcs{$provide} = $src;
150             }
151              
152 2         9 foreach my $req ( keys %{ $dist->{requirements} } )
  2         16  
153             {
154 14         34 $reqs{$req} = undef;
155             }
156             }
157              
158 2 50       15 if ( ref $opts->{source} eq 'HASH' )
159             {
160 0         0 %srcs = ( %srcs, %{ $opts->{source} } );
  0         0  
161             }
162 2         28 $opts->{source} = { %reqs, %srcs };
163 2         15 $opts->{update} = 0;
164 2         14 $opts->{'only-sources'} = 1;
165             $opts->{'smart-tests'} = 1
166 2 50       31 if !defined $opts->{'smart-tests'};
167             }
168              
169 5         15 my $result;
170 5   100     69 $opts->{update} //= 0;
171              
172 5 50       23 if ( !$opts->{'skip-perl'} )
173             {
174 0         0 $result = App::MechaCPAN::Perl->go($opts);
175 0 0       0 return $result if $result;
176             }
177              
178 5         119 $result = App::MechaCPAN::Install->go( $opts, @reqs );
179 5 50       29 return $result if $result;
180              
181 5         224 return 0;
182             }
183              
184             my $sandbox_num = 1;
185              
186             sub parse_cpanfile
187             {
188 6     6 0 3555 my $file = shift;
189              
190 6         69 my $result = { runtime => {} };
191              
192 6         60 $result->{current} = $result->{runtime};
193              
194             my $methods = {
195             on => sub
196             {
197 4     4   16 my ( $phase, $code ) = @_;
198 4   50     94 local $result->{current} = $result->{$phase} //= {};
199 4         15 $code->();
200             },
201 0     0   0 feature => sub {...},
202 6         288 };
203              
204 6         54 foreach my $type (qw/requires recommends suggests conflicts/)
205             {
206             $methods->{$type} = sub
207             {
208 15     15   43 my ( $module, $ver ) = @_;
209 15 100       59 if ( $module eq 'perl' )
210             {
211 6         42 $result->{perl} = $ver;
212 6         30 return;
213             }
214 9         80 $result->{current}->{$type}->{$module} = $ver;
215 24         377 };
216             }
217              
218 6         35 foreach my $phase (qw/configure build test author/)
219             {
220             $methods->{ $phase . '_requires' } = sub
221             {
222 0     0   0 my ( $module, $ver ) = @_;
223 0         0 $result->{$phase}->{requires}->{$module} = $ver;
224 24         362 };
225             }
226              
227 6         149 open my $code_fh, '<', $file;
228 6         10505 my $code = do { local $/; <$code_fh> };
  6         33  
  6         152  
229              
230 6         56 my $pkg = __PACKAGE__ . "::Sandbox$sandbox_num";
231 6         17 $sandbox_num++;
232              
233 6         60 foreach my $method ( keys %$methods )
234             {
235 25     25   186 no strict 'refs';
  25         45  
  25         9186  
236 60         92 *{"${pkg}::${method}"} = $methods->{$method};
  60         445  
237             }
238              
239 6         26 local $@;
240 6         72 my $sandbox = join(
241             "\n",
242             qq[package $pkg;],
243             qq[no warnings;],
244             qq[# line 1 "$file"],
245             qq[$code],
246             qq[return 1;],
247             );
248              
249 6     4   847 my $no_error = eval $sandbox;
  4         48  
  4         23  
  4         1618  
250              
251 6 50       47 croak $@
252             unless $no_error;
253              
254 6         18 delete $result->{current};
255              
256 6         106 return $result;
257             }
258              
259             my $snapshot_re = qr/^\# carton snapshot format: version 1\.0/;
260              
261             sub parse_snapshot
262             {
263 3     3 0 4901 my $file = shift;
264              
265 3         17 my $result = {};
266              
267 3         91 open my $snap_fh, '<', $file;
268              
269 3 50       3770 if ( my $line = <$snap_fh> !~ $snapshot_re )
270             {
271 0         0 die "File doesn't looks like a carton snapshot: $file";
272             }
273              
274 3         20 my @stack = ($result);
275 3         27 my $prefix = '';
276 3         27 while ( my $line = <$snap_fh> )
277             {
278 42         69 chomp $line;
279              
280 42 100       456 if ( $line =~ m/^ \Q$prefix\E (\S+?) :? $/xms )
281             {
282 12         44 my $new_depth = {};
283 12         75 $stack[0]->{$1} = $new_depth;
284 12         33 unshift @stack, $new_depth;
285 12         38 $prefix = ' ' x $#stack;
286 12         64 next;
287             }
288              
289 30 100       235 if ( $line =~ m/^ \Q$prefix\E (\S+?) (?: :? \s (.*) )? $/xms )
290             {
291 27         114 $stack[0]->{$1} = $2;
292 27         104 next;
293             }
294              
295 3 50       57 if ( $line !~ m/^ \Q$prefix\E /xms )
296             {
297 3         8 shift @stack;
298 3         21 $prefix = ' ' x $#stack;
299 3         15 redo;
300             }
301              
302 0         0 die "Unable to parse snapshot (line $.)\n";
303             }
304              
305 3         50 return $result->{DISTRIBUTIONS};
306             }
307              
308             1;
309             __END__