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   454151 use strict;
  50         112  
  50         1500  
4 50     50   243 use warnings;
  50         105  
  50         1153  
5 50     50   766 use 5.008004;
  50         183  
6 50     50   3006 use Path::Tiny ();
  50         34370  
  50         892  
7 50     50   284 use Carp ();
  50         113  
  50         1050  
8 50     50   16167 use File::chdir;
  50         112499  
  50         6308  
9 50     50   30324 use JSON::PP ();
  50         589008  
  50         1717  
10 50     50   39678 use Env qw( @PATH @PKG_CONFIG_PATH );
  50         118205  
  50         401  
11 50     50   9813 use Config ();
  50         152  
  50         938  
12 50     50   23117 use Alien::Build::Log;
  50         125  
  50         20913  
13              
14             # ABSTRACT: Build external dependencies for use in CPAN
15             our $VERSION = '2.45'; # VERSION
16              
17              
18 909     909   4568 sub _path { goto \&Path::Tiny::path }
19              
20              
21             sub new
22             {
23 339     339 1 18749 my($class, %args) = @_;
24             my $self = bless {
25             install_prop => {
26             root => _path($args{root} || "_alien")->absolute->stringify,
27 339 100 100     1694 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     25431 $args{filename} || do {
40             my(undef, $filename) = caller;
41             _path($filename)->absolute->stringify;
42             }
43             );
44              
45 339 100       1057 if($args{meta_prop})
46             {
47 19         30 $self->meta->prop->{$_} = $args{meta_prop}->{$_} for keys %{ $args{meta_prop} };
  19         72  
48             }
49              
50 339         1011 $self;
51             }
52              
53              
54             my $count = 0;
55              
56             sub load
57             {
58 338     338 1 31998 my(undef, $alienfile, @args) = @_;
59              
60 338   50     2948 my $rcfile = Path::Tiny->new($ENV{ALIEN_BUILD_RC} || '~/.alienbuild/rc.pl')->absolute;
61 338 100       38432 if(-r $rcfile)
62             {
63 19         1761 require Alien::Build::rc;
64             package Alien::Build::rc;
65 19         81 require $rcfile;
66             }
67              
68 338 100       10287 unless(-r $alienfile)
69             {
70 1         192 Carp::croak "Unable to read alienfile: $alienfile";
71             }
72              
73 337         1518 my $file = _path $alienfile;
74 337         10340 my $name = $file->parent->basename;
75 337         27959 $name =~ s/^alien-//i;
76 337         2109 $name =~ s/[^a-z]//g;
77 337 100       1250 $name = 'x' if $name eq '';
78 337         1161 $name = ucfirst $name;
79              
80 337         1015 my $class = "Alien::Build::Auto::$name@{[ $count++ ]}";
  337         1386  
81              
82 50     50   397 { no strict 'refs';
  50         118  
  50         298984  
  337         817  
83 337         636 @{ "${class}::ISA" } = ('Alien::Build');
  337         9487  
84 337         3593 *{ "${class}::Alienfile::meta" } = sub {
85 1011     1011   2444 $class =~ s{::Alienfile$}{};
86 1011         2141 $class->meta;
87 337         2888 }};
88              
89 337         1465 my @preload = qw( Core::Setup Core::Download Core::FFI Core::Override Core::CleanInstall );
90 337         860 push @preload, @Alien::Build::rc::PRELOAD;
91             push @preload, split /;/, $ENV{ALIEN_BUILD_PRELOAD}
92 337 100       1428 if defined $ENV{ALIEN_BUILD_PRELOAD};
93              
94 337         1157 my @postload = qw( Core::Legacy Core::Gather Core::Tail );
95 337         669 push @postload, @Alien::Build::rc::POSTLOAD;
96             push @postload, split /;/, $ENV{ALIEN_BUILD_POSTLOAD}
97 337 50       1067 if defined $ENV{ALIEN_BUILD_POSTLOAD};
98              
99 337         1496 my $self = $class->new(
100             filename => $file->absolute->stringify,
101             @args,
102             );
103              
104 337         4008 require alienfile;
105              
106 337         1025 foreach my $preload (@preload)
107             {
108 1707 100       5125 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         1089 eval '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . qq{
114             package ${class}::Alienfile;
115 337         1326 do '@{[ $file->absolute->stringify ]}';
116             die \$\@ if \$\@;
117             };
118 337 100       1914 die $@ if $@;
119             ## use critic
120              
121 322         932 foreach my $postload (@postload)
122             {
123 968 100       3317 ref $postload eq 'CODE' ? $postload->($self->meta) : $self->meta->apply_plugin($postload);
124             }
125              
126 322         2580 $self->{args} = \@args;
127 322 100       975 unless(defined $self->meta->prop->{arch})
128             {
129 320         768 $self->meta->prop->{arch} = 1;
130             }
131              
132 322 100       799 unless(defined $self->meta->prop->{network})
133             {
134 319         715 $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     1262 $self->meta->prop->{network} = 0 if (defined $ENV{ALIEN_INSTALL_NETWORK}) && ! $ENV{ALIEN_INSTALL_NETWORK};
138             }
139              
140 322 100       710 unless(defined $self->meta->prop->{local_source})
141             {
142 318 100       717 if(! defined $self->meta->prop->{start_url})
    100          
143             {
144 283         775 $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         68 my $scheme = $1;
151 20         62 $self->meta->prop->{local_source} = $scheme eq 'file';
152             }
153             else
154             {
155 15         45 $self->meta->prop->{local_source} = 1;
156             }
157             }
158              
159 322         2235 return $self;
160             }
161              
162              
163             sub resume
164             {
165 24     24 1 9522 my(undef, $alienfile, $root) = @_;
166 24         114 my $h = JSON::PP::decode_json(_path("$root/state.json")->slurp);
167 24         133121 my $self = Alien::Build->load("$alienfile", @{ $h->{args} });
  24         234  
168 24         94 $self->{install_prop} = $h->{install};
169 24         54 $self->{plugin_instance_prop} = $h->{plugin_instance};
170 24         59 $self->{runtime_prop} = $h->{runtime};
171 24         111 $self;
172             }
173              
174              
175             sub meta_prop
176             {
177 957     957 1 6511 my($class) = @_;
178 957         2261 $class->meta->prop;
179             }
180              
181              
182             sub install_prop
183             {
184 2615     2615 1 86013 shift->{install_prop};
185             }
186              
187              
188             sub plugin_instance_prop
189             {
190 10     10 1 5496 my($self, $plugin) = @_;
191 10         58 my $instance_id = $plugin->instance_id;
192 10   100     81 $self->{plugin_instance_prop}->{$instance_id} ||= {};
193             }
194              
195              
196             sub runtime_prop
197             {
198 1056     1056 1 35796 shift->{runtime_prop};
199             }
200              
201              
202             sub hook_prop
203             {
204 283     283 1 1952 shift->{hook_prop};
205             }
206              
207             sub _command_prop
208             {
209 23     23   61 my($self) = @_;
210              
211             return {
212 23         88 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 115 my($self) = @_;
229 40         148 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         273 );
238 40         73961 $self;
239             }
240              
241              
242             sub root
243             {
244 475     475 1 1113 my($self) = @_;
245 475         1248 my $root = $self->install_prop->{root};
246 475 100       8051 _path($root)->mkpath unless -d $root;
247 475         5755 $root;
248             }
249              
250              
251             sub install_type
252             {
253 231     231 1 7222 my($self) = @_;
254 231   66     1766 $self->{runtime_prop}->{install_type} ||= $self->probe;
255             }
256              
257              
258             sub set_prefix
259             {
260 285     285 1 774 my($self, $prefix) = @_;
261              
262 285 100       1546 if($self->meta_prop->{destdir})
263             {
264             $self->runtime_prop->{prefix} =
265 6         22 $self->install_prop->{prefix} = $prefix;
266             }
267             else
268             {
269 279         1379 $self->runtime_prop->{prefix} = $prefix;
270 279         695 $self->install_prop->{prefix} = $self->install_prop->{stage};
271             }
272             }
273              
274              
275             sub set_stage
276             {
277 290     290 1 1751 my($self, $dir) = @_;
278 290         1435 $self->install_prop->{stage} = $dir;
279             }
280              
281             sub _merge
282             {
283 135     135   376 my %h;
284 135         468 while(@_)
285             {
286 131         257 my $mod = shift;
287 131         241 my $ver = shift;
288 131 100 100     507 if((!defined $h{$mod}) || $ver > $h{$mod})
289 129         373 { $h{$mod} = $ver }
290             }
291 135         663 \%h;
292             }
293              
294              
295             sub requires
296             {
297 240     240 1 9545 my($self, $phase) = @_;
298 240   100     641 $phase ||= 'any';
299 240         702 my $meta = $self->meta;
300             $phase =~ /^(?:any|configure)$/
301             ? $meta->{require}->{$phase} || {}
302 240 100 100     2328 : _merge %{ $meta->{require}->{any} }, %{ $meta->{require}->{$phase} };
  111         384  
  111         420  
303             }
304              
305              
306             sub load_requires
307             {
308 177     177 1 3831 my($self, $phase, $eval) = @_;
309 177         829 my $reqs = $self->requires($phase);
310 177         789 foreach my $mod (keys %$reqs)
311             {
312 73         173 my $ver = $reqs->{$mod};
313             my $check = sub {
314 73     73   209 my $pm = "$mod.pm";
315 73         285 $pm =~ s{::}{/}g;
316 73         7171 require $pm;
317 73         394 };
318 73 100       245 if($eval)
319             {
320 11         17 eval { $check->() };
  11         24  
321 11 100 100     4246 die "Required $mod @{[ $ver || 'undef' ]}, missing" if $@;
  9         129  
322             }
323             else
324             {
325 62         133 $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 58 50 0     18126 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 56 100       293 next if $mod eq 'Alien::Build';
333 40 100       108 next if $mod eq 'Alien::Base';
334              
335 39 100       402 if($mod->can('bin_dir'))
336             {
337 2         6 push @{ $self->{bin_dir} }, $mod->bin_dir;
  2         21  
338             }
339              
340 39 100 66     736 if(($mod->can('runtime_prop') && $mod->runtime_prop)
      33        
      66        
341             || ($mod->isa('Alien::Base') && $mod->install_type('share')))
342             {
343 2         20 for my $dir (qw(lib share)) {
344 4         72 my $path = _path($mod->dist_dir)->child("$dir/pkgconfig");
345 4 100       255 if(-d $path)
346             {
347 2         47 push @{ $self->{pkg_config_path} }, $path->stringify;
  2         10  
348             }
349             }
350 2         41 my $path = _path($mod->dist_dir)->child('share/aclocal');
351 2 50       118 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 39 50 33     239 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 39 100       369 if($mod->can('alien_helper'))
371             {
372 2         10 my $helpers = $mod->alien_helper;
373 2         21 foreach my $name (sort keys %$helpers)
374             {
375 4         11 my $code = $helpers->{$name};
376 4         16 $self->meta->interpolator->replace_helper($name => $code);
377             }
378             }
379              
380             }
381 160         517 1;
382             }
383              
384             sub _call_hook
385             {
386 657     657   7453 my $self = shift;
387              
388 657         3865 local $ENV{PATH} = $ENV{PATH};
389 657         1336 unshift @PATH, @{ $self->{bin_dir} };
  657         3462  
390              
391 657         21203 local $ENV{PKG_CONFIG_PATH} = $ENV{PKG_CONFIG_PATH};
392 657         1307 unshift @PKG_CONFIG_PATH, @{ $self->{pkg_config_path} };
  657         1912  
393              
394 657         11527 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         1017  
398 657 100       1923 @path = split /:/, $ENV{ACLOCAL_PATH} if defined $ENV{ACLOCAL_PATH};
399 657         1025 unshift @path, @{ $self->{aclocal_path} };
  657         1240  
400 657         2253 $ENV{ACLOCAL_PATH} = join ':', @path;
401             }
402              
403 657 100       2109 my $config = ref($_[0]) eq 'HASH' ? shift : {};
404 657         1599 my($name, @args) = @_;
405              
406 657         1876 local $self->{hook_prop} = {};
407              
408 657         1665 $self->meta->call_hook( $config, $name => $self, @args );
409             }
410              
411              
412             sub probe
413             {
414 145     145 1 48218 my($self) = @_;
415 145         868 local $CWD = $self->root;
416 145         7971 my $dir;
417              
418 145         1150 my $env = $self->_call_hook('override');
419 145         381 my $type;
420             my $error;
421              
422 145 100       604 $env = '' if $env eq 'default';
423              
424 145 100       422 if($env eq 'share')
425             {
426 3         28 $type = 'share';
427             }
428             else
429             {
430 142         265 $type = eval {
431             $self->_call_hook(
432             {
433             before => sub {
434 144     144   1034 $dir = Alien::Build::TempDir->new($self, "probe");
435 144         46823 $CWD = "$dir";
436             },
437             after => sub {
438 144     144   452 $CWD = $self->root;
439             },
440             ok => 'system',
441             continue => sub {
442 124 100   124   475 if($_[0] eq 'system')
443             {
444 51         134 foreach my $name (qw( probe_class probe_instance_id ))
445             {
446 102 100 66     405 if(exists $self->hook_prop->{$name} && defined $self->hook_prop->{$name})
447             {
448 30         52 $self->install_prop->{"system_$name"} = $self->hook_prop->{$name};
449             }
450             }
451 51         212 return undef;
452             }
453             else
454             {
455 73         574 return 1;
456             }
457             },
458             },
459 142         2127 'probe',
460             );
461             };
462 142         2636 $error = $@;
463 142 100       554 $type = 'share' unless defined $type;
464             }
465              
466 145 100       418 if($error)
467             {
468 27 100       110 if($env eq 'system')
469             {
470 1         7 die $error;
471             }
472 26         247 $self->log("error in probe (will do a share install): $@");
473 26         152 $self->log("Don't panic, we will attempt a share build from source if possible.");
474 26         127 $self->log("Do not file a bug unless you expected a system install to succeed.");
475 26         90 $type = 'share';
476             }
477              
478 144 100 100     517 if($env && $env ne $type)
479             {
480 1         18 die "requested $env install not available";
481             }
482              
483 143 50       1240 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     916 if($type eq 'share' && (!$self->meta_prop->{network}) && (!$self->meta_prop->{local_source}))
      66        
489             {
490 1         9 $self->log("install type share requested or detected, but network fetch is turned off");
491 1         7 $self->log("see https://metacpan.org/pod/Alien::Build::Manual::FAQ#Network-fetch-is-turned-off");
492 1         208 Carp::croak "network fetch is turned off";
493             }
494              
495 142         457 $self->runtime_prop->{install_type} = $type;
496              
497 142         662 $type;
498             }
499              
500              
501             sub download
502             {
503 58     58 1 13826 my($self) = @_;
504              
505 58 100       257 return $self unless $self->install_type eq 'share';
506 48 50       709 return $self if $self->install_prop->{complete}->{download};
507              
508 48 100       305 if($self->meta->has_hook('download'))
509             {
510 36         84 my $tmp;
511 36         166 local $CWD;
512 36         908 my $valid = 0;
513              
514             $self->_call_hook(
515             {
516             before => sub {
517 36     36   207 $tmp = Alien::Build::TempDir->new($self, "download");
518 36         11513 $CWD = "$tmp";
519             },
520             verify => sub {
521 35     35   157 my @list = grep { $_->basename !~ /^\./, } _path('.')->children;
  36         5249  
522              
523 35         1389 my $count = scalar @list;
524              
525 35 100       219 if($count == 0)
    100          
526             {
527 2         23 die "no files downloaded";
528             }
529             elsif($count == 1)
530             {
531 32         88 my($archive) = $list[0];
532 32 50       160 if(-d $archive)
533             {
534 0         0 $self->log("single dir, assuming directory");
535             }
536             else
537             {
538 32         891 $self->log("single file, assuming archive");
539             }
540 32         365 $self->install_prop->{download} = $archive->absolute->stringify;
541 32         137 $self->install_prop->{complete}->{download} = 1;
542 32         110 $valid = 1;
543             }
544             else
545             {
546 1         5 $self->log("multiple files, assuming directory");
547 1         7 $self->install_prop->{complete}->{download} = 1;
548 1         6 $self->install_prop->{download} = _path('.')->absolute->stringify;
549 1         18 $valid = 1;
550             }
551             },
552             after => sub {
553 36     36   153 $CWD = $self->root;
554             },
555             },
556 36         650 'download',
557             );
558              
559 33 50       545 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         48 return $self->_call_hook('download');
567             }
568              
569 0         0 die "download failed";
570             }
571              
572              
573             sub fetch
574             {
575 26     26 1 47607 my $self = shift;
576 26         124 $self->_call_hook( 'fetch' => @_ );
577             }
578              
579              
580             sub decode
581             {
582 5     5 1 36511 my($self, $res) = @_;
583 5         29 $self->_call_hook( decode => $res );
584             }
585              
586              
587             sub prefer
588             {
589 4     4 1 28 my($self, $res) = @_;
590 4         16 $self->_call_hook( prefer => $res );
591             }
592              
593              
594             sub extract
595             {
596 77     77 1 26620 my($self, $archive) = @_;
597              
598 77   66     420 $archive ||= $self->install_prop->{download};
599              
600 77 50       241 unless(defined $archive)
601             {
602 0         0 die "tried to call extract before download";
603             }
604              
605 77         212 my $nick_name = 'build';
606              
607 77 100       282 if($self->meta_prop->{out_of_source})
608             {
609 6         14 $nick_name = 'extract';
610 6         25 my $extract = $self->install_prop->{extract};
611 6 100 66     101 return $extract if defined $extract && -d $extract;
612             }
613              
614 74         140 my $tmp;
615 74         323 local $CWD;
616 74         1999 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   606 $tmp = Alien::Build::TempDir->new($self, $nick_name);
625 74         24776 $CWD = "$tmp";
626             },
627             verify => sub {
628              
629 74     74   371 my $path = '.';
630 74 100 100     897 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       444 my @list = grep { $_->basename !~ /^\./ && $_->basename ne 'pax_global_header' } _path($path)->children;
  112         15786  
636              
637 74         4187 my $count = scalar @list;
638              
639 74 100 100     1002 if($count == 0)
    100          
640             {
641 1         14 die "no files extracted";
642             }
643             elsif($count == 1 && -d $list[0])
644             {
645 28         797 $ret = $list[0]->absolute->stringify;
646             }
647             else
648             {
649 45         316 $ret = "$tmp";
650             }
651              
652             },
653             after => sub {
654 74     74   543 $CWD = $self->root;
655             },
656              
657 74         1435 }, 'extract', $archive);
658              
659 73   66     1301 $self->install_prop->{extract} ||= $ret;
660 73 50       543 $ret ? $ret : ();
661             }
662              
663              
664             sub build
665             {
666 42     42 1 9410 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         3649 local %ENV = %ENV;
671              
672 42         283 my $stage = _path($self->install_prop->{stage});
673 42         1369 $stage->mkpath;
674              
675 42         5630 my $tmp;
676              
677 42 100       217 if($self->install_type eq 'share')
    50          
678             {
679 30         482 foreach my $suffix ('', '_ffi')
680             {
681 60         1143 local $CWD;
682 60 100       1924 delete $ENV{DESTDIR} unless $self->meta_prop->{destdir};
683              
684 60 100       155 my %env_meta = %{ $self->meta_prop ->{env} || {} };
  60         191  
685 60 100       179 my %env_inst = %{ $self->install_prop->{env} || {} };
  60         149  
686              
687 60 100       193 if($self->meta_prop->{env_interpolate})
688             {
689 2         7 foreach my $key (keys %env_meta)
690             {
691 2         11 $env_meta{$key} = $self->meta->interpolator->interpolate($env_meta{$key});
692             }
693             }
694              
695 60         3966 %ENV = (%ENV, %env_meta);
696 60         2789 %ENV = (%ENV, %env_inst);
697              
698 60         221 my $destdir;
699              
700             $self->_call_hook(
701             {
702             before => sub {
703 60 100   60   197 if($self->meta_prop->{out_of_source})
704             {
705 6         40 $self->extract;
706 6         504 $CWD = $tmp = Alien::Build::TempDir->new($self, 'build');
707             }
708             else
709             {
710 54         288 $CWD = $tmp = $self->extract;
711             }
712 60 100       10108 if($self->meta_prop->{destdir})
713             {
714 12         89 $destdir = Alien::Build::TempDir->new($self, 'destdir');
715 12         3775 $ENV{DESTDIR} = "$destdir";
716             }
717 60         480 $self->_call_hook({ all => 1 }, "patch${suffix}");
718             },
719             after => sub {
720 60 100   60   220 $destdir = "$destdir" if $destdir;
721             },
722 60         814 }, "build${suffix}");
723              
724 60   100     877 $self->install_prop->{"_ab_build@{[ $suffix || '_share' ]}"} = "$CWD";
  60         985  
725              
726 60   100     191 $self->_call_hook("gather@{[ $suffix || '_share' ]}");
  60         473  
727             }
728             }
729              
730             elsif($self->install_type eq 'system')
731             {
732 12         47 local $CWD = $self->root;
733 12         562 my $dir;
734              
735             $self->_call_hook(
736             {
737             before => sub {
738 12     12   73 $dir = Alien::Build::TempDir->new($self, "gather");
739 12         3440 $CWD = "$dir";
740             },
741             after => sub {
742 12     12   40 $CWD = $self->root;
743             },
744             },
745 12         152 'gather_system',
746             );
747              
748 12         98 $self->install_prop->{finished} = 1;
749 12         39 $self->install_prop->{complete}->{gather_system} = 1;
750             }
751              
752 42         3113 $self;
753             }
754              
755              
756             sub test
757             {
758 6     6 1 7685 my($self) = @_;
759              
760 6 100       38 if($self->install_type eq 'share')
761             {
762 4         17 foreach my $suffix ('_share', '_ffi')
763             {
764 7 100       47 if($self->meta->has_hook("test$suffix"))
765             {
766 4         22 my $dir = $self->install_prop->{"_ab_build$suffix"};
767 4 50 33     86 Carp::croak("no build directory to run tests") unless $dir && -d $dir;
768 4         35 local $CWD = $dir;
769 4         282 $self->_call_hook("test$suffix");
770             }
771             }
772             }
773             else
774             {
775 2 50       7 if($self->meta->has_hook("test_system"))
776             {
777 2         14 my $dir = Alien::Build::TempDir->new($self, "test");
778 2         720 local $CWD = "$dir";
779 2         138 $self->_call_hook("test_system");
780             }
781             }
782              
783             }
784              
785              
786             sub clean_install
787             {
788 3     3 1 8 my($self) = @_;
789 3 100       16 if($self->install_type eq 'share')
790             {
791 2         181 $self->_call_hook("clean_install");
792             }
793             }
794              
795              
796             sub system
797             {
798 2     2 1 4321 my($self, $command, @args) = @_;
799              
800 2         11 my $prop = $self->_command_prop;
801              
802             ($command, @args) = map {
803 2         5 $self->meta->interpolator->interpolate($_, $prop)
  3         8  
804             } ($command, @args);
805              
806 2         16 $self->log("+ $command @args");
807              
808             scalar @args
809 2 100       18 ? system $command, @args
810             : system $command;
811             }
812              
813              
814             sub log
815             {
816 397     397 1 2597 my(undef, $message) = @_;
817 397         1616 my $caller = [caller];
818 397         986 chomp $message;
819 397         1473 foreach my $line (split /\n/, $message)
820             {
821 408         2564 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 34356 my($class) = @_;
835 8585 100       18859 $class = ref $class if ref $class;
836 8585   66     29472 $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   1488 my($class, %args) = @_;
847 339         3902 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         3127 $self;
860             }
861              
862              
863             sub prop
864             {
865 4823     4823   17495 shift->{prop};
866             }
867              
868             sub filename
869             {
870 549     549   1370 my($self, $new) = @_;
871 549 100       1491 $self->{filename} = $new if defined $new;
872 549         3446 $self->{filename};
873             }
874              
875              
876             sub add_requires
877             {
878 438     438   702 my $self = shift;
879 438         639 my $phase = shift;
880 438         1082 while(@_)
881             {
882 267         406 my $module = shift;
883 267         399 my $version = shift;
884 267         686 my $old = $self->{require}->{$phase}->{$module};
885 267 100 66     830 if((!defined $old) || $version > $old)
886 249         828 { $self->{require}->{$phase}->{$module} = $version }
887             }
888 438         873 $self;
889             }
890              
891              
892             sub interpolator
893             {
894 112     112   314 my($self, $new) = @_;
895 112 50       532 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         4621 require Alien::Build::Interpolate::Default;
913 49         564 $self->{intr} = Alien::Build::Interpolate::Default->new;
914             }
915 112         488 $self->{intr};
916             }
917              
918              
919             sub has_hook
920             {
921 79     79   581 my($self, $name) = @_;
922 79         466 defined $self->{hook}->{$name};
923             }
924              
925              
926             sub _instr
927             {
928 2939     2939   5013 my($self, $name, $instr) = @_;
929 2939 100       6790 if(ref($instr) eq 'CODE')
    50          
930             {
931 2883         8160 return $instr;
932             }
933             elsif(ref($instr) eq 'ARRAY')
934             {
935 56         828 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         4478 require Alien::Build::CommandSequence;
954 56         401 my $seq = Alien::Build::CommandSequence->new(@$instr);
955 56   100     347 $seq->apply_requirements($self, $phase{$name} || 'any');
956 56         310 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   29094 my($self, $name, $instr) = @_;
967 603         894 push @{ $self->{hook}->{$name} }, _instr $self, $name, $instr;
  603         2316  
968 603         1417 $self;
969             }
970              
971              
972             sub default_hook
973             {
974 2336     2336   5197 my($self, $name, $instr) = @_;
975 2336         4588 $self->{default_hook}->{$name} = _instr $self, $name, $instr;
976 2336         6461 $self;
977             }
978              
979              
980             sub around_hook
981             {
982 2316     2316   4248 my($self, $name, $code) = @_;
983 2316 100       5080 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   240 my $orig = shift;
989 108         704 $code->(sub { $old->($orig, @_) }, @_);
  108         323  
990 1317         7381 };
991             }
992             else
993             {
994 999         4182 $self->{around}->{$name} = $code;
995             }
996             }
997              
998             sub after_hook
999             {
1000 1310     1310   3263 my($self, $name, $code) = @_;
1001             $self->around_hook(
1002             $name => sub {
1003 86     86   185 my $orig = shift;
1004 86         211 my $ret = $orig->(@_);
1005 86         1142 $code->(@_);
1006 86         599 $ret;
1007             }
1008 1310         5164 );
1009             }
1010              
1011             sub before_hook
1012             {
1013 17     17   153 my($self, $name, $code) = @_;
1014             $self->around_hook(
1015             $name => sub {
1016 2     2   5 my $orig = shift;
1017 2         13 $code->(@_);
1018 2         10 my $ret = $orig->(@_);
1019 2         6 $ret;
1020             }
1021 17         139 );
1022             }
1023              
1024              
1025             sub call_hook
1026             {
1027 657     657   1139 my $self = shift;
1028 657 50       1574 my %args = ref $_[0] ? %{ shift() } : ();
  657         2347  
1029 657         1661 my($name, @args) = @_;
1030 657         1018 my $error;
1031              
1032 657 100       908 my @hooks = @{ $self->{hook}->{$name} || []};
  657         2902  
1033              
1034 657 100       1751 if(@hooks == 0)
1035             {
1036 301 100       1128 if(defined $self->{default_hook}->{$name})
    100          
1037             {
1038 234         656 @hooks = ($self->{default_hook}->{$name})
1039             }
1040             elsif(!$args{all})
1041             {
1042 11         2076 Carp::croak "No hooks registered for $name";
1043             }
1044             }
1045              
1046 646         1027 my $value;
1047              
1048 646         1506 foreach my $hook (@hooks)
1049             {
1050 605 50       979 if(eval { $args[0]->isa('Alien::Build') })
  605         3755  
1051             {
1052 605         1024 %{ $args[0]->{hook_prop} } = (
  605         1929  
1053             name => $name,
1054             );
1055             }
1056              
1057 605   100 518   4637 my $wrapper = $self->{around}->{$name} || sub { my $code = shift; $code->(@_) };
  518         979  
  518         1104  
1058 605         1019 my $value;
1059 605 100       2044 $args{before}->() if $args{before};
1060 605 100       10797 if(ref($hook) eq 'CODE')
1061             {
1062 594         1010 $value = eval {
1063 594     589   3013 my $value = $wrapper->(sub { $hook->(@_) }, @args);
  589         2158  
1064 567 100       156401 $args{verify}->('code') if $args{verify};
1065 565         3822 $value;
1066             };
1067             }
1068             else
1069             {
1070             $value = $wrapper->(sub {
1071 11     11   23 eval {
1072 11         58 $hook->execute(@_);
1073 8 100       110 $args{verify}->('command') if $args{verify};
1074             };
1075 11 100       273 defined $args{ok} ? $args{ok} : 1;
1076 11         77 }, @args);
1077             }
1078 605         2325 $error = $@;
1079 605 100       2058 $args{after}->() if $args{after};
1080 605 100       7964 if($args{all})
1081             {
1082 5 50       25 die if $error;
1083             }
1084             else
1085             {
1086 600 100       2926 next if $error;
1087 567 100 100     1975 next if $args{continue} && $args{continue}->($value);
1088 494         8243 return $value;
1089             }
1090             }
1091              
1092 152 100 66     1171 die $error if $error && ! $args{all};
1093              
1094 124         1275 $value;
1095             }
1096              
1097              
1098             sub apply_plugin
1099             {
1100 2821     2821   5978 my($self, $name, @args) = @_;
1101              
1102 2821         6094 my $class;
1103             my $pm;
1104 2821         0 my $found;
1105              
1106 2821 100       7135 if($name =~ /^=(.*)$/)
1107             {
1108 1         5 $class = $1;
1109 1         4 $pm = "$class.pm";
1110 1         5 $pm =~ s!::!/!g;
1111 1         3 $found = 1;
1112             }
1113              
1114 2821 50 66     8220 if($name !~ /::/ && !$found)
1115             {
1116 26         86 foreach my $inc (@INC)
1117             {
1118             # TODO: allow negotiators to work with @INC hooks
1119 100 50       1448 next if ref $inc;
1120 100         495 my $file = Path::Tiny->new("$inc/Alien/Build/Plugin/$name/Negotiate.pm");
1121 100 100       3146 if(-r $file)
1122             {
1123 22         551 $class = "Alien::Build::Plugin::${name}::Negotiate";
1124 22         60 $pm = "Alien/Build/Plugin/$name/Negotiate.pm";
1125 22         40 $found = 1;
1126 22         81 last;
1127             }
1128             }
1129             }
1130              
1131 2821 100       5677 unless($found)
1132             {
1133 2798         5139 $class = "Alien::Build::Plugin::$name";
1134 2798         4953 $pm = "Alien/Build/Plugin/$name.pm";
1135 2798         12072 $pm =~ s{::}{/}g;
1136             }
1137              
1138 2821 100       199244 require $pm unless $class->can('new');
1139 2821         11669 my $plugin = $class->new(@args);
1140 2821         12228 $plugin->init($self);
1141 2821         10924 $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   733 use overload '""' => sub { shift->as_string }, bool => sub { 1 }, fallback => 1;
  50     269   126  
  50         857  
  354         4820  
  24         72  
1153 50     50   7213 use File::Temp qw( tempdir );
  50         37500  
  50         14446  
1154              
1155             sub new
1156             {
1157 293     293   1534 my($class, $build, $name) = @_;
1158 293         758 my $root = $build->install_prop->{root};
1159 293 50       5195 Path::Tiny->new($root)->mkpath unless -d $root;
1160 293         2164 bless {
1161             dir => Path::Tiny->new(tempdir( "${name}_XXXX", DIR => $root)),
1162             }, $class;
1163             }
1164              
1165             sub as_string
1166             {
1167 354     354   1293 shift->{dir}->stringify;
1168             }
1169              
1170             sub DESTROY
1171             {
1172 293     293   7812 my($self) = @_;
1173 293 100 66     1945 if(-d $self->{dir} && $self->{dir}->children == 0)
1174             {
1175 169 50       21046 rmdir($self->{dir}) || warn "unable to remove @{[ $self->{dir} ]} $!";
  0            
1176             }
1177             }
1178              
1179             1;
1180              
1181             __END__