File Coverage

bin/plx
Criterion Covered Total %
statement 216 390 55.3
branch 57 184 30.9
condition 10 53 18.8
subroutine 55 81 67.9
pod 0 55 0.0
total 338 763 44.3


line stmt bran cond sub pod time code
1             #!perl
2              
3             package App::plx;
4              
5             our $VERSION = '0.902002'; # 0.902.2
6              
7             $VERSION = eval $VERSION;
8              
9 1     1   1614 use strict;
  1         2  
  1         27  
10 1     1   4 use warnings;
  1         1  
  1         21  
11 1     1   3 use File::Spec;
  1         1  
  1         17  
12 1     1   3 use File::Basename ();
  1         1  
  1         10  
13 1     1   3 use Cwd ();
  1         1  
  1         9  
14 1     1   441 use lib ();
  1         574  
  1         18  
15 1     1   5 use Config;
  1         1  
  1         22  
16 1     1   3 use File::Which ();
  1         1  
  1         11  
17 1     1   3 use List::Util ();
  1         2  
  1         8  
18 1     1   488 use local::lib ();
  1         5272  
  1         73  
19              
20             BEGIN {
21 1     1   27 our %orig_env = %ENV;
22 1 50       3 { local $0 = $0 eq '-' ? 'plx' : $0;
  1         23  
23 1         7 local::lib->import('--deactivate-all') }
24 1         750 delete @ENV{grep /^PERL/, keys %ENV}
25             }
26 1     1   5 no lib @Config{qw(sitearch sitelibexp)};
  1         1  
  1         5  
27              
28             my $fs = 'File::Spec';
29              
30             my $self = do {
31             package Perl::Layout::Executor::_self;
32 301     301   1751 sub self { package DB; () = caller(2); $DB::args[0] }
  301         3626  
33 1     1   303 use overload '%{}' => sub { self }, fallback => 1;
  1     187   1  
  1         8  
  187         250  
34             sub AUTOLOAD {
35 114     114   845 my ($meth) = (our $AUTOLOAD =~ /([^:]+)$/);
36 114         193 self->$meth(@_[1..$#_]);
37             }
38       0     sub DESTROY {}
39             bless([], __PACKAGE__);
40             };
41              
42 0     0   0 sub barf { die "$_[0]\n" }
43              
44 0     0   0 sub stderr { warn "$_[0]\n" }
45              
46 0     0   0 sub say { print "$_[0]\n" }
47              
48             sub new {
49 33     33 0 115869 my $class = shift;
50 33 0 33     273 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  0 50       0  
51             }
52              
53             sub layout_base_dir {
54 178   66 178 0 627 $self->{layout_base_dir} //= $self->_build_layout_base_dir
55             }
56             sub layout_perl {
57 9   66 9 0 20 $self->{layout_perl} //= $self->_build_layout_perl
58             }
59              
60             sub _build_layout_base_dir {
61 28     28   1646 my @parts = $fs->splitdir(Cwd::realpath(Cwd::getcwd()));
62 28         68 my $cand;
63 28         31 my $reason = '';
64 28         102 while (@parts > 1) { # go back to one step before root at most
65 76         374 $cand = $fs->catdir(@parts);
66 76 100       1109 return $cand if -d $fs->catdir($cand, '.plx');
67 56 50       572 if (-d $fs->catdir($cand, '.git')) { # don't escape current repository
68 0         0 $reason = ' due to .git directory';
69 0         0 last;
70             }
71 56         143 pop @parts;
72             }
73 8         26 barf "Couldn't find .plx directory (stopped searching at ${cand}${reason})";
74             }
75              
76             sub _build_layout_perl {
77 6     6   24 my $perl_bin = $self->read_config_entry('perl');
78 6 50       23 unless ($perl_bin) {
79 0         0 my $perl_spec = $self->read_config_entry('perl.spec');
80 0 0       0 barf "No perl and no perl.spec in config" unless $perl_spec;
81 0         0 $self->run_config_perl_set($perl_spec);
82 0         0 $perl_bin = $self->read_config_entry('perl');
83 0 0       0 barf "Rehydration of perl from perl.spec failed" unless $perl_bin;
84             }
85 6 50       96 barf "perl binary ${perl_bin} not executable" unless -x $perl_bin;
86 6         66 return $perl_bin;
87             }
88              
89             sub layout_libspec_config {
90 4     4 0 16 [ grep $_->[1],
91             map [ $_, $self->read_config_entry([ libspec => $_ ]) ],
92             $self->list_config_names('libspec') ];
93             }
94              
95             sub layout_lib_specs {
96 3     3 0 23 my $base_dir = $self->layout_base_dir;
97 3     9   86 local *_ = sub { Cwd::realpath($fs->rel2abs(shift, $base_dir)) };
  9         1136  
98             [ map [ ($_->[0] =~ /\.([^.]+)$/), _($_->[1]) ],
99 3         11 @{$self->layout_libspec_config} ];
  3         18  
100             }
101              
102             sub layout_file {
103 99     99 0 182 my ($self, @path) = @_;
104 99         272 $fs->catfile($self->layout_base_dir, @path);
105             }
106              
107             sub layout_dir {
108 71     71 0 182 my ($self, @path) = @_;
109 71         132 $fs->catdir($self->layout_base_dir, @path);
110             }
111              
112             sub ensure_layout_config_dir {
113 17 50   17 0 63 barf ".plx directory does not exist"
114             unless -d $self->layout_dir('.plx');
115 10         74 my $format = $self->read_config_entry('format');
116 10 50       30 barf ".plx directory has no format specifier" unless $format;
117 10 50       27 barf ".plx format ${format} unknown" unless $format eq '1';
118             }
119              
120 96     96 0 182 sub layout_config_file { shift->layout_file('.plx', @_) }
121 51     51 0 142 sub layout_config_dir { shift->layout_dir('.plx', @_) }
122              
123             sub write_config_entry {
124 68     68 0 142 my ($self, $path, $value) = @_;
125 68 100       179 my $file = $self->layout_config_file(ref($path) ? @$path : $path);
126 68 50       3875 open my $wfh, '>', $file or die "Couldn't open ${file}: $!";
127 68         2826 print $wfh "${value}\n";
128             }
129              
130             sub clear_config_entry {
131 0     0 0 0 my ($self, $path) = @_;
132 0 0       0 my $file = $self->layout_config_file(ref($path) ? @$path : $path);
133 0 0 0     0 unlink($file) or barf "Failed to unlink ${file}: $!" if -e $file;
134             }
135              
136             sub read_config_entry {
137 28     28 0 80 my ($self, $path) = @_;
138 28 100       98 my $file = $self->layout_config_file(ref($path) ? @$path : $path);
139 28 50       428 return undef unless -f $file;
140 28 50       1027 open my $rfh, '<', $file or die "Couldn't open ${file}: $!";
141 28         463 chomp(my $value = <$rfh>);
142 28         533 return $value;
143             }
144              
145             sub list_config_names {
146 7     7 0 44 my ($self, $path) = @_;
147 7 50       62 my $dir = $self->layout_config_dir(ref($path) ? @$path : $path);
148 7 100       284 return () unless -d $dir;
149 4 50       274 opendir my($dh), $dir or die "Couldn't opendir ${dir}: $!";
150 4         628 return grep -f $fs->catfile($dir, $_), sort readdir($dh);
151             }
152              
153             sub slurp_command {
154 3     3 0 10 my ($self, @cmd) = @_;
155 3 50       8842 open my $slurp_fh, '-|', @cmd
156             or barf "Failed to start command (".join(' ', @cmd)."): $!";
157 3         29059 chomp(my @slurp = <$slurp_fh>);
158 3         498 return @slurp;
159             }
160              
161             sub prepend_env {
162 0     0 0 0 my ($self, $env, @parts) = @_;
163 0   0     0 $ENV{$env} = join(':', @parts, $ENV{$env}||());
164             }
165              
166             sub setup_env_for_ll {
167 0     0 0 0 my ($self, $path) = @_;
168 0 0       0 local $0 = $0 eq '-' ? 'plx' : $0;
169 0         0 local::lib->import($path);
170             }
171              
172             sub setup_env_for_dir {
173 0     0 0 0 my ($self, $path) = @_;
174 0         0 $self->prepend_env(PERL5LIB => $path);
175             }
176              
177             sub setup_env {
178 3     3 0 24 $ENV{PERL_PLX_BASE} = $self->layout_base_dir;
179 3         33 my ($site_libs) = $self->slurp_command(
180             $self->layout_perl, '-MConfig', '-e',
181             'print join(",", @Config{qw(sitearch sitelibexp)})'
182             );
183 3         204 $ENV{PERL5OPT} = '-M-lib='.$site_libs;
184             $ENV{$_} = $self->read_config_entry([ env => $_ ])
185 3         124 for $self->list_config_names('env');
186 3         27 my $perl_dirname = File::Basename::dirname($self->layout_perl);
187 3         14 our %orig_env;
188 3 50       43 unless (grep $_ eq $perl_dirname, split ':', $orig_env{PATH}) {
189 0         0 $self->prepend_env(PATH => $perl_dirname);
190             }
191 3         12 foreach my $lib_spec (@{$self->layout_lib_specs}) {
  3         37  
192 9         32 my ($type, $path) = @$lib_spec;
193 9 50 33     139 next unless $path and -d $path;
194 0         0 $self->${\"setup_env_for_${type}"}($path);
  0         0  
195             }
196 3         13 return;
197             }
198              
199 2     2 0 6 sub cmd_search_path { qw(.plx/cmd dev bin) }
200              
201             sub run_action_commands {
202 2     2 0 3 my ($self, $filter) = @_;
203 2         5 $self->ensure_layout_config_dir;
204 1         2 my @commands;
205             my %seen;
206 1         4 foreach my $dirname ($self->cmd_search_path) {
207 3 50       8 next unless -d (my $dir = $self->layout_dir($dirname));
208 0 0       0 opendir my ($dh), $dir or barf "Couldn't open ${dir}: $!";
209 0         0 foreach my $entry (sort readdir($dh)) {
210 0 0       0 next if $entry =~ /^\.+$/;
211 0         0 my $file = $self->layout_file($dirname, $entry);
212 0 0       0 next unless -f $file;
213 0 0       0 unless ($seen{$entry}++) {
214 0         0 push @commands, [ $entry, "${dirname}/${entry}" ];
215             }
216             }
217             }
218 1         3 my $path = do { local $ENV{PATH} = ''; $self->setup_env; $ENV{PATH} };
  1         7  
  1         3  
  1         10  
219 1         11 foreach my $dir (split ':', $path) {
220 0         0 opendir my ($dh), $dir;
221 0         0 foreach my $entry (sort readdir($dh)) {
222 0 0       0 next if $entry =~ /^\.+$/;
223 0         0 my $file = $fs->catfile($dir, $entry);
224 0 0       0 next unless -x $file;
225 0 0       0 push @commands, [ $entry, $file ] unless $seen{$entry}++;
226             }
227             }
228 1 50       6 if ($filter) {
229 0 0       0 my $match = $filter =~ m{^/(.+)/$} ? $1 : qr/^\Q${filter}/;
230 0         0 @commands = grep { $_->[0] =~ $match } @commands;
  0         0  
231             }
232 1         8 my $max = List::Util::max(map length($_->[0]), @commands);
233 1         4 my $base = $self->layout_base_dir;
234 1         3 my $home = $ENV{HOME};
235 1         59 foreach my $command (@commands) {
236 0         0 my ($name, $path) = @$command;
237 0         0 $path =~ s/^\Q${base}\///;
238 0 0       0 $path =~ s/^\Q${home}/~/ if $home;
239 0         0 say sprintf("%-${max}s %s", $name, $path);
240             }
241             }
242              
243             sub run_action_bareinit {
244 11     11 0 24 my ($self, $perl) = @_;
245 11   33     288 my $dir = $fs->catdir($self->{layout_base_dir}||Cwd::getcwd(), '.plx');
246 11 50       196 if (-d $dir) {
247 0 0       0 if ($perl) {
248 0         0 stderr <
249             .plx already initialised - if you wanted to set the perl to ${perl} run:
250              
251             plx --config perl set ${perl}
252             END
253             }
254 0         0 return;
255             }
256 11 50       557 mkdir($dir) or barf "Couldn't create ${dir}: $!";
257 11   50     105 $self->run_config_perl_set($perl||'perl');
258 11         42 $self->write_config_entry(format => 1);
259             }
260              
261             sub run_action_userinit {
262 0     0 0 0 my ($self, @args) = @_;
263 0 0 0     0 my @perl = (
264             (@args and !ref($args[0]) and $args[0] ne '[')
265             ? shift(@args)
266             : ()
267             );
268 0 0       0 barf "--userinit requires \$HOME to be set" unless $ENV{HOME};
269             $self->run_action_base(
270             $ENV{HOME},
271 0 0       0 '--multi' =>
272             [ '--bareinit', @perl ],
273             [ qw(--config libspec add 25.perl5.ll perl5) ],
274             (@args ? [ '--multi', @args ] : ()),
275             );
276             }
277              
278             sub run_action_userstrap {
279 0     0 0 0 my ($self, @args) = @_;
280 0 0 0     0 my @perl = (
281             (@args and !ref($args[0]) and $args[0] ne '[')
282             ? shift(@args)
283             : ()
284             );
285 0         0 $self->run_action_userinit(
286             @perl,
287             [ '--installself' ],
288             [ '--installenv' ],
289             @args
290             );
291             }
292              
293             sub run_action_installself {
294 0     0 0 0 my $last_ll;
295 0         0 foreach my $lib_spec (@{$self->layout_lib_specs}) {
  0         0  
296 0         0 my ($type, $path) = @$lib_spec;
297 0 0       0 $last_ll = $path if $type eq 'll';
298             }
299 0 0       0 barf "No local::lib in libspec config" unless $last_ll;
300 0         0 $self->run_action_cpanm(
301             "-l${last_ll}", '-n',
302             qw(App::cpanminus App::plx)
303             );
304             }
305              
306             sub run_action_installenv {
307 0     0 0 0 $self->ensure_layout_config_dir;
308             barf "--installenv action currently assumes bash"
309 0 0       0 unless $ENV{SHELL} =~ /bash/;
310             barf "Couldn't find .bashrc"
311 0 0       0 unless -f (my $bashrc = $fs->catfile($ENV{HOME}, ".bashrc"));
312             my $plx_bin = do {
313             local %ENV = our %orig_env;
314             File::Which::which('plx-packed');
315 0   0     0 } || do {
316             local %ENV = %ENV;
317             $self->setup_env;
318             File::Which::which('plx-packed');
319             };
320 0 0       0 barf "Couldn't find plx in PATH" unless $plx_bin;
321             {
322 0 0       0 open my $fh, '<', $bashrc or die "Couldn't open ${bashrc} to read: $!";
  0         0  
323 0 0       0 if (my ($line) = grep /plx-packed/, <$fh>) {
324 0         0 chomp($line);
325 0         0 stderr("Found line in .bashrc: $line");
326 0         0 return;
327             }
328             }
329 0         0 my $base = $self->layout_base_dir;
330 0         0 stderr("Appending to .bashrc");
331 0 0       0 open my $fh, '>>', $bashrc or die "Couldn't open ${bashrc} to append: $!";
332 0         0 print $fh "\neval \$(${plx_bin} --base ${base} --env)\n";
333             }
334              
335             sub run_action_init {
336 11     11 0 20 my ($self, $perl) = @_;
337 11         45 $self->run_action_bareinit($perl);
338 11         43 my $libspec_dir = $self->layout_config_dir('libspec');
339 11 50       611 mkdir($libspec_dir) or barf "Couldn't create ${libspec_dir}: $!";
340 11         152 $self->run_config_libspec_add(@$_) for (
341             [ '25-local.ll' => 'local' ],
342             [ '50-devel.ll' => 'devel' ],
343             [ '75-lib.dir' => 'lib' ],
344             );
345             }
346              
347             sub _which {
348 3     3   5 my ($self, @args) = @_;
349 3         5 $self->ensure_layout_config_dir;
350              
351 2         2 my @env;
352              
353 2         7 push @env, shift @args while $args[0] =~ /^\w+=/;
354              
355 2         3 my $cmd = shift @args;
356              
357 2 50       4 barf "--cmd " unless $cmd;
358              
359 2 50       15 if ($fs->file_name_is_absolute($cmd)) {
360 0         0 return (exec => @env => $cmd => @args);
361             }
362              
363 2 100       4 if ($cmd eq 'perl') {
364 1         4 return (perl => @env => @args);
365             }
366              
367 1 50       4 if ($cmd =~ /^-/) {
368 0         0 my @optargs = ($cmd, @args);
369 0         0 foreach my $optarg (@optargs) {
370 0 0       0 next if $optarg =~ /^-/;
371 0         0 foreach my $dirname ($self->cmd_search_path) {
372 0 0       0 if (-f (my $file = $self->layout_file($dirname => $optarg))) {
373 0         0 $optarg = $file;
374 0         0 last;
375             }
376             }
377 0         0 last;
378             }
379 0         0 return (perl => @env => @optargs);
380             }
381              
382 1         3 foreach my $dirname ($self->cmd_search_path) {
383 3 50       9 if (-f (my $file = $self->layout_file($dirname => $cmd))) {
384 0         0 return (perl => @env => $file, @args);
385             }
386             }
387              
388 1         7 return (exec => @env => $cmd, @args);
389             }
390              
391             sub run_action_which {
392 0     0 0 0 my ($self, @args) = @_;
393 0         0 my ($action, @call) = $self->_which(@args);
394 0         0 say join(' ', 'plx', "--${action}", @call);
395             }
396              
397             sub run_action_cmd {
398 3     3 0 6 my ($self, @args) = @_;
399 3         6 my ($action, @call) = $self->_which(@args);
400 2         4 $self->${\"run_action_${action}"}(@call);
  2         9  
401             }
402              
403             sub run_action_perl {
404 4     4 0 10 my ($self, @call) = @_;
405 4         9 $self->ensure_layout_config_dir;
406 3 100       15 return $self->show_config_perl unless @call;
407 1         3 my @env;
408 1         6 push @env, shift @call while $call[0] =~ /^\w+=/;
409 1         3 $self->run_action_exec(@env, $self->layout_perl, @call);
410             }
411              
412             sub run_action_exec {
413 0     0   0 my ($self, @exec) = @_;
414 0         0 $self->ensure_layout_config_dir;
415 0         0 $self->setup_env;
416              
417 0   0     0 shift @exec and $ENV{$1} = $2 while $exec[0] =~ /^(\w+)=(.*)$/;
418              
419 0 0       0 exec(@exec) or barf "exec of (".join(' ', @exec).") failed: $!";
420             }
421              
422             sub find_cpanm {
423 0     0 0 0 local %ENV = our %orig_env;
424 0 0       0 barf "Couldn't find cpanm in \$PATH"
425             unless my $cpanm = File::Which::which('cpanm');
426 0         0 $cpanm;
427             }
428              
429             sub run_action_cpanm {
430 1     1 0 2 my ($self, @args) = @_;
431 1         2 $self->ensure_layout_config_dir;
432 0         0 my @cpanm = $self->find_cpanm;
433 0 0 0     0 unless (@args and $args[0] =~ /^-[lL]/) {
434 0         0 barf "--cpanm args must start with -l or -L to specify target local::lib";
435             }
436 0         0 $self->setup_env;
437 0         0 system($self->layout_perl, @cpanm, @args);
438             }
439              
440             sub run_action_config {
441 3     3 0 6 my ($self, $config, @args) = @_;
442 3         8 $self->ensure_layout_config_dir;
443 2 50       5 unless ($config) {
444 0         0 say "# perl";
445 0         0 $self->show_config_perl;
446 0         0 say "# libspec";
447 0         0 $self->show_config_libspec;
448 0 0       0 if ($self->list_config_names('env')) {
449 0         0 say "# env";
450 0         0 $self->show_config_env;
451             }
452 0         0 return;
453             }
454 2 50       12 barf "Unknown config key ${config}"
455             unless my $show = $self->can("show_config_${config}");
456 2 100       8 return $self->$show unless @args;
457 1 50       8 if (my $code = $self->can("run_config_${config}")) {
458 0         0 return $self->$code(@args);
459             }
460 1         3 my ($subcmd, @rest) = @args;
461 1 50       7 barf "Invalid subcommand ${subcmd} for config key ${config}"
462             unless my $code = $self->can("run_config_${config}_${subcmd}");
463 1         3 return $self->$code(@rest);
464             }
465              
466 2     2 0 15 sub show_config_perl { say $self->layout_perl }
467              
468             sub resolve_perl_via_perlbrew {
469 0     0 0 0 my ($self, $perl) = @_;
470 0         0 stderr "Resolving perl '${perl}' via perlbrew";
471 0         0 local %ENV = our %orig_env;
472 0 0       0 barf "Couldn't find perlbrew in \$PATH"
473             unless my $perlbrew = File::Which::which('perlbrew');
474 0         0 my @list = $self->slurp_command($perlbrew, 'list');
475 0 0       0 barf join(
476             "\n", "No such perlbrew perl '${perl}', choose from:\n", @list, ''
477             ) unless grep $_ eq $perl, map /(\S+)/, @list;
478 0         0 my ($perl_path) = $self->slurp_command(
479             $perlbrew, qw(exec --with), $perl, qw(perl -e), 'print $^X'
480             );
481 0         0 return $perl_path;
482             }
483              
484             sub run_config_perl_set {
485 12     12 0 33 my ($self, $new_perl) = @_;
486 12 50       23 barf "plx --config perl set " unless $new_perl;
487 12         27 my $perl_spec = $new_perl;
488 12 100       54 unless ($new_perl =~ m{/}) {
489 11 50       27 $new_perl = "perl${new_perl}" if $new_perl =~ /^5/;
490 11         21 $new_perl =~ s/perl-5/perl5/; # perlbrew name to perl binary
491 11         90 require File::Which;
492 11         70 stderr "Resolving perl '${new_perl}' via PATH";
493 11 50       82 if (my $resolved = File::Which::which($new_perl)) {
494 11         1712 $new_perl = $resolved;
495             } else {
496 0         0 $new_perl =~ s/^perl5/perl-5/; # perl binary to perlbrew name
497 0         0 $new_perl = $self->resolve_perl_via_perlbrew($new_perl);
498             }
499             }
500 12 50       146 barf "Not executable: $new_perl" unless -x $new_perl;
501 12         55 $self->write_config_entry('perl.spec' => $perl_spec);
502 12         50 $self->write_config_entry(perl => $new_perl);
503             }
504              
505             sub show_config_libspec {
506 1     1 0 3 my @ent = @{$self->layout_libspec_config};
  1         6  
507 1         7 my $max = List::Util::max(map length($_->[0]), @ent);
508 1         10 say sprintf("%-${max}s %s", @$_) for @ent;
509             }
510              
511             sub run_named_config_add {
512 33     33 0 81 my ($self, $type, $name, $value) = @_;
513 33 50 33     136 barf "plx --config ${type} add "
514             unless $name and defined($value);
515 33 50       60 unless (-d (my $dir = $self->layout_config_dir($type))) {
516 0 0       0 mkdir($dir) or die "Couldn't make config dir ${dir}: $!";
517             }
518 33         163 $self->write_config_entry([ $type => $name ], $value);
519             }
520              
521             sub run_named_config_del {
522 0     0 0 0 my ($self, $type, $name) = @_;
523 0 0       0 barf "plx --config ${type} dev " unless $name;
524 0         0 $self->clear_config_entry([ $type => $name ]);
525             }
526              
527 33     33 0 97 sub run_config_libspec_add { shift->run_named_config_add(libspec => @_) }
528 0     0 0 0 sub run_config_libspec_del { shift->run_named_config_del(libspec => @_) }
529              
530             sub show_config_env {
531 0     0 0 0 my $max = List::Util::max(
532             map length, my @names = $self->list_config_names('env')
533             );
534             say sprintf("%-${max}s %s", $_, $self->read_config_entry([ env => $_ ]))
535 0         0 for @names;
536             }
537              
538 0     0 0 0 sub run_config_env_add { shift->run_named_config_add(env => @_) }
539 0     0 0 0 sub run_config_env_del { shift->run_named_config_del(env => @_) }
540              
541             sub show_env {
542 4     4 0 10 my ($self, $env) = @_;
543 4         11 $self->ensure_layout_config_dir;
544 2         26 local $ENV{$env} = '';
545 2         12 $self->setup_env;
546 2         226 say $_ for split ':', $ENV{$env};
547             }
548              
549 2     2 0 15 sub run_action_libs { $self->show_env('PERL5LIB') }
550              
551 2     2 0 12 sub run_action_paths { $self->show_env('PATH') }
552              
553             sub run_action_env {
554 0     0 0 0 $self->ensure_layout_config_dir;
555 0         0 $self->setup_env;
556 0         0 my @env_change;
557 0         0 our %orig_env;
558 0         0 foreach my $key (sort(keys %{{ %orig_env, %ENV }})) {
  0         0  
559 0         0 my ($oval, $eval) = ($orig_env{$key}, $ENV{$key});
560 0 0 0     0 if (!defined($eval) or ($oval//'') ne $eval) {
      0        
561 0         0 push @env_change, [ $key, $eval ];
562             }
563             }
564 0         0 my $shelltype = local::lib->guess_shelltype;
565 0         0 my $shellbuild = "build_${shelltype}_env_declaration";
566 0         0 foreach my $change (@env_change) {
567 0         0 print +local::lib->$shellbuild(@$change);
568             }
569             }
570              
571             sub run_action_help {
572 1     1 0 10 require Pod::Usage;
573 1         5 Pod::Usage::pod2usage();
574             }
575              
576             sub run_action_version {
577 1     1 0 47 say sprintf "%f", $VERSION;
578             }
579              
580             sub run_action_base {
581 1     1 0 2 my ($self, $base, @chain) = @_;
582 1 50       3 unless ($base) {
583 1         4 say $self->layout_base_dir;
584 0         0 return;
585             }
586 0 0       0 barf "--base " unless @chain;
587 0         0 $self->new({ layout_base_dir => $base })->run(@chain);
588             }
589              
590             sub _parse_multi {
591 0     0   0 my ($self, @args) = @_;
592 0         0 my @multi;
593 0         0 MULTI: while (@args) {
594 0 0       0 barf "Expected multi arg [, got: $args[0]" unless $args[0] eq '[';
595 0         0 shift @args;
596 0         0 my @action;
597 0         0 while (my $el = shift @args) {
598 0 0 0     0 push @multi, \@action and next MULTI if $el eq ']';
599 0         0 push @action, $el;
600             }
601 0         0 barf "Missing closing ] for multi";
602             }
603 0         0 return @multi;
604             }
605              
606             sub run_action_multi {
607 0     0 0 0 my ($self, @args) = @_;
608 0 0 0     0 return $self->run_multi(@args) if @args and ref($args[0]);
609 0         0 my @multi = $self->_parse_multi(@args);
610 0         0 $self->run_multi(@multi);
611             }
612              
613             sub run_multi {
614 0     0 0 0 my ($self, @multi) = @_;
615 0         0 foreach my $multi (@multi) {
616 0 0       0 my @debug_multi = map +(ref($_) ? ('[', @$_, ']') : $_), @$multi;
617 0         0 stderr '# '.join(' ', plx => @debug_multi);
618 0         0 $self->run(@$multi);
619             }
620             }
621              
622             sub run_action_showmulti {
623 0     0 0 0 my ($self, @args) = @_;
624 0         0 my @multi = $self->_parse_multi(@args);
625 0         0 say join(' ', plx => @$_) for @multi;
626             }
627              
628             sub run {
629 33     33 0 88 my ($self, $cmd, @args) = @_;
630 33   50     59 $cmd ||= '--help';
631 33 50       70 if ($cmd eq '[') {
632 0         0 return $self->run_action_multi($cmd, @args);
633             }
634 33 50       168 if ($cmd =~ s/^--//) {
635 33 50       50 if ($cmd) {
636 33         127 my $method = join('_', 'run_action', split '-', $cmd);
637 33 100       156 if (my $code = $self->can($method)) {
638 32         80 return $self->$code(@args);
639             }
640 1         5 barf "No such action --${cmd}, see 'perldoc plx' for the full list";
641             }
642 0           $cmd = shift @args;
643             }
644 0           $self->ensure_layout_config_dir;
645 0           return $self->run_action_cmd($cmd, @args);
646             }
647              
648             caller() ? 1 : __PACKAGE__->new->run(@ARGV);
649              
650             =head1 NAME
651              
652             App::plx - Perl Layout Executor
653              
654             =head1 SYNOPSIS
655              
656             plx --help # This output
657              
658             plx --init # Initialize layout config
659             plx --perl # Show layout perl binary
660             plx --libs # Show layout $PERL5LIB entries
661             plx --paths # Show layout additional $PATH entries
662             plx --env # Show layout env var changes
663             plx --cpanm -llocal --installdeps . # Run cpanm from outside $PATH
664            
665             plx perl # Run perl within layout
666             plx -E '...' # (ditto)
667             plx script-in-dev # Run dev/ script within layout
668             plx script-in-bin # Run bin/ script within layout
669             plx ./script # Run script within layout
670             plx script/in/cwd # (ditto)
671             plx program # Run program from layout $PATH
672              
673             =head1 WHY PLX
674              
675             While perl has many tools for configuring per-project development
676             environments, using them can still be a little on the lumpy side. With
677             L, you find yourself running one of
678              
679             perl -Ilocal/lib/perl -Ilib bin/myapp
680             carton exec perl -Ilib bin/myapp
681              
682             With L,
683              
684             perlbrew switch perl-5.28.0@libname
685             perl -Ilib bin/myapp
686              
687             With L,
688              
689             plenv exec perl -Ilib bin/myapp
690              
691             and if you have more than one distinct layer of dependencies, while
692             L will happily handle that, integrating it with everything else
693             becomes a pain in the buttocks.
694              
695             As a result of this, your not-so-humble author found himself regularly having
696             a miniature perl executor script at the root of git clones that looked
697             something like:
698              
699             #!/bin/sh
700             eval $(perl -Mlocal::lib=--deactivate-all)
701             export PERL5LIB=$PWD/local/lib/perl5
702             bin=$1
703             shift
704             ~/perl5/perlbrew/perls/perl-5.28.0/bin/$bin "$@"
705              
706             and then running:
707              
708             ./pl perl -Ilib bin/myapp
709              
710             However, much like back in 2007 frustration with explaining to other
711             developers how to set up L to install into C<~/perl5> and how to
712             set up one's environment variables to then find the modules so installed
713             led to the exercise in rage driven development that first created
714             L, walking newbies through the creation and subsequent use of
715             such a script was not the most enjoyable experience for anybody involved.
716              
717             Thus, the creation of this module to reduce the setup process to:
718              
719             cpanm App::plx
720             cd MyProject
721             plx --init 5.28.0
722             plx --cpanm -llocal --notest --installdeps .
723              
724             Follwed by being able to immediately (and even more concisely) run:
725              
726             plx myapp
727              
728             which will execute C with the correct C and the
729             relevant L already in scope.
730              
731             If this seems of use to you, the L is next and the L
732             section of this document lists the full capabilities of plx. Onwards!
733              
734             =head1 QUICKSTART
735              
736             Let's assume we're going to be working on Foo-Bar, so we start with:
737              
738             git clone git@github.com:arthur-nonymous/Foo-Bar.git
739             cd Foo-Bar
740              
741             Assuming the perl we'd get from running just C suffices, then we
742             next run:
743              
744             plx --init
745              
746             If we want a different perl - say, we have a C in our path, or
747             a C built in perlbrew, we'd instead run:
748              
749             plx --init 5.30.1
750              
751             To quickly get our dependencies available, we then run:
752              
753             plx --cpanm -llocal --notest --installdeps .
754              
755             If the project is designed to use L and has a C,
756             instead we would run:
757              
758             plx --cpanm -ldevel --notest Carton
759             plx carton install
760              
761             If the goal is to test this against our current development version of another
762             library, then we'd also want to run:
763              
764             plx --config libspec add 40otherlib.dir ../Other-Lib/lib
765              
766             If we want our ~/perl L available within the plx environment, we
767             can add that as the least significant libspec with:
768              
769             plx --config libspec add 00tilde.ll $HOME/perl5
770              
771             At which point, we're ready to go, and can run:
772              
773             plx myapp # to run bin/myapp
774             plx t/foo.t # to run one test file
775             plx prove # to run all t/*.t test files
776             plx -E 'say for @INC' # to run a one liner within the layout
777              
778             To learn everything else plx is capable of, read on to the L section
779             coming next.
780              
781             Have fun!
782              
783             =head1 BOOTSTRAP
784              
785             Under normal circumstances, one would run something like:
786              
787             cpanm App::plx
788              
789             However, if you want a self-contained plx script without having a cpan
790             installer available, you can run:
791              
792             mkdir bin
793             wget https://raw.githubusercontent.com/shadowcat-mst/plx/master/bin/plx-packed -O bin/plx
794              
795             to get the current latest packed version.
796              
797             The packed version bundles L and L, and also includes
798             a modified C<--cpanm> action that uses an inline C.
799              
800             =head1 ENVIRONMENT
801              
802             C actions that execute external commands all clear any existing
803             environment variables that start with C to keep an encapsulated setup
804             for commands being run within the layouts - and also set C to
805             exclude C (but not C) to avoid locally installed
806             modules causing unexpected effects.
807              
808             Having done so, C then loads each env config entry and sets those
809             variables - then prepends the C specific entries to both C and
810             C. You can add env config entries with L:
811              
812             plx --config env add NAME VALUE
813              
814             The changes that will be made to your environment can be output by calling
815             the L command.
816              
817             Additionally, environment variable overrides may be provided to the
818             L, L and L commands by providing them in
819             C format:
820              
821             # do not do this, it will be deleted
822             PERL_RL=Perl5 plx
823              
824             # do this instead, it will provide the environment variable to the command
825             plx PERL_RL=Perl5
826              
827             =head1 ACTIONS
828              
829             plx --help # Print synopsis
830             plx --version # Print plx version
831              
832             plx --init # Initialize layout config for .
833             plx --bareinit # Initialize bare layout config for .
834             plx --base # Show layout base dir
835             plx --base # Run action with specified base dir
836            
837             plx --perl # Show layout perl binary
838             plx --libs # Show layout $PERL5LIB entries
839             plx --paths # Show layout additional $PATH entries
840             plx --env # Show layout env var changes
841             plx --cpanm -llocal --installdeps . # Run cpanm from outside $PATH
842              
843             plx --config perl # Show perl binary
844             plx --config perl set /path/to/perl # Select exact perl binary
845             plx --config perl set perl-5.xx.y # Select perl via $PATH or perlbrew
846              
847             plx --config libspec # Show lib specifications
848             plx --config libspec add # Add lib specification
849             plx --config libspec del # Delete lib specification
850            
851             plx --config env # Show additional env vars
852             plx --config env add # Add env var
853             plx --config env del # Delete env var
854              
855             plx --exec # exec()s with env vars set
856             plx --perl # Run perl with args
857              
858             plx --cmd # DWIM command:
859            
860             cmd = perl -> --perl
861             cmd = - -> --perl -
862             cmd = some/file -> --perl some/file
863             cmd = ./file -> --perl ./file
864             cmd = name ->
865             exists .plx/cmd/ -> --perl .plx/cmd/
866             exists dev/ -> --perl dev/
867             exists bin/ -> --perl bin/
868             else -> --exec
869              
870             plx --which # Expands --cmd without running
871            
872             plx # Shorthand for plx --cmd
873            
874             plx --commands ? # List available commands
875            
876             plx --multi [ ] [ ... ] # Run multiple actions
877             plx --showmulti [ ... ] [ ... ] # Show multiple action running
878             plx [ ... ] [ ... ] # Shorthand for plx --multi
879            
880             plx --userinit # Init ~/.plx with ~/perl5 ll
881             plx --installself # Installs plx and cpanm into layout
882             plx --installenv # Appends plx --env call to .bashrc
883             plx --userstrap # userinit+installself+installenv
884              
885             =head2 --help
886              
887             Prints out the usage information (i.e. the L) for plx.
888              
889             =head2 --init
890              
891             plx --init # resolve 'perl' in $PATH
892             plx --init perl # (ditto)
893             plx --init 5.28.0 # looks for perl5.28.0 in $PATH
894             # or perl-5.28.0 in perlbrew
895             plx --init /path/to/some/perl # uses the absolute path directly
896              
897             Initializes the layout.
898              
899             If a perl name is passed, attempts to resolve it via C<$PATH> and C
900             and sets the result as the layout perl; if not looks for just C.
901              
902             Creates the following libspec config:
903              
904             25-local.ll local
905             50-devel.ll devel
906             75-lib.dir lib
907              
908             =head2 --bareinit
909              
910             Identical to C<--init> but creates no default configs except for C.
911              
912             =head2 --base
913              
914             plx --base
915             plx --base
916              
917             Without arguments, shows the selected base dir - C finds this by
918             checking for a C<.plx> directory in the current directory, and if not tries
919             the parent directory, recursively. The search stops either when C finds
920             a C<.git> directory, to avoid accidentally escaping a project repository, or
921             at the last directory before the root - i.e. C will test C but
922             not C.
923              
924             With arguments, specifies a base dir to use, and then invokes the rest of the
925             arguments with that base dir selected - so for example one can make a default
926             configuration in C<$HOME> available as C by running:
927              
928             plx --init $HOME
929             alias plh='plx --base $HOME'
930              
931             =head2 --libs
932              
933             Prints the directories that will be added to C, one per line.
934              
935             These will include the C subdirectory for each C entry in the
936             libspecs, and the directory for each C entry.
937              
938             =head2 --paths
939              
940             Prints the directories that will be added to C, one per line.
941              
942             These will include the containing directory of the environment's perl binary
943             if not already in C, followed by the C directories of any C
944             entries in the libspecs.
945              
946             =head2 --env
947              
948             Prints the changes that will be made to your environment variables, in a
949             syntax that is (hopefully) correct for your current shell.
950              
951             =head2 --cpanm
952              
953             plx --cpanm -Llocal --installdeps .
954             plx --cpanm -ldevel App::Ack
955              
956             Finds the C binary in the C that C was executed I,
957             and executes it using the layout's perl binary and environment variables.
958              
959             Requires the user to specify a L to install into via C<-l> or
960             C<-L> in order to avoid installing modules into unexpected places.
961              
962             Note that this action exists primarily for bootstrapping, and if you want
963             to use a different installer such as L, you'd install it with:
964              
965             plx --cpanm -ldevel App::cpm
966              
967             and then subsequently run e.g.
968              
969             plx cpm install App::Ack
970              
971             to install modules.
972              
973             =head2 --exec
974              
975             plx --exec
976              
977             Sets up the layout's environment variables and Cs the command.
978              
979             =head2 --perl
980              
981             plx --perl
982             plx --perl