File Coverage

blib/lib/Installer/Target.pm
Criterion Covered Total %
statement 31 328 9.4
branch 0 108 0.0
condition 0 45 0.0
subroutine 11 40 27.5
pod 0 25 0.0
total 42 546 7.6


line stmt bran cond sub pod time code
1             package Installer::Target;
2             BEGIN {
3 1     1   31 $Installer::Target::AUTHORITY = 'cpan:GETTY';
4             }
5             # ABSTRACT: Currently running project
6             $Installer::Target::VERSION = '0.903';
7 1     1   7 use Moo;
  1         2  
  1         7  
8 1     1   356 use IO::All;
  1         2  
  1         10  
9 1     1   1167 use IPC::Open3 ();
  1         2963  
  1         19  
10 1     1   545 use Installer::Software;
  1         5  
  1         87  
11 1     1   13 use JSON_File;
  1         2  
  1         28  
12 1     1   6 use File::chdir;
  1         2  
  1         143  
13 1     1   3719 use CPAN::Perl::Releases qw[perl_tarballs];
  1         3105  
  1         1027  
14 1     1   8380 use CPAN;
  1         252926  
  1         490  
15 1     1   31 use Path::Class;
  1         3  
  1         84  
16 1     1   8 use namespace::clean;
  1         3  
  1         34  
17              
18             has output_code => (
19             is => 'ro',
20             lazy => 1,
21             default => sub { sub {
22             print @_, "\n";
23             } },
24             );
25              
26             has installer_code => (
27             is => 'ro',
28             required => 1,
29             );
30              
31             has source_directory => (
32             is => 'ro',
33             predicate => 1,
34             );
35             has source => (
36             is => 'ro',
37             lazy => 1,
38             default => sub { dir($_[0]->source_directory)->absolute },
39             );
40 0     0 0   sub source_path { dir(shift->source,@_) }
41 0     0 0   sub source_file { file(shift->source,@_) }
42              
43             has target_directory => (
44             is => 'ro',
45             required => 1,
46             );
47             has target => (
48             is => 'ro',
49             lazy => 1,
50             default => sub { dir($_[0]->target_directory)->absolute },
51             );
52 0     0 0   sub target_path { dir(shift->target,@_) }
53 0     0 0   sub target_file { file(shift->target,@_) }
54              
55             has installer_dir => (
56             is => 'ro',
57             lazy => 1,
58             default => sub { dir($_[0]->target,'installer') },
59             );
60              
61             has software => (
62             is => 'ro',
63             lazy => 1,
64             default => sub {{}},
65             );
66              
67             has actions => (
68             is => 'ro',
69             lazy => 1,
70             default => sub {[]},
71             );
72              
73             has src_dir => (
74             is => 'ro',
75             lazy => 1,
76             default => sub { dir($_[0]->target,'src') },
77             );
78              
79             has log_filename => (
80             is => 'ro',
81             lazy => 1,
82             default => sub { file($_[0]->installer_dir,'build.'.(time).'.log') },
83             );
84              
85             has log_io => (
86             is => 'ro',
87             lazy => 1,
88             default => sub { io($_[0]->log_filename) },
89             );
90              
91             has meta => (
92             is => 'ro',
93             lazy => 1,
94             default => sub {
95             my ( $self ) = @_;
96             tie(my %meta,'JSON_File',file($self->installer_dir,'meta.json')->absolute->stringify, pretty => 1);
97             return \%meta;
98             },
99             );
100              
101             sub install_software {
102 0     0 0   my ( $self, $software ) = @_;
103 0           $self->software->{$software->alias} = $software;
104 0           $software->installation;
105 0           $self->meta->{software_packages_done} = [keys %{$self->software}];
  0            
106 0           push @{$self->actions}, $software;
  0            
107 0           $self->update_env;
108 0 0 0       if (!defined $software->meta->{installed_export} && $software->has_export) {
109 0           $self->install_export(ref $software->export eq 'ARRAY'
110 0 0         ? @{$software->export}
111             : $software->export
112             );
113 0           $software->meta->{installed_export} = 1;
114             }
115 0 0 0       if (!defined $software->meta->{installed_unset} && $software->has_unset) {
116 0           $self->install_unset(ref $software->unset eq 'ARRAY'
117 0 0         ? @{$software->unset}
118             : $software->unset
119             );
120 0           $software->meta->{installed_unset} = 1;
121             }
122 0           $self->write_export;
123 0 0 0       if (!defined $software->meta->{post_install} && $software->has_post_install) {
124 0           $software->post_install->($software);
125 0           $software->meta->{post_install} = 1;
126             }
127 0           $self->write_export;
128             }
129              
130             sub install_url {
131 0     0 0   my ( $self, $url, %args ) = @_;
132 0           $self->install_software(Installer::Software->new(
133             target => $self,
134             archive_url => $url,
135             %args,
136             ));
137             }
138              
139             sub install_file {
140 0     0 0   my ( $self, $file, %args ) = @_;
141 0           $self->install_software(Installer::Software->new(
142             target => $self,
143             archive => rel2abs(catfile($file)),
144             %args,
145             ));
146             }
147              
148             sub install_perl {
149 0     0 0   my ( $self, $perl_version, %args ) = @_;
150 0           my $hashref = perl_tarballs($perl_version);
151 0 0         die 'No such Perl version: '.$perl_version unless defined $hashref;
152 0           my $src = 'http://www.cpan.org/authors/id/'.$hashref->{'tar.gz'};
153             $self->install_software(Installer::Software->new(
154             target => $self,
155             archive_url => $src,
156             testable => 1,
157             custom_configure => sub {
158 0     0     my ( $self ) = @_;
159 0           $self->run($self->unpack_path,'./Configure','-des','-Dprefix='.$self->target_directory);
160             },
161             post_install => sub {
162 0     0     my ( $self ) = @_;
163 0           $self->log_print("Installing App::cpanminus ...");
164 0           my $cpanm_filename = file($self->target->installer_dir,'cpanm');
165 0           io($cpanm_filename)->print(io('http://cpanmin.us/')->get->content);
166 0           chmod(0755,$cpanm_filename);
167 0           $self->run(undef,$cpanm_filename,'-L',$self->target_path('perl5'),qw(
168             App::cpanminus
169             local::lib
170             Module::CPANfile
171             ));
172             },
173             export_sh => sub {
174 0     0     my ( $self ) = @_;
175 0           'eval $( perl -I'.$self->target_path('perl5','lib','perl5').' -Mlocal::lib=--deactivate-all )',
176             'eval $( perl -I'.$self->target_path('perl5','lib','perl5').' -Mlocal::lib='.$self->target_path('perl5').' )'
177             },
178 0           %args,
179             ));
180             }
181              
182             #url "http://ftp.postgresql.org/pub/source/v9.3.0/postgresql-9.3.0.tar.bz2", with => {
183             # pgport => 15700,
184             #};
185             sub install_postgres {
186 0     0 0   my ( $self, $version, %args ) = @_;
187 0           my $url = "http://ftp.postgresql.org/pub/source/v".$version."/postgresql-".$version.".tar.bz2";
188 0           my %with = defined $args{with}
189 0 0         ? %{delete $args{with}}
190             : ();
191 0 0         $with{pgport} = delete $args{port} if defined $args{port};
192 0           my %users = defined $args{users}
193 0 0         ? %{delete $args{users}}
194             : ();
195 0 0         my $pgdata = defined $args{data}
196             ? dir(delete $args{data})->absolute->stringify
197             : $self->target_path('pgdata')->absolute->stringify;
198 0 0         my $logfile = defined $args{log}
199             ? dir(delete $args{log})->absolute->stringify
200             : $self->target_file('pgdata','postgresql.log');
201 0 0         if (defined $args{superuser_with_db}) {
202 0           my $superuser_with_db = delete $args{superuser_with_db};
203 0 0         if (ref $superuser_with_db eq 'HASH') {
    0          
    0          
204 0           for (keys %{$superuser_with_db}) {
  0            
205 0           $users{$_} = {
206             superuser => 1,
207             dbs => [
208             ref $superuser_with_db->{$_} eq 'ARRAY'
209 0 0         ? @{$superuser_with_db->{$_}}
210             : $superuser_with_db->{$_}
211             ],
212             };
213             }
214             } elsif (ref $superuser_with_db eq 'ARRAY') {
215 0           for (@{$superuser_with_db}) {
  0            
216 0           $users{$_} = {
217             superuser => 1,
218             dbs => [$_],
219             };
220             }
221             } elsif (ref $superuser_with_db eq '') {
222 0           $users{$superuser_with_db} = {
223             superuser => 1,
224             dbs => [$superuser_with_db],
225             };
226             } else {
227 0           die "unknown how to handle ".(ref $superuser_with_db);
228             }
229             }
230 0           my $post_install = delete $args{post_install};
231             $self->install_software(Installer::Software->new(
232             target => $self,
233             archive_url => $url,
234             %with ? ( with => \%with ) : (),
235             export => [
236             'PGDATA='.$pgdata,
237             defined $with{pgport} ? ('PGPORT='.$with{pgport}, 'PGHOST=localhost') : (),
238             defined $args{export} ? ( delete $args{export} ) : (),
239             ],
240             post_install => sub {
241 0     0     my @post_install_args = @_;
242              
243 0           $_[0]->run(undef,'initdb');
244 0           $_[0]->run(undef,'pg_ctl','-w','-l',$logfile,'start');
245              
246 0           for my $user (keys %users) {
247 0           my @create_args = '-w';
248 0 0         if ($users{$user}->{superuser}) {
249 0           push @create_args, '-s';
250             }
251 0           $_[0]->run(undef,'createuser',@create_args,$user);
252 0 0         if (defined $users{$user}->{dbs}) {
253 0           for (@{$users{$user}->{dbs}}) {
  0            
254 0           $_[0]->run(undef,'createdb','-O',$user,$_);
255             }
256             }
257             }
258              
259 0 0         if (defined $post_install) {
260 0           $post_install->(@post_install_args);
261             }
262              
263 0           $_[0]->run(undef,'pg_ctl','stop');
264             },
265 0 0         %args,
    0          
    0          
266             ));
267             }
268              
269             sub install_cpanm {
270 0     0 0   my ( $self, @modules ) = @_;
271 0           $self->run(undef,'cpanm',@modules);
272             }
273              
274             sub install_pip {
275 0     0 0   my ( $self, @modules ) = @_;
276 0           for (@modules) {
277 0           $self->run(undef,'pip','install',$_);
278             }
279             }
280              
281             sub install_run {
282 0     0 0   my ( $self, @args ) = @_;
283 0           $self->run($self->target,@args);
284 0           push @{$self->actions}, {
  0            
285             run => \@args,
286             };
287             }
288              
289             sub install_perldeps {
290 0     0 0   my ( $self, $path, @args ) = @_;
291 0 0 0       die "No source_directory or path given" unless defined $path || $self->has_source_directory;
292 0 0         $self->run(undef,"cpanm","--installdeps",defined $path ? $path : $self->source_directory);
293 0           $self->run(undef,"set");
294             }
295              
296             sub install_dzildeps {
297 0     0 0   my ( $self, $path, @args ) = @_;
298 0 0 0       die "No source_directory or path given" unless defined $path || $self->has_source_directory;
299 0           $self->run(undef,"cpanm","Dist::Zilla");
300 0 0         my $dzil_dir = defined $path ? $path : $self->source_directory;
301 0           $self->run($dzil_dir,qw( dzil authordeps | grep -v " " | cpanm ));
302 0           $self->run($dzil_dir,qw( dzil listdeps | grep -v " " | cpanm ));
303             }
304              
305             sub install_export {
306 0     0 0   my ( $self, @args ) = @_;
307 0           my @exports = defined $self->meta->{export}
308 0 0         ? @{$self->meta->{export}}
309             : ();
310 0           for (@args) {
311 0           my @new_exports;
312 0 0         if (ref $_ eq 'CODE') {
313 0           push @new_exports, $_->($self);
314             } else {
315 0           push @new_exports, $_;
316             }
317 0           for (@new_exports) {
318 0           $self->log_print("Adding export ".$_);
319 0           push @exports, $_;
320             }
321             }
322 0           $self->meta->{export} = \@exports;
323 0           $self->write_export;
324             }
325              
326             sub install_unset {
327 0     0 0   my ( $self, @args ) = @_;
328 0           my @unsets = defined $self->meta->{unset}
329 0 0         ? @{$self->meta->{unset}}
330             : ();
331 0           for (@args) {
332 0           my @new_unsets;
333 0 0         if (ref $_ eq 'CODE') {
334 0           push @new_unsets, $_->($self);
335             } else {
336 0           push @new_unsets, $_;
337             }
338 0           for (@new_unsets) {
339 0           $self->log_print("Adding export ".$_);
340 0           push @unsets, $_;
341             }
342             }
343 0           $self->meta->{unset} = \@unsets;
344 0           $self->write_export;
345             }
346              
347             sub patch_via_url {
348 0     0 0   my ( $self, $path, $url, @args ) = @_;
349 0           local $CWD = $path;
350 0           $self->log_print("Fetching patch from $url into ".$path." ...");
351 0           my $diff_name = $url;
352 0           $diff_name =~ s/^https{0,1}//g;
353 0           $diff_name =~ s/[^\w]+/_/g;
354 0           $diff_name =~ s/^_+//g;
355 0           $diff_name =~ s/_+$//g;
356 0           $diff_name .= '.patch';
357 0           io(file($path,$diff_name))->print(io($url)->get->content);
358 0           $self->log_print("Applying patch as ".$diff_name." ...");
359 0           $|=1;
360 0           my $patch_log = "";
361 0           my $pid = IPC::Open3::open3(my ( $in, $out ), undef, "patch",@args);
362 0           print $in scalar io($diff_name)->slurp;
363 0           close ($in);
364 0           while(defined(my $line = <$out>)){
365 0           $patch_log .= $line;
366 0           chomp($line);
367 0           $self->log($line);
368             }
369 0           waitpid($pid, 0);
370 0           my $status = $? >> 8;
371 0 0         if ($status) {
372 0           print $patch_log;
373 0           print "\n";
374 0           print " Command: patch ".join(" ",@args)."\n";
375 0           print "in Directory: ".$path."\n\n";
376 0           print "exited with status $status\n\n";
377 0           die "Error on run ".$self->log_filename;
378             }
379             }
380              
381             sub run {
382 0     0 0   my ( $self, $dir, @args ) = @_;
383 0 0         $dir = $self->target_path unless $dir;
384 0           local $CWD = "$dir";
385 0           $self->log_print("Executing in $dir: ".join(" ",@args));
386 0           $|=1;
387 0           my $run_log = "";
388 0           my $export_sh_filename = $self->target_file('export.sh')->absolute->stringify;
389 0           my $prefix = "";
390 0 0         if (-f $export_sh_filename) {
391 0           my @export_sh_lines = io($export_sh_filename)->slurp;
392 0           for my $line (@export_sh_lines) {
393 0           $run_log .= $line;
394 0           $prefix .= $line;
395 0           chomp($line);
396 0           $self->log($line);
397             }
398 0           $prefix .= "\n# Command ".("#" x 50)."\n";
399             }
400 0           my $shell_script = $prefix.join(" ",@args)."";
401 0           my $pid = IPC::Open3::open3(my ( $in, $out ), undef, "/bin/sh -s");
402 0           print $in $shell_script;
403 0           close ($in);
404 0           while(defined(my $line = <$out>)){
405 0           $run_log .= $line;
406 0           chomp($line);
407 0           $self->log($line);
408             }
409 0           waitpid($pid, 0);
410 0           my $status = $? >> 8;
411 0 0         if ($status) {
412 0           print $run_log;
413 0           print "\n";
414 0           print " Command:\n";
415 0           print "\n".join(" ",@args)."\n\n";
416 0           print " in Directory: ".$dir."\n";
417 0           print "exited with status $status\n\n";
418 0           print "\n";
419 0           die "Error on run ".$self->log_filename;
420             }
421             }
422              
423             sub log {
424 0     0 0   my ( $self, @line ) = @_;
425 0           $self->log_io->append(join(" ",@line),"\n");
426             }
427              
428             sub log_print {
429 0     0 0   my ( $self, @line ) = @_;
430 0           $self->log("#" x 80);
431 0           $self->log("##");
432 0           $self->log("## ",@line);
433 0           my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
434 0           $self->log("## ",sprintf("%.2d.%.2d.%.4d %.2d:%.2d:%.2d",$mday,$mon,$year+1900,$hour,$min,$sec));
435 0           $self->log("##");
436 0           $self->log("#" x 80);
437 0           $self->output_code->(@line);
438             }
439              
440             our $current;
441              
442             sub prepare_installation {
443 0     0 0   my ( $self ) = @_;
444 0 0         die "Target directory is a file" if -f $self->target;
445 0           $current = $self;
446 0 0         $self->target->mkpath unless -d $self->target;
447 0 0         $self->installer_dir->mkpath unless -d $self->installer_dir;
448 0 0         $self->src_dir->mkpath unless -d $self->src_dir;
449 0           $self->log_io->print(("#" x 80)."\nStarting new log ".(time)."\n".("#" x 80)."\n\n");
450 0           $self->meta->{last_run} = time;
451 0           $self->meta->{preinstall_ENV} = \%ENV;
452             }
453              
454             sub finish_installation {
455 0     0 0   my ( $self ) = @_;
456 0           $self->log_print("Done ".$self->log_filename);
457 0           %ENV = %{$self->meta->{preinstall_ENV}};
  0            
458 0           delete $self->meta->{preinstall_ENV};
459 0           $current = undef;
460             }
461              
462             sub installation {
463 0     0 0   my ( $self ) = @_;
464 0           $self->prepare_installation;
465 0           $self->installer_code->($self);
466 0           $self->finish_installation;
467             }
468              
469             sub write_export {
470 0     0 0   my ( $self ) = @_;
471 0           my $export_filename = $self->target_file('export.sh');
472 0           $self->log_print("Generating ".$export_filename." ...");
473 0           my $export_sh = "#!/bin/sh\n#\n# Installer auto generated export.sh\n#\n".("#" x 60)."\n\n";
474 0           $export_sh .= 'export CURRENT_INSTALLER_ENV='.$self->target_path->stringify."\n";
475 0 0 0       if (defined $self->meta->{unset} && @{$self->meta->{unset}}) {
  0            
476 0           $export_sh .= '# custom unsets'."\n";
477 0           for (@{$self->meta->{unset}}) {
  0            
478 0           $export_sh .= 'unset '.$_."\n";
479             }
480             }
481 0 0 0       if (defined $self->meta->{PATH} && @{$self->meta->{PATH}}) {
  0            
482 0           $export_sh .= 'export PATH="'.join(':',@{$self->meta->{PATH}}).'${PATH+:}$PATH"'."\n";
  0            
483             }
484 0 0 0       if (defined $self->meta->{LD_LIBRARY_PATH} && @{$self->meta->{LD_LIBRARY_PATH}}) {
  0            
485 0           $export_sh .= 'export LD_LIBRARY_PATH="'.join(':',@{$self->meta->{LD_LIBRARY_PATH}}).'${LD_LIBRARY_PATH+:}$LD_LIBRARY_PATH"'."\n";
  0            
486             }
487 0 0 0       if (defined $self->meta->{C_INCLUDE_PATH} && @{$self->meta->{C_INCLUDE_PATH}}) {
  0            
488 0           $export_sh .= 'export C_INCLUDE_PATH="'.join(':',@{$self->meta->{C_INCLUDE_PATH}}).'${C_INCLUDE_PATH+:}$C_INCLUDE_PATH"'."\n";
  0            
489             }
490 0 0 0       if (defined $self->meta->{MANPATH} && @{$self->meta->{MANPATH}}) {
  0            
491 0           $export_sh .= 'export MANPATH="'.join(':',@{$self->meta->{MANPATH}}).'${MANPATH+:}$MANPATH"'."\n";
  0            
492             }
493 0 0 0       if (defined $self->meta->{export} && @{$self->meta->{export}}) {
  0            
494 0           $export_sh .= '# custom exports'."\n";
495 0           for (@{$self->meta->{export}}) {
  0            
496 0           $export_sh .= 'export '.$_."\n";
497             }
498             }
499 0           $export_sh .= "\n";
500 0           for (@{$self->meta->{software_packages_done}}) {
  0            
501 0           my $software = $self->software->{$_};
502 0 0         if ($software->has_export_sh) {
503 0           my @lines = $software->export_sh->($software);
504 0           $export_sh .= "# export.sh addition by ".$software->alias."\n";
505 0           $export_sh .= join("\n",@lines)."\n\n";
506             }
507             }
508 0           $export_sh .= ("#" x 60)."\n";
509 0           io($export_filename)->print($export_sh);
510 0           chmod(0755,$export_filename);
511             }
512              
513             sub update_env {
514 0     0 0   my ( $self ) = @_;
515 0           my %seen = defined $self->meta->{seen_dirs}
516 0 0         ? %{$self->meta->{seen_dirs}}
517             : ();
518 0 0 0       if (!$seen{'bin'} and -e $self->target_path('bin')) {
519 0           my @bindirs = defined $self->meta->{PATH}
520 0 0         ? @{$self->meta->{PATH}}
521             : ();
522 0           my $bindir = $self->target_path('bin')->absolute->stringify;
523 0           push @bindirs, $bindir;
524 0           $self->meta->{PATH} = \@bindirs;
525 0           $seen{'bin'} = 1;
526             }
527 0 0 0       if (!$seen{'man'} and -e $self->target_path('man')) {
528 0           my @mandirs = defined $self->meta->{MANPATH}
529 0 0         ? @{$self->meta->{MANPATH}}
530             : ();
531 0           my $mandir = $self->target_path('man')->absolute->stringify;
532 0           push @mandirs, $mandir;
533 0           $self->meta->{MANPATH} = \@mandirs;
534 0           $seen{'man'} = 1;
535             }
536 0 0 0       if (!$seen{'lib'} and -e $self->target_path('lib')) {
537 0           my @libdirs = defined $self->meta->{LD_LIBRARY_PATH}
538 0 0         ? @{$self->meta->{LD_LIBRARY_PATH}}
539             : ();
540 0           my $libdir = $self->target_path('lib')->absolute->stringify;
541 0           push @libdirs, $libdir;
542 0           $self->meta->{LD_LIBRARY_PATH} = \@libdirs;
543 0           $seen{'lib'} = 1;
544             }
545 0 0 0       if (!$seen{'include'} and -e $self->target_path('include')) {
546 0           my @libdirs = defined $self->meta->{C_INCLUDE_PATH}
547 0 0         ? @{$self->meta->{C_INCLUDE_PATH}}
548             : ();
549 0           my $libdir = $self->target_path('include')->absolute->stringify;
550 0           push @libdirs, $libdir;
551 0           $self->meta->{C_INCLUDE_PATH} = \@libdirs;
552 0           $seen{'include'} = 1;
553             }
554 0           $self->meta->{seen_dirs} = \%seen;
555             }
556              
557             1;
558              
559             __END__