File Coverage

blib/lib/Alien/Build.pm
Criterion Covered Total %
statement 500 519 96.3
branch 191 220 86.8
condition 74 99 74.7
subroutine 83 83 100.0
pod 27 27 100.0
total 875 948 92.3


line stmt bran cond sub pod time code
1             package Alien::Build;
2              
3 50     144   432792 use strict;
  50         115  
  50         1360  
4 50     50   214 use warnings;
  50         101  
  50         1082  
5 50     50   706 use 5.008004;
  50         161  
6 50     50   2270 use Path::Tiny ();
  50         31549  
  50         769  
7 50     50   243 use Carp ();
  50         86  
  50         1021  
8 50     50   14485 use File::chdir;
  50         100726  
  50         6111  
9 50     50   27359 use JSON::PP ();
  50         532539  
  50         1474  
10 50     50   33523 use Env qw( @PATH @PKG_CONFIG_PATH );
  50         103824  
  50         404  
11 50     50   8656 use Config ();
  50         128  
  50         799  
12 50     50   19814 use Alien::Build::Log;
  50         121  
  50         18873  
13              
14             # ABSTRACT: Build external dependencies for use in CPAN
15             our $VERSION = '2.47'; # VERSION
16              
17              
18 909     909   4287 sub _path { goto \&Path::Tiny::path }
19              
20              
21             sub new
22             {
23 339     339 1 15841 my($class, %args) = @_;
24             my $self = bless {
25             install_prop => {
26             root => _path($args{root} || "_alien")->absolute->stringify,
27 339 100 100     1393 patch => (defined $args{patch}) ? _path($args{patch})->absolute->stringify : undef,
      50        
28             },
29             runtime_prop => {
30             alien_build_version => $Alien::Build::VERSION || 'dev',
31             },
32             plugin_instance_prop => {},
33             bin_dir => [],
34             pkg_config_path => [],
35             aclocal_path => [],
36             }, $class;
37              
38             $self->meta->filename(
39 339   66     22003 $args{filename} || do {
40             my(undef, $filename) = caller;
41             _path($filename)->absolute->stringify;
42             }
43             );
44              
45 339 100       904 if($args{meta_prop})
46             {
47 19         38 $self->meta->prop->{$_} = $args{meta_prop}->{$_} for keys %{ $args{meta_prop} };
  19         67  
48             }
49              
50 339         753 $self;
51             }
52              
53              
54             my $count = 0;
55              
56             sub load
57             {
58 338     338 1 31856 my(undef, $alienfile, @args) = @_;
59              
60 338   50     2548 my $rcfile = Path::Tiny->new($ENV{ALIEN_BUILD_RC} || '~/.alienbuild/rc.pl')->absolute;
61 338 100       34453 if(-r $rcfile)
62             {
63 19         1413 require Alien::Build::rc;
64             package Alien::Build::rc;
65 19         67 require $rcfile;
66             }
67              
68 338 100       8944 unless(-r $alienfile)
69             {
70 1         143 Carp::croak "Unable to read alienfile: $alienfile";
71             }
72              
73 337         1279 my $file = _path $alienfile;
74 337         9059 my $name = $file->parent->basename;
75 337         24830 $name =~ s/^alien-//i;
76 337         1753 $name =~ s/[^a-z]//g;
77 337 100       984 $name = 'x' if $name eq '';
78 337         1048 $name = ucfirst $name;
79              
80 337         934 my $class = "Alien::Build::Auto::$name@{[ $count++ ]}";
  337         1244  
81              
82 50     50   349 { no strict 'refs';
  50         103  
  50         268416  
  337         688  
83 337         552 @{ "${class}::ISA" } = ('Alien::Build');
  337         8054  
84 337         3344 *{ "${class}::Alienfile::meta" } = sub {
85 1011     1011   1988 $class =~ s{::Alienfile$}{};
86 1011         1833 $class->meta;
87 337         2475 }};
88              
89 337         1236 my @preload = qw( Core::Setup Core::Download Core::FFI Core::Override Core::CleanInstall );
90 337         763 push @preload, @Alien::Build::rc::PRELOAD;
91             push @preload, split /;/, $ENV{ALIEN_BUILD_PRELOAD}
92 337 100       1112 if defined $ENV{ALIEN_BUILD_PRELOAD};
93              
94 337         897 my @postload = qw( Core::Legacy Core::Gather Core::Tail );
95 337         592 push @postload, @Alien::Build::rc::POSTLOAD;
96             push @postload, split /;/, $ENV{ALIEN_BUILD_POSTLOAD}
97 337 50       1078 if defined $ENV{ALIEN_BUILD_POSTLOAD};
98              
99 337         1246 my $self = $class->new(
100             filename => $file->absolute->stringify,
101             @args,
102             );
103              
104 337         3416 require alienfile;
105              
106 337         919 foreach my $preload (@preload)
107             {
108 1707 100       4456 ref $preload eq 'CODE' ? $preload->($self->meta) : $self->meta->apply_plugin($preload);
109             }
110              
111             # TODO: do this without a string eval ?
112             ## no critic
113 337         960 eval '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . qq{
114             package ${class}::Alienfile;
115 337         1134 do '@{[ $file->absolute->stringify ]}';
116             die \$\@ if \$\@;
117             };
118 337 100       1701 die $@ if $@;
119             ## use critic
120              
121 322         810 foreach my $postload (@postload)
122             {
123 968 100       2962 ref $postload eq 'CODE' ? $postload->($self->meta) : $self->meta->apply_plugin($postload);
124             }
125              
126 322         2299 $self->{args} = \@args;
127 322 100       833 unless(defined $self->meta->prop->{arch})
128             {
129 320         643 $self->meta->prop->{arch} = 1;
130             }
131              
132 322 100       668 unless(defined $self->meta->prop->{network})
133             {
134 319         612 $self->meta->prop->{network} = 1;
135             ## https://github.com/PerlAlien/Alien-Build/issues/23#issuecomment-341114414
136             #$self->meta->prop->{network} = 0 if $ENV{NO_NETWORK_TESTING};
137 319 100 100     1128 $self->meta->prop->{network} = 0 if (defined $ENV{ALIEN_INSTALL_NETWORK}) && ! $ENV{ALIEN_INSTALL_NETWORK};
138             }
139              
140 322 100       617 unless(defined $self->meta->prop->{local_source})
141             {
142 318 100       620 if(! defined $self->meta->prop->{start_url})
    100          
143             {
144 283         537 $self->meta->prop->{local_source} = 0;
145             }
146             # we assume URL schemes are at least two characters, that
147             # way Windows absolute paths can be used as local start_url
148             elsif($self->meta->prop->{start_url} =~ /^([a-z]{2,}):/i)
149             {
150 20         56 my $scheme = $1;
151 20         52 $self->meta->prop->{local_source} = $scheme eq 'file';
152             }
153             else
154             {
155 15         35 $self->meta->prop->{local_source} = 1;
156             }
157             }
158              
159 322         2001 return $self;
160             }
161              
162              
163             sub resume
164             {
165 24     24 1 8652 my(undef, $alienfile, $root) = @_;
166 24         111 my $h = JSON::PP::decode_json(_path("$root/state.json")->slurp);
167 24         108826 my $self = Alien::Build->load("$alienfile", @{ $h->{args} });
  24         274  
168 24         79 $self->{install_prop} = $h->{install};
169 24         50 $self->{plugin_instance_prop} = $h->{plugin_instance};
170 24         56 $self->{runtime_prop} = $h->{runtime};
171 24         120 $self;
172             }
173              
174              
175             sub meta_prop
176             {
177 957     957 1 5489 my($class) = @_;
178 957         2017 $class->meta->prop;
179             }
180              
181              
182             sub install_prop
183             {
184 2615     2615 1 84264 shift->{install_prop};
185             }
186              
187              
188             sub plugin_instance_prop
189             {
190 10     10 1 4706 my($self, $plugin) = @_;
191 10         39 my $instance_id = $plugin->instance_id;
192 10   100     78 $self->{plugin_instance_prop}->{$instance_id} ||= {};
193             }
194              
195              
196             sub runtime_prop
197             {
198 1056     1056 1 34160 shift->{runtime_prop};
199             }
200              
201              
202             sub hook_prop
203             {
204 283     283 1 1775 shift->{hook_prop};
205             }
206              
207             sub _command_prop
208             {
209 23     23   63 my($self) = @_;
210              
211             return {
212 23         75 alien => {
213             install => $self->install_prop,
214             runtime => $self->runtime_prop,
215             hook => $self->hook_prop,
216             meta => $self->meta_prop,
217             },
218             perl => {
219             config => \%Config::Config,
220             },
221             env => \%ENV,
222             };
223             }
224              
225              
226             sub checkpoint
227             {
228 40     40 1 178 my($self) = @_;
229 40         125 my $root = $self->root;
230             _path("$root/state.json")->spew(
231             JSON::PP->new->pretty->canonical(1)->ascii->encode({
232             install => $self->install_prop,
233             runtime => $self->runtime_prop,
234             plugin_instance => $self->{plugin_instance_prop},
235             args => $self->{args},
236             })
237 40         220 );
238 40         62711 $self;
239             }
240              
241              
242             sub root
243             {
244 475     475 1 1031 my($self) = @_;
245 475         1111 my $root = $self->install_prop->{root};
246 475 100       6758 _path($root)->mkpath unless -d $root;
247 475         5320 $root;
248             }
249              
250              
251             sub install_type
252             {
253 231     231 1 7236 my($self) = @_;
254 231   66     1582 $self->{runtime_prop}->{install_type} ||= $self->probe;
255             }
256              
257              
258             sub set_prefix
259             {
260 285     285 1 651 my($self, $prefix) = @_;
261              
262 285 100       1447 if($self->meta_prop->{destdir})
263             {
264             $self->runtime_prop->{prefix} =
265 6         23 $self->install_prop->{prefix} = $prefix;
266             }
267             else
268             {
269 279         1208 $self->runtime_prop->{prefix} = $prefix;
270 279         649 $self->install_prop->{prefix} = $self->install_prop->{stage};
271             }
272             }
273              
274              
275             sub set_stage
276             {
277 290     290 1 1658 my($self, $dir) = @_;
278 290         1291 $self->install_prop->{stage} = $dir;
279             }
280              
281             sub _merge
282             {
283 135     135   266 my %h;
284 135         462 while(@_)
285             {
286 131         266 my $mod = shift;
287 131         219 my $ver = shift;
288 131 100 100     379 if((!defined $h{$mod}) || $ver > $h{$mod})
289 129         338 { $h{$mod} = $ver }
290             }
291 135         583 \%h;
292             }
293              
294              
295             sub requires
296             {
297 240     240 1 8622 my($self, $phase) = @_;
298 240   100     584 $phase ||= 'any';
299 240         695 my $meta = $self->meta;
300             $phase =~ /^(?:any|configure)$/
301             ? $meta->{require}->{$phase} || {}
302 240 100 100     2093 : _merge %{ $meta->{require}->{any} }, %{ $meta->{require}->{$phase} };
  111         338  
  111         397  
303             }
304              
305              
306             sub load_requires
307             {
308 177     177 1 3367 my($self, $phase, $eval) = @_;
309 177         694 my $reqs = $self->requires($phase);
310 177         600 foreach my $mod (keys %$reqs)
311             {
312 75         147 my $ver = $reqs->{$mod};
313             my $check = sub {
314 75     75   207 my $pm = "$mod.pm";
315 75         271 $pm =~ s{::}{/}g;
316 75         6319 require $pm;
317 75         372 };
318 75 100       285 if($eval)
319             {
320 12         26 eval { $check->() };
  12         20  
321 12 100 100     3727 die "Required $mod @{[ $ver || 'undef' ]}, missing" if $@;
  9         105  
322             }
323             else
324             {
325 63         123 $check->();
326             }
327             # note Test::Alien::Build#alienfile_skip_if_missing_prereqs does a regex
328             # on this diagnostic, so if you change it here, change it there too.
329 60 50 0     15749 die "Required $mod $ver, have @{[ $mod->VERSION || 0 ]}" if $ver && ! $mod->VERSION($ver);
  0   66     0  
330              
331             # allow for requires on Alien::Build or Alien::Base
332 58 100       260 next if $mod eq 'Alien::Build';
333 42 100       95 next if $mod eq 'Alien::Base';
334              
335 41 100       347 if($mod->can('bin_dir'))
336             {
337 2         28 push @{ $self->{bin_dir} }, $mod->bin_dir;
  2         19  
338             }
339              
340 41 100 66     543 if(($mod->can('runtime_prop') && $mod->runtime_prop)
      33        
      66        
341             || ($mod->isa('Alien::Base') && $mod->install_type('share')))
342             {
343 2         5 for my $dir (qw(lib share)) {
344 4         42 my $path = _path($mod->dist_dir)->child("$dir/pkgconfig");
345 4 100       219 if(-d $path)
346             {
347 2         35 push @{ $self->{pkg_config_path} }, $path->stringify;
  2         7  
348             }
349             }
350 2         35 my $path = _path($mod->dist_dir)->child('share/aclocal');
351 2 50       126 if(-d $path)
352             {
353 0         0 $path = "$path";
354 0 0       0 if($^O eq 'MSWin32')
355             {
356             # convert to MSYS path
357 0         0 $path =~ s{^([a-z]):}{/$1/}i;
358             }
359 0         0 push @{ $self->{aclocal_path} }, $path;
  0         0  
360             }
361             }
362              
363             # sufficiently new Autotools have a aclocal_dir which will
364             # give us the directories we need.
365 41 50 33     179 if($mod eq 'Alien::Autotools' && $mod->can('aclocal_dir'))
366             {
367 0         0 push @{ $self->{aclocal_path} }, $mod->aclocal_dir;
  0         0  
368             }
369              
370 41 100       342 if($mod->can('alien_helper'))
371             {
372 2         7 my $helpers = $mod->alien_helper;
373 2         11 foreach my $name (sort keys %$helpers)
374             {
375 4         10 my $code = $helpers->{$name};
376 4         9 $self->meta->interpolator->replace_helper($name => $code);
377             }
378             }
379              
380             }
381 160         444 1;
382             }
383              
384             sub _call_hook
385             {
386 657     657   6756 my $self = shift;
387              
388 657         3380 local $ENV{PATH} = $ENV{PATH};
389 657         1190 unshift @PATH, @{ $self->{bin_dir} };
  657         3203  
390              
391 657         18718 local $ENV{PKG_CONFIG_PATH} = $ENV{PKG_CONFIG_PATH};
392 657         1130 unshift @PKG_CONFIG_PATH, @{ $self->{pkg_config_path} };
  657         1710  
393              
394 657         10311 local $ENV{ACLOCAL_PATH} = $ENV{ACLOCAL_PATH};
395             # autoconf uses MSYS paths, even for the ACLOCAL_PATH environment variable, so we can't use Env for this.
396             {
397 657         1105 my @path;
  657         800  
398 657 100       1782 @path = split /:/, $ENV{ACLOCAL_PATH} if defined $ENV{ACLOCAL_PATH};
399 657         967 unshift @path, @{ $self->{aclocal_path} };
  657         1074  
400 657         2013 $ENV{ACLOCAL_PATH} = join ':', @path;
401             }
402              
403 657 100       1996 my $config = ref($_[0]) eq 'HASH' ? shift : {};
404 657         1612 my($name, @args) = @_;
405              
406 657         1600 local $self->{hook_prop} = {};
407              
408 657         1542 $self->meta->call_hook( $config, $name => $self, @args );
409             }
410              
411              
412             sub probe
413             {
414 145     145 1 43906 my($self) = @_;
415 145         729 local $CWD = $self->root;
416 145         7091 my $dir;
417              
418 145         961 my $env = $self->_call_hook('override');
419 145         355 my $type;
420             my $error;
421              
422 145 100       505 $env = '' if $env eq 'default';
423              
424 145 100       345 if($env eq 'share')
425             {
426 3         18 $type = 'share';
427             }
428             else
429             {
430 142         215 $type = eval {
431             $self->_call_hook(
432             {
433             before => sub {
434 144     144   894 $dir = Alien::Build::TempDir->new($self, "probe");
435 144         42820 $CWD = "$dir";
436             },
437             after => sub {
438 144     144   375 $CWD = $self->root;
439             },
440             ok => 'system',
441             continue => sub {
442 124 100   124   403 if($_[0] eq 'system')
443             {
444 51         146 foreach my $name (qw( probe_class probe_instance_id ))
445             {
446 102 100 66     345 if(exists $self->hook_prop->{$name} && defined $self->hook_prop->{$name})
447             {
448 30         53 $self->install_prop->{"system_$name"} = $self->hook_prop->{$name};
449             }
450             }
451 51         174 return undef;
452             }
453             else
454             {
455 73         496 return 1;
456             }
457             },
458             },
459 142         1917 'probe',
460             );
461             };
462 142         2343 $error = $@;
463 142 100       425 $type = 'share' unless defined $type;
464             }
465              
466 145 100       396 if($error)
467             {
468 27 100       103 if($env eq 'system')
469             {
470 1         5 die $error;
471             }
472 26         237 $self->log("error in probe (will do a share install): $@");
473 26         148 $self->log("Don't panic, we will attempt a share build from source if possible.");
474 26         128 $self->log("Do not file a bug unless you expected a system install to succeed.");
475 26         95 $type = 'share';
476             }
477              
478 144 100 100     547 if($env && $env ne $type)
479             {
480 1         11 die "requested $env install not available";
481             }
482              
483 143 50       1055 if($type !~ /^(system|share)$/)
484             {
485 0         0 Carp::croak "probe hook returned something other than system or share: $type";
486             }
487              
488 143 100 100     858 if($type eq 'share' && (!$self->meta_prop->{network}) && (!$self->meta_prop->{local_source}))
      66        
489             {
490 1         10 $self->log("install type share requested or detected, but network fetch is turned off");
491 1         9 $self->log("see https://metacpan.org/pod/Alien::Build::Manual::FAQ#Network-fetch-is-turned-off");
492 1         173 Carp::croak "network fetch is turned off";
493             }
494              
495 142         438 $self->runtime_prop->{install_type} = $type;
496              
497 142         562 $type;
498             }
499              
500              
501             sub download
502             {
503 58     58 1 15700 my($self) = @_;
504              
505 58 100       246 return $self unless $self->install_type eq 'share';
506 48 50       625 return $self if $self->install_prop->{complete}->{download};
507              
508 48 100       199 if($self->meta->has_hook('download'))
509             {
510 36         68 my $tmp;
511 36         148 local $CWD;
512 36         918 my $valid = 0;
513              
514             $self->_call_hook(
515             {
516             before => sub {
517 36     36   217 $tmp = Alien::Build::TempDir->new($self, "download");
518 36         11002 $CWD = "$tmp";
519             },
520             verify => sub {
521 35     35   154 my @list = grep { $_->basename !~ /^\./, } _path('.')->children;
  36         4617  
522              
523 35         1257 my $count = scalar @list;
524              
525 35 100       217 if($count == 0)
    100          
526             {
527 2         20 die "no files downloaded";
528             }
529             elsif($count == 1)
530             {
531 32         85 my($archive) = $list[0];
532 32 50       116 if(-d $archive)
533             {
534 0         0 $self->log("single dir, assuming directory");
535             }
536             else
537             {
538 32         763 $self->log("single file, assuming archive");
539             }
540 32         308 $self->install_prop->{download} = $archive->absolute->stringify;
541 32         108 $self->install_prop->{complete}->{download} = 1;
542 32         96 $valid = 1;
543             }
544             else
545             {
546 1         5 $self->log("multiple files, assuming directory");
547 1         8 $self->install_prop->{complete}->{download} = 1;
548 1         4 $self->install_prop->{download} = _path('.')->absolute->stringify;
549 1         7 $valid = 1;
550             }
551             },
552             after => sub {
553 36     36   123 $CWD = $self->root;
554             },
555             },
556 36         594 'download',
557             );
558              
559 33 50       492 return $self if $valid;
560             }
561             else
562             {
563             # This will call the default download hook
564             # defined in Core::Download since the recipe
565             # does not provide a download hook
566 12         44 return $self->_call_hook('download');
567             }
568              
569 0         0 die "download failed";
570             }
571              
572              
573             sub fetch
574             {
575 26     26 1 46538 my $self = shift;
576 26         157 $self->_call_hook( 'fetch' => @_ );
577             }
578              
579              
580             sub decode
581             {
582 5     5 1 26492 my($self, $res) = @_;
583 5         27 $self->_call_hook( decode => $res );
584             }
585              
586              
587             sub prefer
588             {
589 4     4 1 32 my($self, $res) = @_;
590 4         16 $self->_call_hook( prefer => $res );
591             }
592              
593              
594             sub extract
595             {
596 77     77 1 30547 my($self, $archive) = @_;
597              
598 77   66     425 $archive ||= $self->install_prop->{download};
599              
600 77 50       234 unless(defined $archive)
601             {
602 0         0 die "tried to call extract before download";
603             }
604              
605 77         194 my $nick_name = 'build';
606              
607 77 100       282 if($self->meta_prop->{out_of_source})
608             {
609 6         9 $nick_name = 'extract';
610 6         11 my $extract = $self->install_prop->{extract};
611 6 100 66     71 return $extract if defined $extract && -d $extract;
612             }
613              
614 74         155 my $tmp;
615 74         309 local $CWD;
616 74         1774 my $ret;
617              
618             $self->_call_hook({
619              
620             before => sub {
621             # called build instead of extract, because this
622             # will be used for the build step, and technically
623             # extract is a substage of build anyway.
624 74     74   601 $tmp = Alien::Build::TempDir->new($self, $nick_name);
625 74         23388 $CWD = "$tmp";
626             },
627             verify => sub {
628              
629 74     74   360 my $path = '.';
630 74 100 100     947 if($self->meta_prop->{out_of_source} && $self->install_prop->{extract})
631             {
632 1         3 $path = $self->install_prop->{extract};
633             }
634              
635 74 50       463 my @list = grep { $_->basename !~ /^\./ && $_->basename ne 'pax_global_header' } _path($path)->children;
  112         14303  
636              
637 74         3675 my $count = scalar @list;
638              
639 74 100 100     946 if($count == 0)
    100          
640             {
641 1         8 die "no files extracted";
642             }
643             elsif($count == 1 && -d $list[0])
644             {
645 28         704 $ret = $list[0]->absolute->stringify;
646             }
647             else
648             {
649 45         252 $ret = "$tmp";
650             }
651              
652             },
653             after => sub {
654 74     74   503 $CWD = $self->root;
655             },
656              
657 74         1363 }, 'extract', $archive);
658              
659 73   66     1288 $self->install_prop->{extract} ||= $ret;
660 73 50       518 $ret ? $ret : ();
661             }
662              
663              
664             sub build
665             {
666 42     42 1 9142 my($self) = @_;
667              
668             # save the evironment, in case some plugins decide
669             # to alter it. Or us! See just a few lines below.
670 42         3323 local %ENV = %ENV;
671              
672 42         229 my $stage = _path($self->install_prop->{stage});
673 42         1388 $stage->mkpath;
674              
675 42         4756 my $tmp;
676              
677 42 100       192 if($self->install_type eq 'share')
    50          
678             {
679 30         423 foreach my $suffix ('', '_ffi')
680             {
681 60         997 local $CWD;
682 60 100       1388 delete $ENV{DESTDIR} unless $self->meta_prop->{destdir};
683              
684 60 100       163 my %env_meta = %{ $self->meta_prop ->{env} || {} };
  60         138  
685 60 100       147 my %env_inst = %{ $self->install_prop->{env} || {} };
  60         130  
686              
687 60 100       176 if($self->meta_prop->{env_interpolate})
688             {
689 2         5 foreach my $key (keys %env_meta)
690             {
691 2         4 $env_meta{$key} = $self->meta->interpolator->interpolate($env_meta{$key});
692             }
693             }
694              
695 60         3733 %ENV = (%ENV, %env_meta);
696 60         2397 %ENV = (%ENV, %env_inst);
697              
698 60         201 my $destdir;
699              
700             $self->_call_hook(
701             {
702             before => sub {
703 60 100   60   166 if($self->meta_prop->{out_of_source})
704             {
705 6         37 $self->extract;
706 6         462 $CWD = $tmp = Alien::Build::TempDir->new($self, 'build');
707             }
708             else
709             {
710 54         279 $CWD = $tmp = $self->extract;
711             }
712 60 100       8904 if($self->meta_prop->{destdir})
713             {
714 12         68 $destdir = Alien::Build::TempDir->new($self, 'destdir');
715 12         3020 $ENV{DESTDIR} = "$destdir";
716             }
717 60         449 $self->_call_hook({ all => 1 }, "patch${suffix}");
718             },
719             after => sub {
720 60 100   60   208 $destdir = "$destdir" if $destdir;
721             },
722 60         741 }, "build${suffix}");
723              
724 60   100     814 $self->install_prop->{"_ab_build@{[ $suffix || '_share' ]}"} = "$CWD";
  60         637  
725              
726 60   100     198 $self->_call_hook("gather@{[ $suffix || '_share' ]}");
  60         402  
727             }
728             }
729              
730             elsif($self->install_type eq 'system')
731             {
732 12         39 local $CWD = $self->root;
733 12         450 my $dir;
734              
735             $self->_call_hook(
736             {
737             before => sub {
738 12     12   73 $dir = Alien::Build::TempDir->new($self, "gather");
739 12         3296 $CWD = "$dir";
740             },
741             after => sub {
742 12     12   50 $CWD = $self->root;
743             },
744             },
745 12         164 'gather_system',
746             );
747              
748 12         89 $self->install_prop->{finished} = 1;
749 12         34 $self->install_prop->{complete}->{gather_system} = 1;
750             }
751              
752 42         3124 $self;
753             }
754              
755              
756             sub test
757             {
758 6     6 1 5796 my($self) = @_;
759              
760 6 100       25 if($self->install_type eq 'share')
761             {
762 4         11 foreach my $suffix ('_share', '_ffi')
763             {
764 7 100       38 if($self->meta->has_hook("test$suffix"))
765             {
766 4         15 my $dir = $self->install_prop->{"_ab_build$suffix"};
767 4 50 33     70 Carp::croak("no build directory to run tests") unless $dir && -d $dir;
768 4         21 local $CWD = $dir;
769 4         186 $self->_call_hook("test$suffix");
770             }
771             }
772             }
773             else
774             {
775 2 50       4 if($self->meta->has_hook("test_system"))
776             {
777 2         10 my $dir = Alien::Build::TempDir->new($self, "test");
778 2         656 local $CWD = "$dir";
779 2         103 $self->_call_hook("test_system");
780             }
781             }
782              
783             }
784              
785              
786             sub clean_install
787             {
788 3     3 1 5 my($self) = @_;
789 3 100       13 if($self->install_type eq 'share')
790             {
791 2         142 $self->_call_hook("clean_install");
792             }
793             }
794              
795              
796             sub system
797             {
798 2     2 1 3696 my($self, $command, @args) = @_;
799              
800 2         9 my $prop = $self->_command_prop;
801              
802             ($command, @args) = map {
803 2         5 $self->meta->interpolator->interpolate($_, $prop)
  3         5  
804             } ($command, @args);
805              
806 2         11 $self->log("+ $command @args");
807              
808             scalar @args
809 2 100       17 ? system $command, @args
810             : system $command;
811             }
812              
813              
814             sub log
815             {
816 397     397 1 2264 my(undef, $message) = @_;
817 397         1531 my $caller = [caller];
818 397         851 chomp $message;
819 397         1407 foreach my $line (split /\n/, $message)
820             {
821 408         2212 Alien::Build::Log->default->log(
822             caller => $caller,
823             message => $line,
824             );
825             }
826             }
827              
828              
829             {
830             my %meta;
831              
832             sub meta
833             {
834 8585     8585 1 33326 my($class) = @_;
835 8585 100       16653 $class = ref $class if ref $class;
836 8585   66     25776 $meta{$class} ||= Alien::Build::Meta->new( class => $class );
837             }
838             }
839              
840             package Alien::Build::Meta;
841              
842             our @CARP_NOT = qw( alienfile );
843              
844             sub new
845             {
846 339     339   1264 my($class, %args) = @_;
847 339         3163 my $self = bless {
848             phase => 'any',
849             build_suffix => '',
850             require => {
851             any => {},
852             share => {},
853             system => {},
854             },
855             around => {},
856             prop => {},
857             %args,
858             }, $class;
859 339         2847 $self;
860             }
861              
862              
863             sub prop
864             {
865 4823     4823   14993 shift->{prop};
866             }
867              
868             sub filename
869             {
870 549     549   1221 my($self, $new) = @_;
871 549 100       1365 $self->{filename} = $new if defined $new;
872 549         2924 $self->{filename};
873             }
874              
875              
876             sub add_requires
877             {
878 438     438   631 my $self = shift;
879 438         565 my $phase = shift;
880 438         902 while(@_)
881             {
882 267         359 my $module = shift;
883 267         329 my $version = shift;
884 267         515 my $old = $self->{require}->{$phase}->{$module};
885 267 100 66     759 if((!defined $old) || $version > $old)
886 249         734 { $self->{require}->{$phase}->{$module} = $version }
887             }
888 438         726 $self;
889             }
890              
891              
892             sub interpolator
893             {
894 112     112   275 my($self, $new) = @_;
895 112 50       446 if(defined $new)
    100          
896             {
897 0 0       0 if(defined $self->{intr})
898             {
899 0         0 Carp::croak "tried to set interpolator twice";
900             }
901 0 0       0 if(ref $new)
902             {
903 0         0 $self->{intr} = $new;
904             }
905             else
906             {
907 0         0 $self->{intr} = $new->new;
908             }
909             }
910             elsif(!defined $self->{intr})
911             {
912 49         3706 require Alien::Build::Interpolate::Default;
913 49         486 $self->{intr} = Alien::Build::Interpolate::Default->new;
914             }
915 112         404 $self->{intr};
916             }
917              
918              
919             sub has_hook
920             {
921 79     79   572 my($self, $name) = @_;
922 79         404 defined $self->{hook}->{$name};
923             }
924              
925              
926             sub _instr
927             {
928 2939     2939   4190 my($self, $name, $instr) = @_;
929 2939 100       5971 if(ref($instr) eq 'CODE')
    50          
930             {
931 2883         7342 return $instr;
932             }
933             elsif(ref($instr) eq 'ARRAY')
934             {
935 56         724 my %phase = (
936             download => 'share',
937             fetch => 'share',
938             decode => 'share',
939             prefer => 'share',
940             extract => 'share',
941             patch => 'share',
942             patch_ffi => 'share',
943             build => 'share',
944             build_ffi => 'share',
945             stage => 'share',
946             gather_ffi => 'share',
947             gather_share => 'share',
948             gather_system => 'system',
949             test_ffi => 'share',
950             test_share => 'share',
951             test_system => 'system',
952             );
953 56         3715 require Alien::Build::CommandSequence;
954 56         354 my $seq = Alien::Build::CommandSequence->new(@$instr);
955 56   100     265 $seq->apply_requirements($self, $phase{$name} || 'any');
956 56         265 return $seq;
957             }
958             else
959             {
960 0         0 Carp::croak "type not supported as a hook";
961             }
962             }
963              
964             sub register_hook
965             {
966 603     603   28304 my($self, $name, $instr) = @_;
967 603         846 push @{ $self->{hook}->{$name} }, _instr $self, $name, $instr;
  603         2049  
968 603         1240 $self;
969             }
970              
971              
972             sub default_hook
973             {
974 2336     2336   4727 my($self, $name, $instr) = @_;
975 2336         3945 $self->{default_hook}->{$name} = _instr $self, $name, $instr;
976 2336         5403 $self;
977             }
978              
979              
980             sub around_hook
981             {
982 2316     2316   4191 my($self, $name, $code) = @_;
983 2316 100       4539 if(my $old = $self->{around}->{$name})
984             {
985             # this is the craziest shit I have ever
986             # come up with.
987             $self->{around}->{$name} = sub {
988 108     108   209 my $orig = shift;
989 108         665 $code->(sub { $old->($orig, @_) }, @_);
  108         356  
990 1317         6442 };
991             }
992             else
993             {
994 999         3740 $self->{around}->{$name} = $code;
995             }
996             }
997              
998             sub after_hook
999             {
1000 1310     1310   2928 my($self, $name, $code) = @_;
1001             $self->around_hook(
1002             $name => sub {
1003 86     86   180 my $orig = shift;
1004 86         213 my $ret = $orig->(@_);
1005 86         1025 $code->(@_);
1006 86         473 $ret;
1007             }
1008 1310         4565 );
1009             }
1010              
1011             sub before_hook
1012             {
1013 17     17   115 my($self, $name, $code) = @_;
1014             $self->around_hook(
1015             $name => sub {
1016 2     2   4 my $orig = shift;
1017 2         10 $code->(@_);
1018 2         9 my $ret = $orig->(@_);
1019 2         6 $ret;
1020             }
1021 17         162 );
1022             }
1023              
1024              
1025             sub call_hook
1026             {
1027 657     657   1042 my $self = shift;
1028 657 50       1525 my %args = ref $_[0] ? %{ shift() } : ();
  657         2013  
1029 657         1450 my($name, @args) = @_;
1030 657         859 my $error;
1031              
1032 657 100       812 my @hooks = @{ $self->{hook}->{$name} || []};
  657         2745  
1033              
1034 657 100       1659 if(@hooks == 0)
1035             {
1036 301 100       980 if(defined $self->{default_hook}->{$name})
    100          
1037             {
1038 234         557 @hooks = ($self->{default_hook}->{$name})
1039             }
1040             elsif(!$args{all})
1041             {
1042 11         2486 Carp::croak "No hooks registered for $name";
1043             }
1044             }
1045              
1046 646         855 my $value;
1047              
1048 646         1250 foreach my $hook (@hooks)
1049             {
1050 605 50       867 if(eval { $args[0]->isa('Alien::Build') })
  605         3378  
1051             {
1052 605         1021 %{ $args[0]->{hook_prop} } = (
  605         1707  
1053             name => $name,
1054             );
1055             }
1056              
1057 605   100 518   3795 my $wrapper = $self->{around}->{$name} || sub { my $code = shift; $code->(@_) };
  518         860  
  518         1011  
1058 605         970 my $value;
1059 605 100       1639 $args{before}->() if $args{before};
1060 605 100       8539 if(ref($hook) eq 'CODE')
1061             {
1062 594         848 $value = eval {
1063 594     589   2835 my $value = $wrapper->(sub { $hook->(@_) }, @args);
  589         1982  
1064 567 100       153433 $args{verify}->('code') if $args{verify};
1065 565         3696 $value;
1066             };
1067             }
1068             else
1069             {
1070             $value = $wrapper->(sub {
1071 11     11   25 eval {
1072 11         88 $hook->execute(@_);
1073 8 100       169 $args{verify}->('command') if $args{verify};
1074             };
1075 11 100       376 defined $args{ok} ? $args{ok} : 1;
1076 11         93 }, @args);
1077             }
1078 605         2225 $error = $@;
1079 605 100       1832 $args{after}->() if $args{after};
1080 605 100       6832 if($args{all})
1081             {
1082 5 50       20 die if $error;
1083             }
1084             else
1085             {
1086 600 100       2525 next if $error;
1087 567 100 100     1674 next if $args{continue} && $args{continue}->($value);
1088 494         7745 return $value;
1089             }
1090             }
1091              
1092 152 100 66     1082 die $error if $error && ! $args{all};
1093              
1094 124         1124 $value;
1095             }
1096              
1097              
1098             sub apply_plugin
1099             {
1100 2821     2821   5072 my($self, $name, @args) = @_;
1101              
1102 2821         5692 my $class;
1103             my $pm;
1104 2821         0 my $found;
1105              
1106 2821 100       6307 if($name =~ /^=(.*)$/)
1107             {
1108 1         7 $class = $1;
1109 1         3 $pm = "$class.pm";
1110 1         5 $pm =~ s!::!/!g;
1111 1         3 $found = 1;
1112             }
1113              
1114 2821 50 66     7199 if($name !~ /::/ && !$found)
1115             {
1116 26         84 foreach my $inc (@INC)
1117             {
1118             # TODO: allow negotiators to work with @INC hooks
1119 100 50       1231 next if ref $inc;
1120 100         424 my $file = Path::Tiny->new("$inc/Alien/Build/Plugin/$name/Negotiate.pm");
1121 100 100       2757 if(-r $file)
1122             {
1123 22         479 $class = "Alien::Build::Plugin::${name}::Negotiate";
1124 22         48 $pm = "Alien/Build/Plugin/$name/Negotiate.pm";
1125 22         68 $found = 1;
1126 22         69 last;
1127             }
1128             }
1129             }
1130              
1131 2821 100       5237 unless($found)
1132             {
1133 2798         4483 $class = "Alien::Build::Plugin::$name";
1134 2798         4399 $pm = "Alien/Build/Plugin/$name.pm";
1135 2798         7835 $pm =~ s{::}{/}g;
1136             }
1137              
1138 2821 100       176166 require $pm unless $class->can('new');
1139 2821         10173 my $plugin = $class->new(@args);
1140 2821         10366 $plugin->init($self);
1141 2821         9475 $self;
1142             }
1143              
1144             package Alien::Build::TempDir;
1145              
1146             # TODO: it's confusing that there is both a AB::TempDir and AB::Temp
1147             # although they do different things. there could maybe be a better
1148             # name for AB::TempDir (maybe AB::TempBuildDir, though that is a little
1149             # redundant). Happily both are private classes, and either are able to
1150             # rename, if a good name can be thought of.
1151              
1152 50     50   636 use overload '""' => sub { shift->as_string }, bool => sub { 1 }, fallback => 1;
  50     292   150  
  50         677  
  354         4408  
  24         53  
1153 50     50   6269 use File::Temp qw( tempdir );
  50         35845  
  50         12661  
1154              
1155             sub new
1156             {
1157 293     293   1392 my($class, $build, $name) = @_;
1158 293         679 my $root = $build->install_prop->{root};
1159 293 50       4256 Path::Tiny->new($root)->mkpath unless -d $root;
1160 293         2065 bless {
1161             dir => Path::Tiny->new(tempdir( "${name}_XXXX", DIR => $root)),
1162             }, $class;
1163             }
1164              
1165             sub as_string
1166             {
1167 354     354   1173 shift->{dir}->stringify;
1168             }
1169              
1170             sub DESTROY
1171             {
1172 293     293   7014 my($self) = @_;
1173 293 100 66     1857 if(-d $self->{dir} && $self->{dir}->children == 0)
1174             {
1175 169 50       18580 rmdir($self->{dir}) || warn "unable to remove @{[ $self->{dir} ]} $!";
  0            
1176             }
1177             }
1178              
1179             1;
1180              
1181             __END__