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   505717 use strict;
  50         141  
  50         1957  
4 50     50   271 use warnings;
  50         118  
  50         1255  
5 50     50   826 use 5.008004;
  50         179  
6 50     50   2722 use Path::Tiny ();
  50         35366  
  50         997  
7 50     50   291 use Carp ();
  50         137  
  50         1460  
8 50     50   17540 use File::chdir;
  50         121712  
  50         6650  
9 50     50   32872 use JSON::PP ();
  50         637327  
  50         1812  
10 50     50   41621 use Env qw( @PATH @PKG_CONFIG_PATH );
  50         126914  
  50         455  
11 50     50   10272 use Config ();
  50         145  
  50         919  
12 50     50   23650 use Alien::Build::Log;
  50         143  
  50         22832  
13              
14             # ABSTRACT: Build external dependencies for use in CPAN
15             our $VERSION = '2.46'; # VERSION
16              
17              
18 909     909   4566 sub _path { goto \&Path::Tiny::path }
19              
20              
21             sub new
22             {
23 339     339 1 19622 my($class, %args) = @_;
24             my $self = bless {
25             install_prop => {
26             root => _path($args{root} || "_alien")->absolute->stringify,
27 339 100 100     1506 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     25442 $args{filename} || do {
40             my(undef, $filename) = caller;
41             _path($filename)->absolute->stringify;
42             }
43             );
44              
45 339 100       925 if($args{meta_prop})
46             {
47 19         31 $self->meta->prop->{$_} = $args{meta_prop}->{$_} for keys %{ $args{meta_prop} };
  19         76  
48             }
49              
50 339         894 $self;
51             }
52              
53              
54             my $count = 0;
55              
56             sub load
57             {
58 338     338 1 31798 my(undef, $alienfile, @args) = @_;
59              
60 338   50     2565 my $rcfile = Path::Tiny->new($ENV{ALIEN_BUILD_RC} || '~/.alienbuild/rc.pl')->absolute;
61 338 100       38112 if(-r $rcfile)
62             {
63 19         1642 require Alien::Build::rc;
64             package Alien::Build::rc;
65 19         67 require $rcfile;
66             }
67              
68 338 100       10793 unless(-r $alienfile)
69             {
70 1         210 Carp::croak "Unable to read alienfile: $alienfile";
71             }
72              
73 337         1438 my $file = _path $alienfile;
74 337         10380 my $name = $file->parent->basename;
75 337         29254 $name =~ s/^alien-//i;
76 337         1982 $name =~ s/[^a-z]//g;
77 337 100       1196 $name = 'x' if $name eq '';
78 337         1007 $name = ucfirst $name;
79              
80 337         936 my $class = "Alien::Build::Auto::$name@{[ $count++ ]}";
  337         1257  
81              
82 50     50   448 { no strict 'refs';
  50         121  
  50         324778  
  337         687  
83 337         570 @{ "${class}::ISA" } = ('Alien::Build');
  337         9128  
84 337         3493 *{ "${class}::Alienfile::meta" } = sub {
85 1011     1011   2345 $class =~ s{::Alienfile$}{};
86 1011         2181 $class->meta;
87 337         2743 }};
88              
89 337         1389 my @preload = qw( Core::Setup Core::Download Core::FFI Core::Override Core::CleanInstall );
90 337         811 push @preload, @Alien::Build::rc::PRELOAD;
91             push @preload, split /;/, $ENV{ALIEN_BUILD_PRELOAD}
92 337 100       1274 if defined $ENV{ALIEN_BUILD_PRELOAD};
93              
94 337         1039 my @postload = qw( Core::Legacy Core::Gather Core::Tail );
95 337         642 push @postload, @Alien::Build::rc::POSTLOAD;
96             push @postload, split /;/, $ENV{ALIEN_BUILD_POSTLOAD}
97 337 50       1123 if defined $ENV{ALIEN_BUILD_POSTLOAD};
98              
99 337         1435 my $self = $class->new(
100             filename => $file->absolute->stringify,
101             @args,
102             );
103              
104 337         3810 require alienfile;
105              
106 337         1043 foreach my $preload (@preload)
107             {
108 1707 100       5183 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         1064 eval '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . qq{
114             package ${class}::Alienfile;
115 337         1224 do '@{[ $file->absolute->stringify ]}';
116             die \$\@ if \$\@;
117             };
118 337 100       1873 die $@ if $@;
119             ## use critic
120              
121 322         978 foreach my $postload (@postload)
122             {
123 968 100       3277 ref $postload eq 'CODE' ? $postload->($self->meta) : $self->meta->apply_plugin($postload);
124             }
125              
126 322         2300 $self->{args} = \@args;
127 322 100       928 unless(defined $self->meta->prop->{arch})
128             {
129 320         694 $self->meta->prop->{arch} = 1;
130             }
131              
132 322 100       740 unless(defined $self->meta->prop->{network})
133             {
134 319         644 $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     1162 $self->meta->prop->{network} = 0 if (defined $ENV{ALIEN_INSTALL_NETWORK}) && ! $ENV{ALIEN_INSTALL_NETWORK};
138             }
139              
140 322 100       735 unless(defined $self->meta->prop->{local_source})
141             {
142 318 100       718 if(! defined $self->meta->prop->{start_url})
    100          
143             {
144 283         607 $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         72 my $scheme = $1;
151 20         67 $self->meta->prop->{local_source} = $scheme eq 'file';
152             }
153             else
154             {
155 15         42 $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 9463 my(undef, $alienfile, $root) = @_;
166 24         116 my $h = JSON::PP::decode_json(_path("$root/state.json")->slurp);
167 24         133178 my $self = Alien::Build->load("$alienfile", @{ $h->{args} });
  24         256  
168 24         90 $self->{install_prop} = $h->{install};
169 24         61 $self->{plugin_instance_prop} = $h->{plugin_instance};
170 24         69 $self->{runtime_prop} = $h->{runtime};
171 24         105 $self;
172             }
173              
174              
175             sub meta_prop
176             {
177 957     957 1 6244 my($class) = @_;
178 957         2305 $class->meta->prop;
179             }
180              
181              
182             sub install_prop
183             {
184 2615     2615 1 82733 shift->{install_prop};
185             }
186              
187              
188             sub plugin_instance_prop
189             {
190 10     10 1 5349 my($self, $plugin) = @_;
191 10         63 my $instance_id = $plugin->instance_id;
192 10   100     89 $self->{plugin_instance_prop}->{$instance_id} ||= {};
193             }
194              
195              
196             sub runtime_prop
197             {
198 1056     1056 1 36981 shift->{runtime_prop};
199             }
200              
201              
202             sub hook_prop
203             {
204 283     283 1 2051 shift->{hook_prop};
205             }
206              
207             sub _command_prop
208             {
209 23     23   57 my($self) = @_;
210              
211             return {
212 23         79 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 132 my($self) = @_;
229 40         119 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         258 );
238 40         71670 $self;
239             }
240              
241              
242             sub root
243             {
244 475     475 1 1104 my($self) = @_;
245 475         1203 my $root = $self->install_prop->{root};
246 475 100       7931 _path($root)->mkpath unless -d $root;
247 475         5497 $root;
248             }
249              
250              
251             sub install_type
252             {
253 231     231 1 7331 my($self) = @_;
254 231   66     1785 $self->{runtime_prop}->{install_type} ||= $self->probe;
255             }
256              
257              
258             sub set_prefix
259             {
260 285     285 1 785 my($self, $prefix) = @_;
261              
262 285 100       1439 if($self->meta_prop->{destdir})
263             {
264             $self->runtime_prop->{prefix} =
265 6         18 $self->install_prop->{prefix} = $prefix;
266             }
267             else
268             {
269 279         1276 $self->runtime_prop->{prefix} = $prefix;
270 279         636 $self->install_prop->{prefix} = $self->install_prop->{stage};
271             }
272             }
273              
274              
275             sub set_stage
276             {
277 290     290 1 1802 my($self, $dir) = @_;
278 290         1429 $self->install_prop->{stage} = $dir;
279             }
280              
281             sub _merge
282             {
283 135     135   341 my %h;
284 135         479 while(@_)
285             {
286 131         372 my $mod = shift;
287 131         268 my $ver = shift;
288 131 100 100     498 if((!defined $h{$mod}) || $ver > $h{$mod})
289 129         379 { $h{$mod} = $ver }
290             }
291 135         694 \%h;
292             }
293              
294              
295             sub requires
296             {
297 240     240 1 10312 my($self, $phase) = @_;
298 240   100     673 $phase ||= 'any';
299 240         685 my $meta = $self->meta;
300             $phase =~ /^(?:any|configure)$/
301             ? $meta->{require}->{$phase} || {}
302 240 100 100     2285 : _merge %{ $meta->{require}->{any} }, %{ $meta->{require}->{$phase} };
  111         396  
  111         430  
303             }
304              
305              
306             sub load_requires
307             {
308 177     177 1 3970 my($self, $phase, $eval) = @_;
309 177         851 my $reqs = $self->requires($phase);
310 177         716 foreach my $mod (keys %$reqs)
311             {
312 74         187 my $ver = $reqs->{$mod};
313             my $check = sub {
314 74     74   227 my $pm = "$mod.pm";
315 74         300 $pm =~ s{::}{/}g;
316 74         8433 require $pm;
317 74         394 };
318 74 100       264 if($eval)
319             {
320 12         16 eval { $check->() };
  12         24  
321 12 100 100     4783 die "Required $mod @{[ $ver || 'undef' ]}, missing" if $@;
  9         111  
322             }
323             else
324             {
325 62         169 $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 59 50 0     21021 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 57 100       301 next if $mod eq 'Alien::Build';
333 41 100       119 next if $mod eq 'Alien::Base';
334              
335 40 100       407 if($mod->can('bin_dir'))
336             {
337 2         6 push @{ $self->{bin_dir} }, $mod->bin_dir;
  2         23  
338             }
339              
340 40 100 66     703 if(($mod->can('runtime_prop') && $mod->runtime_prop)
      33        
      66        
341             || ($mod->isa('Alien::Base') && $mod->install_type('share')))
342             {
343 2         7 for my $dir (qw(lib share)) {
344 4         55 my $path = _path($mod->dist_dir)->child("$dir/pkgconfig");
345 4 100       257 if(-d $path)
346             {
347 2         48 push @{ $self->{pkg_config_path} }, $path->stringify;
  2         10  
348             }
349             }
350 2         42 my $path = _path($mod->dist_dir)->child('share/aclocal');
351 2 50       157 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 40 50 33     238 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 40 100       362 if($mod->can('alien_helper'))
371             {
372 2         9 my $helpers = $mod->alien_helper;
373 2         16 foreach my $name (sort keys %$helpers)
374             {
375 4         12 my $code = $helpers->{$name};
376 4         11 $self->meta->interpolator->replace_helper($name => $code);
377             }
378             }
379              
380             }
381 160         543 1;
382             }
383              
384             sub _call_hook
385             {
386 657     657   7530 my $self = shift;
387              
388 657         3832 local $ENV{PATH} = $ENV{PATH};
389 657         1329 unshift @PATH, @{ $self->{bin_dir} };
  657         3237  
390              
391 657         20518 local $ENV{PKG_CONFIG_PATH} = $ENV{PKG_CONFIG_PATH};
392 657         1627 unshift @PKG_CONFIG_PATH, @{ $self->{pkg_config_path} };
  657         2044  
393              
394 657         12294 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         1197 my @path;
  657         972  
398 657 100       1932 @path = split /:/, $ENV{ACLOCAL_PATH} if defined $ENV{ACLOCAL_PATH};
399 657         993 unshift @path, @{ $self->{aclocal_path} };
  657         1207  
400 657         2132 $ENV{ACLOCAL_PATH} = join ':', @path;
401             }
402              
403 657 100       2091 my $config = ref($_[0]) eq 'HASH' ? shift : {};
404 657         1557 my($name, @args) = @_;
405              
406 657         1805 local $self->{hook_prop} = {};
407              
408 657         1653 $self->meta->call_hook( $config, $name => $self, @args );
409             }
410              
411              
412             sub probe
413             {
414 145     145 1 50835 my($self) = @_;
415 145         674 local $CWD = $self->root;
416 145         7740 my $dir;
417              
418 145         1055 my $env = $self->_call_hook('override');
419 145         353 my $type;
420             my $error;
421              
422 145 100       605 $env = '' if $env eq 'default';
423              
424 145 100       375 if($env eq 'share')
425             {
426 3         24 $type = 'share';
427             }
428             else
429             {
430 142         218 $type = eval {
431             $self->_call_hook(
432             {
433             before => sub {
434 144     144   955 $dir = Alien::Build::TempDir->new($self, "probe");
435 144         45827 $CWD = "$dir";
436             },
437             after => sub {
438 144     144   420 $CWD = $self->root;
439             },
440             ok => 'system',
441             continue => sub {
442 124 100   124   824 if($_[0] eq 'system')
443             {
444 51         131 foreach my $name (qw( probe_class probe_instance_id ))
445             {
446 102 100 66     371 if(exists $self->hook_prop->{$name} && defined $self->hook_prop->{$name})
447             {
448 30         72 $self->install_prop->{"system_$name"} = $self->hook_prop->{$name};
449             }
450             }
451 51         218 return undef;
452             }
453             else
454             {
455 73         575 return 1;
456             }
457             },
458             },
459 142         1933 'probe',
460             );
461             };
462 142         2505 $error = $@;
463 142 100       461 $type = 'share' unless defined $type;
464             }
465              
466 145 100       383 if($error)
467             {
468 27 100       115 if($env eq 'system')
469             {
470 1         6 die $error;
471             }
472 26         224 $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         135 $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     453 if($env && $env ne $type)
479             {
480 1         11 die "requested $env install not available";
481             }
482              
483 143 50       1095 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     882 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         181 Carp::croak "network fetch is turned off";
493             }
494              
495 142         459 $self->runtime_prop->{install_type} = $type;
496              
497 142         598 $type;
498             }
499              
500              
501             sub download
502             {
503 58     58 1 12954 my($self) = @_;
504              
505 58 100       236 return $self unless $self->install_type eq 'share';
506 48 50       694 return $self if $self->install_prop->{complete}->{download};
507              
508 48 100       202 if($self->meta->has_hook('download'))
509             {
510 36         86 my $tmp;
511 36         156 local $CWD;
512 36         933 my $valid = 0;
513              
514             $self->_call_hook(
515             {
516             before => sub {
517 36     36   213 $tmp = Alien::Build::TempDir->new($self, "download");
518 36         11422 $CWD = "$tmp";
519             },
520             verify => sub {
521 35     35   138 my @list = grep { $_->basename !~ /^\./, } _path('.')->children;
  36         5140  
522              
523 35         1329 my $count = scalar @list;
524              
525 35 100       240 if($count == 0)
    100          
526             {
527 2         20 die "no files downloaded";
528             }
529             elsif($count == 1)
530             {
531 32         78 my($archive) = $list[0];
532 32 50       134 if(-d $archive)
533             {
534 0         0 $self->log("single dir, assuming directory");
535             }
536             else
537             {
538 32         914 $self->log("single file, assuming archive");
539             }
540 32         323 $self->install_prop->{download} = $archive->absolute->stringify;
541 32         120 $self->install_prop->{complete}->{download} = 1;
542 32         117 $valid = 1;
543             }
544             else
545             {
546 1         5 $self->log("multiple files, assuming directory");
547 1         5 $self->install_prop->{complete}->{download} = 1;
548 1         4 $self->install_prop->{download} = _path('.')->absolute->stringify;
549 1         6 $valid = 1;
550             }
551             },
552             after => sub {
553 36     36   138 $CWD = $self->root;
554             },
555             },
556 36         616 'download',
557             );
558              
559 33 50       532 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         43 return $self->_call_hook('download');
567             }
568              
569 0         0 die "download failed";
570             }
571              
572              
573             sub fetch
574             {
575 26     26 1 55695 my $self = shift;
576 26         129 $self->_call_hook( 'fetch' => @_ );
577             }
578              
579              
580             sub decode
581             {
582 5     5 1 32509 my($self, $res) = @_;
583 5         32 $self->_call_hook( decode => $res );
584             }
585              
586              
587             sub prefer
588             {
589 4     4 1 37 my($self, $res) = @_;
590 4         17 $self->_call_hook( prefer => $res );
591             }
592              
593              
594             sub extract
595             {
596 77     77 1 32730 my($self, $archive) = @_;
597              
598 77   66     482 $archive ||= $self->install_prop->{download};
599              
600 77 50       308 unless(defined $archive)
601             {
602 0         0 die "tried to call extract before download";
603             }
604              
605 77         220 my $nick_name = 'build';
606              
607 77 100       299 if($self->meta_prop->{out_of_source})
608             {
609 6         10 $nick_name = 'extract';
610 6         22 my $extract = $self->install_prop->{extract};
611 6 100 66     100 return $extract if defined $extract && -d $extract;
612             }
613              
614 74         176 my $tmp;
615 74         333 local $CWD;
616 74         2117 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   668 $tmp = Alien::Build::TempDir->new($self, $nick_name);
625 74         25320 $CWD = "$tmp";
626             },
627             verify => sub {
628              
629 74     74   348 my $path = '.';
630 74 100 100     887 if($self->meta_prop->{out_of_source} && $self->install_prop->{extract})
631             {
632 1         2 $path = $self->install_prop->{extract};
633             }
634              
635 74 50       497 my @list = grep { $_->basename !~ /^\./ && $_->basename ne 'pax_global_header' } _path($path)->children;
  112         17031  
636              
637 74         4146 my $count = scalar @list;
638              
639 74 100 100     969 if($count == 0)
    100          
640             {
641 1         12 die "no files extracted";
642             }
643             elsif($count == 1 && -d $list[0])
644             {
645 28         837 $ret = $list[0]->absolute->stringify;
646             }
647             else
648             {
649 45         285 $ret = "$tmp";
650             }
651              
652             },
653             after => sub {
654 74     74   508 $CWD = $self->root;
655             },
656              
657 74         1413 }, 'extract', $archive);
658              
659 73   66     1412 $self->install_prop->{extract} ||= $ret;
660 73 50       563 $ret ? $ret : ();
661             }
662              
663              
664             sub build
665             {
666 42     42 1 9880 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         3511 local %ENV = %ENV;
671              
672 42         273 my $stage = _path($self->install_prop->{stage});
673 42         1490 $stage->mkpath;
674              
675 42         5163 my $tmp;
676              
677 42 100       228 if($self->install_type eq 'share')
    50          
678             {
679 30         445 foreach my $suffix ('', '_ffi')
680             {
681 60         1129 local $CWD;
682 60 100       1704 delete $ENV{DESTDIR} unless $self->meta_prop->{destdir};
683              
684 60 100       156 my %env_meta = %{ $self->meta_prop ->{env} || {} };
  60         158  
685 60 100       159 my %env_inst = %{ $self->install_prop->{env} || {} };
  60         158  
686              
687 60 100       207 if($self->meta_prop->{env_interpolate})
688             {
689 2         12 foreach my $key (keys %env_meta)
690             {
691 2         8 $env_meta{$key} = $self->meta->interpolator->interpolate($env_meta{$key});
692             }
693             }
694              
695 60         3877 %ENV = (%ENV, %env_meta);
696 60         2860 %ENV = (%ENV, %env_inst);
697              
698 60         205 my $destdir;
699              
700             $self->_call_hook(
701             {
702             before => sub {
703 60 100   60   180 if($self->meta_prop->{out_of_source})
704             {
705 6         33 $self->extract;
706 6         448 $CWD = $tmp = Alien::Build::TempDir->new($self, 'build');
707             }
708             else
709             {
710 54         310 $CWD = $tmp = $self->extract;
711             }
712 60 100       10681 if($self->meta_prop->{destdir})
713             {
714 12         74 $destdir = Alien::Build::TempDir->new($self, 'destdir');
715 12         3909 $ENV{DESTDIR} = "$destdir";
716             }
717 60         493 $self->_call_hook({ all => 1 }, "patch${suffix}");
718             },
719             after => sub {
720 60 100   60   247 $destdir = "$destdir" if $destdir;
721             },
722 60         757 }, "build${suffix}");
723              
724 60   100     931 $self->install_prop->{"_ab_build@{[ $suffix || '_share' ]}"} = "$CWD";
  60         673  
725              
726 60   100     208 $self->_call_hook("gather@{[ $suffix || '_share' ]}");
  60         480  
727             }
728             }
729              
730             elsif($self->install_type eq 'system')
731             {
732 12         60 local $CWD = $self->root;
733 12         568 my $dir;
734              
735             $self->_call_hook(
736             {
737             before => sub {
738 12     12   78 $dir = Alien::Build::TempDir->new($self, "gather");
739 12         3826 $CWD = "$dir";
740             },
741             after => sub {
742 12     12   44 $CWD = $self->root;
743             },
744             },
745 12         193 'gather_system',
746             );
747              
748 12         111 $self->install_prop->{finished} = 1;
749 12         44 $self->install_prop->{complete}->{gather_system} = 1;
750             }
751              
752 42         3292 $self;
753             }
754              
755              
756             sub test
757             {
758 6     6 1 7347 my($self) = @_;
759              
760 6 100       33 if($self->install_type eq 'share')
761             {
762 4         17 foreach my $suffix ('_share', '_ffi')
763             {
764 7 100       51 if($self->meta->has_hook("test$suffix"))
765             {
766 4         21 my $dir = $self->install_prop->{"_ab_build$suffix"};
767 4 50 33     87 Carp::croak("no build directory to run tests") unless $dir && -d $dir;
768 4         31 local $CWD = $dir;
769 4         233 $self->_call_hook("test$suffix");
770             }
771             }
772             }
773             else
774             {
775 2 50       10 if($self->meta->has_hook("test_system"))
776             {
777 2         14 my $dir = Alien::Build::TempDir->new($self, "test");
778 2         668 local $CWD = "$dir";
779 2         130 $self->_call_hook("test_system");
780             }
781             }
782              
783             }
784              
785              
786             sub clean_install
787             {
788 3     3 1 10 my($self) = @_;
789 3 100       17 if($self->install_type eq 'share')
790             {
791 2         185 $self->_call_hook("clean_install");
792             }
793             }
794              
795              
796             sub system
797             {
798 2     2 1 4312 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         7  
804             } ($command, @args);
805              
806 2         16 $self->log("+ $command @args");
807              
808             scalar @args
809 2 100       24 ? system $command, @args
810             : system $command;
811             }
812              
813              
814             sub log
815             {
816 397     397 1 2814 my(undef, $message) = @_;
817 397         1690 my $caller = [caller];
818 397         987 chomp $message;
819 397         1476 foreach my $line (split /\n/, $message)
820             {
821 408         2443 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 37119 my($class) = @_;
835 8585 100       18951 $class = ref $class if ref $class;
836 8585   66     29491 $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   1384 my($class, %args) = @_;
847 339         3272 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         3014 $self;
860             }
861              
862              
863             sub prop
864             {
865 4823     4823   16708 shift->{prop};
866             }
867              
868             sub filename
869             {
870 549     549   1382 my($self, $new) = @_;
871 549 100       1473 $self->{filename} = $new if defined $new;
872 549         3374 $self->{filename};
873             }
874              
875              
876             sub add_requires
877             {
878 438     438   841 my $self = shift;
879 438         675 my $phase = shift;
880 438         1004 while(@_)
881             {
882 267         446 my $module = shift;
883 267         408 my $version = shift;
884 267         655 my $old = $self->{require}->{$phase}->{$module};
885 267 100 66     809 if((!defined $old) || $version > $old)
886 249         884 { $self->{require}->{$phase}->{$module} = $version }
887             }
888 438         896 $self;
889             }
890              
891              
892             sub interpolator
893             {
894 112     112   313 my($self, $new) = @_;
895 112 50       438 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         4408 require Alien::Build::Interpolate::Default;
913 49         457 $self->{intr} = Alien::Build::Interpolate::Default->new;
914             }
915 112         454 $self->{intr};
916             }
917              
918              
919             sub has_hook
920             {
921 79     79   503 my($self, $name) = @_;
922 79         437 defined $self->{hook}->{$name};
923             }
924              
925              
926             sub _instr
927             {
928 2939     2939   5305 my($self, $name, $instr) = @_;
929 2939 100       6774 if(ref($instr) eq 'CODE')
    50          
930             {
931 2883         8294 return $instr;
932             }
933             elsif(ref($instr) eq 'ARRAY')
934             {
935 56         731 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         3994 require Alien::Build::CommandSequence;
954 56         398 my $seq = Alien::Build::CommandSequence->new(@$instr);
955 56   100     337 $seq->apply_requirements($self, $phase{$name} || 'any');
956 56         317 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   29067 my($self, $name, $instr) = @_;
967 603         917 push @{ $self->{hook}->{$name} }, _instr $self, $name, $instr;
  603         2283  
968 603         1494 $self;
969             }
970              
971              
972             sub default_hook
973             {
974 2336     2336   5008 my($self, $name, $instr) = @_;
975 2336         4597 $self->{default_hook}->{$name} = _instr $self, $name, $instr;
976 2336         6095 $self;
977             }
978              
979              
980             sub around_hook
981             {
982 2316     2316   4413 my($self, $name, $code) = @_;
983 2316 100       5130 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   259 my $orig = shift;
989 108         709 $code->(sub { $old->($orig, @_) }, @_);
  108         367  
990 1317         7622 };
991             }
992             else
993             {
994 999         4187 $self->{around}->{$name} = $code;
995             }
996             }
997              
998             sub after_hook
999             {
1000 1310     1310   3299 my($self, $name, $code) = @_;
1001             $self->around_hook(
1002             $name => sub {
1003 86     86   181 my $orig = shift;
1004 86         275 my $ret = $orig->(@_);
1005 86         1178 $code->(@_);
1006 86         519 $ret;
1007             }
1008 1310         5064 );
1009             }
1010              
1011             sub before_hook
1012             {
1013 17     17   133 my($self, $name, $code) = @_;
1014             $self->around_hook(
1015             $name => sub {
1016 2     2   5 my $orig = shift;
1017 2         12 $code->(@_);
1018 2         14 my $ret = $orig->(@_);
1019 2         7 $ret;
1020             }
1021 17         94 );
1022             }
1023              
1024              
1025             sub call_hook
1026             {
1027 657     657   1062 my $self = shift;
1028 657 50       1592 my %args = ref $_[0] ? %{ shift() } : ();
  657         2342  
1029 657         1674 my($name, @args) = @_;
1030 657         1008 my $error;
1031              
1032 657 100       945 my @hooks = @{ $self->{hook}->{$name} || []};
  657         2988  
1033              
1034 657 100       1805 if(@hooks == 0)
1035             {
1036 301 100       940 if(defined $self->{default_hook}->{$name})
    100          
1037             {
1038 234         637 @hooks = ($self->{default_hook}->{$name})
1039             }
1040             elsif(!$args{all})
1041             {
1042 11         1829 Carp::croak "No hooks registered for $name";
1043             }
1044             }
1045              
1046 646         1031 my $value;
1047              
1048 646         1383 foreach my $hook (@hooks)
1049             {
1050 605 50       984 if(eval { $args[0]->isa('Alien::Build') })
  605         3726  
1051             {
1052 605         1112 %{ $args[0]->{hook_prop} } = (
  605         1947  
1053             name => $name,
1054             );
1055             }
1056              
1057 605   100 518   4294 my $wrapper = $self->{around}->{$name} || sub { my $code = shift; $code->(@_) };
  518         923  
  518         1125  
1058 605         1047 my $value;
1059 605 100       1797 $args{before}->() if $args{before};
1060 605 100       10443 if(ref($hook) eq 'CODE')
1061             {
1062 594         1007 $value = eval {
1063 594     589   3086 my $value = $wrapper->(sub { $hook->(@_) }, @args);
  589         1982  
1064 567 100       165136 $args{verify}->('code') if $args{verify};
1065 565         4292 $value;
1066             };
1067             }
1068             else
1069             {
1070             $value = $wrapper->(sub {
1071 11     11   33 eval {
1072 11         68 $hook->execute(@_);
1073 8 100       121 $args{verify}->('command') if $args{verify};
1074             };
1075 11 100       275 defined $args{ok} ? $args{ok} : 1;
1076 11         73 }, @args);
1077             }
1078 605         2273 $error = $@;
1079 605 100       1966 $args{after}->() if $args{after};
1080 605 100       8168 if($args{all})
1081             {
1082 5 50       26 die if $error;
1083             }
1084             else
1085             {
1086 600 100       2876 next if $error;
1087 567 100 100     1804 next if $args{continue} && $args{continue}->($value);
1088 494         8471 return $value;
1089             }
1090             }
1091              
1092 152 100 66     1149 die $error if $error && ! $args{all};
1093              
1094 124         1301 $value;
1095             }
1096              
1097              
1098             sub apply_plugin
1099             {
1100 2821     2821   6120 my($self, $name, @args) = @_;
1101              
1102 2821         6307 my $class;
1103             my $pm;
1104 2821         0 my $found;
1105              
1106 2821 100       7222 if($name =~ /^=(.*)$/)
1107             {
1108 1         4 $class = $1;
1109 1         3 $pm = "$class.pm";
1110 1         5 $pm =~ s!::!/!g;
1111 1         2 $found = 1;
1112             }
1113              
1114 2821 50 66     8428 if($name !~ /::/ && !$found)
1115             {
1116 26         97 foreach my $inc (@INC)
1117             {
1118             # TODO: allow negotiators to work with @INC hooks
1119 100 50       1636 next if ref $inc;
1120 100         536 my $file = Path::Tiny->new("$inc/Alien/Build/Plugin/$name/Negotiate.pm");
1121 100 100       3264 if(-r $file)
1122             {
1123 22         547 $class = "Alien::Build::Plugin::${name}::Negotiate";
1124 22         68 $pm = "Alien/Build/Plugin/$name/Negotiate.pm";
1125 22         45 $found = 1;
1126 22         82 last;
1127             }
1128             }
1129             }
1130              
1131 2821 100       6016 unless($found)
1132             {
1133 2798         5287 $class = "Alien::Build::Plugin::$name";
1134 2798         5168 $pm = "Alien/Build/Plugin/$name.pm";
1135 2798         9379 $pm =~ s{::}{/}g;
1136             }
1137              
1138 2821 100       209950 require $pm unless $class->can('new');
1139 2821         11902 my $plugin = $class->new(@args);
1140 2821         11377 $plugin->init($self);
1141 2821         11099 $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   642 use overload '""' => sub { shift->as_string }, bool => sub { 1 }, fallback => 1;
  50     161   130  
  50         756  
  354         4598  
  24         74  
1153 50     50   7821 use File::Temp qw( tempdir );
  50         41108  
  50         15325  
1154              
1155             sub new
1156             {
1157 293     293   1549 my($class, $build, $name) = @_;
1158 293         758 my $root = $build->install_prop->{root};
1159 293 50       5263 Path::Tiny->new($root)->mkpath unless -d $root;
1160 293         2182 bless {
1161             dir => Path::Tiny->new(tempdir( "${name}_XXXX", DIR => $root)),
1162             }, $class;
1163             }
1164              
1165             sub as_string
1166             {
1167 354     354   1281 shift->{dir}->stringify;
1168             }
1169              
1170             sub DESTROY
1171             {
1172 293     293   8238 my($self) = @_;
1173 293 100 66     2000 if(-d $self->{dir} && $self->{dir}->children == 0)
1174             {
1175 169 50       20643 rmdir($self->{dir}) || warn "unable to remove @{[ $self->{dir} ]} $!";
  0            
1176             }
1177             }
1178              
1179             1;
1180              
1181             __END__