File Coverage

bin/plx
Criterion Covered Total %
statement 217 392 55.3
branch 58 186 31.1
condition 10 53 18.8
subroutine 55 81 67.9
pod 0 55 0.0
total 340 767 44.3


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