File Coverage

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


line stmt bran cond sub pod time code
1             package App::MechaCPAN::Deploy;
2              
3 23     23   185 use strict;
  23         65  
  23         923  
4 23     23   157 use warnings;
  23         63  
  23         816  
5 22     22   121 use autodie;
  22         54  
  22         175  
6 22     22   120696 use Carp;
  22         110  
  22         1939  
7 22     22   155 use CPAN::Meta;
  22         52  
  22         852  
8 22     22   127 use List::Util qw/first reduce/;
  22         51  
  22         1295  
9 22     22   420 use File::Temp qw/tempdir/;
  22         52  
  22         869  
10 22     22   135 use App::MechaCPAN qw/:go/;
  22         541  
  22         31169  
11              
12             our @args = (
13             'skip-perl!',
14             'update!',
15             );
16              
17             sub munge_args
18             {
19 5     5 0 17 my $class = shift;
20 5         17 my $opts = shift;
21 5   100     46 my $file = shift || '.';
22              
23 5 50       36 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         34 return ($file);
98             }
99              
100             sub go
101             {
102 5     5 1 22 my $class = shift;
103 5         34 my $opts = shift;
104 5   50     32 my $file = shift || '.';
105              
106 5 100       88 if ( -d $file )
107             {
108 3         19 $file = "$file/cpanfile";
109             }
110              
111 5 50       78 if ( !-e $file )
112             {
113 0         0 croak "Could not find cpanfile ($file)";
114             }
115              
116 5 50       77 if ( !-f $file )
117             {
118 0         0 croak "cpanfile must be a regular file";
119             }
120              
121 5         34 my $prereq = parse_cpanfile($file);
122 5         39 my @phases = qw/configure build test runtime/;
123              
124 5         15 my @acc = map {%$_} map { values %{ $prereq->{$_} } } @phases;
  7         40  
  20         38  
  20         87  
125 5         20 my @reqs;
126 5         26 while (@acc)
127             {
128 7         47 push @reqs, [ splice( @acc, 0, 2 ) ];
129             }
130              
131 5 100       117 if ( -f "$file.snapshot" )
132             {
133 2         16 my $snapshot_info = parse_snapshot("$file.snapshot");
134 2         14 my %srcs;
135             my %reqs;
136 2         16 foreach my $dist ( values %$snapshot_info )
137             {
138 2         12 my $src = $dist->{pathname};
139 2         7 foreach my $provide ( keys %{ $dist->{provides} } )
  2         14  
140             {
141 2 50       15 if ( exists $srcs{$provide} )
142             {
143 0         0 die
144             "Found dumplicate distributions ($src and $srcs{$provide}) that provides the same module ($provide)\n";
145             }
146 2         8 $srcs{$provide} = $src;
147             }
148              
149 2         7 foreach my $req ( keys %{ $dist->{requirements} } )
  2         11  
150             {
151 14         26 $reqs{$req} = undef;
152             }
153             }
154              
155 2 50       11 if ( ref $opts->{source} eq 'HASH' )
156             {
157 0         0 %srcs = ( %srcs, %{ $opts->{source} } );
  0         0  
158             }
159 2         16 $opts->{source} = { %reqs, %srcs };
160 2         11 $opts->{update} = 0;
161 2         11 $opts->{'only-sources'} = 1;
162             $opts->{'smart-tests'} = 1
163 2 50       29 if !defined $opts->{'smart-tests'};
164             }
165              
166 5         17 my $result;
167 5   100     50 $opts->{update} //= 0;
168              
169 5 50       28 if ( !$opts->{'skip-perl'} )
170             {
171 0         0 $result = App::MechaCPAN::Perl->go($opts);
172 0 0       0 return $result if $result;
173             }
174              
175 5         165 $result = App::MechaCPAN::Install->go( $opts, @reqs );
176 5 50       25 return $result if $result;
177              
178 5         218 return 0;
179             }
180              
181             my $sandbox_num = 1;
182              
183             sub parse_cpanfile
184             {
185 6     6 0 5955 my $file = shift;
186              
187 6         49 my $result = { runtime => {} };
188              
189 6         48 $result->{current} = $result->{runtime};
190              
191             my $methods = {
192             on => sub
193             {
194 4     4   14 my ( $phase, $code ) = @_;
195 4   50     55 local $result->{current} = $result->{$phase} //= {};
196 4         17 $code->();
197             },
198 0     0   0 feature => sub {...},
199 6         137 };
200              
201 6         37 foreach my $type (qw/requires recommends suggests conflicts/)
202             {
203             $methods->{$type} = sub
204             {
205 15     15   49 my ( $module, $ver ) = @_;
206 15 100       61 if ( $module eq 'perl' )
207             {
208 6         28 $result->{perl} = $ver;
209 6         23 return;
210             }
211 9         52 $result->{current}->{$type}->{$module} = $ver;
212 24         191 };
213             }
214              
215 6         25 foreach my $phase (qw/configure build test author/)
216             {
217             $methods->{ $phase . '_requires' } = sub
218             {
219 0     0   0 my ( $module, $ver ) = @_;
220 0         0 $result->{$phase}->{requires}->{$module} = $ver;
221 24         314 };
222             }
223              
224 6         63 open my $code_fh, '<', $file;
225 6         10365 my $code = do { local $/; <$code_fh> };
  6         35  
  6         132  
226              
227 6         54 my $pkg = __PACKAGE__ . "::Sandbox$sandbox_num";
228 6         27 $sandbox_num++;
229              
230 6         49 foreach my $method ( keys %$methods )
231             {
232 22     22   197 no strict 'refs';
  22         54  
  22         9226  
233 60         140 *{"${pkg}::${method}"} = $methods->{$method};
  60         426  
234             }
235              
236 6         26 local $@;
237 6         55 my $sandbox = join(
238             "\n",
239             qq[package $pkg;],
240             qq[no warnings;],
241             qq[# line 1 "$file"],
242             qq[$code],
243             qq[return 1;],
244             );
245              
246 6     4   712 my $no_error = eval $sandbox;
  4         51  
  4         14  
  4         1458  
247              
248 6 50       46 croak $@
249             unless $no_error;
250              
251 6         21 delete $result->{current};
252              
253 6         94 return $result;
254             }
255              
256             my $snapshot_re = qr/^\# carton snapshot format: version 1\.0/;
257              
258             sub parse_snapshot
259             {
260 3     3 0 6959 my $file = shift;
261              
262 3         13 my $result = {};
263              
264 3         29 open my $snap_fh, '<', $file;
265              
266 3 50       3844 if ( my $line = <$snap_fh> !~ $snapshot_re )
267             {
268 0         0 die "File doesn't looks like a carton snapshot: $file";
269             }
270              
271 3         18 my @stack = ($result);
272 3         14 my $prefix = '';
273 3         25 while ( my $line = <$snap_fh> )
274             {
275 42         142 chomp $line;
276              
277 42 100       549 if ( $line =~ m/^ \Q$prefix\E (\S+?) :? $/xms )
278             {
279 12         43 my $new_depth = {};
280 12         67 $stack[0]->{$1} = $new_depth;
281 12         35 unshift @stack, $new_depth;
282 12         41 $prefix = ' ' x $#stack;
283 12         64 next;
284             }
285              
286 30 100       281 if ( $line =~ m/^ \Q$prefix\E (\S+?) (?: :? \s (.*) )? $/xms )
287             {
288 27         138 $stack[0]->{$1} = $2;
289 27         139 next;
290             }
291              
292 3 50       44 if ( $line !~ m/^ \Q$prefix\E /xms )
293             {
294 3         15 shift @stack;
295 3         17 $prefix = ' ' x $#stack;
296 3         12 redo;
297             }
298              
299 0         0 die "Unable to parse snapshot (line $.)\n";
300             }
301              
302 3         48 return $result->{DISTRIBUTIONS};
303             }
304              
305             1;
306             __END__