File Coverage

blib/lib/Alien/Base.pm
Criterion Covered Total %
statement 167 216 77.3
branch 46 88 52.2
condition 10 21 47.6
subroutine 30 34 88.2
pod 11 18 61.1
total 264 377 70.0


line stmt bran cond sub pod time code
1             package Alien::Base;
2              
3 2     2   12 use strict;
  2         3  
  2         60  
4 2     2   8 use warnings;
  2         4  
  2         89  
5              
6             our $VERSION = '0.041';
7              
8 2     2   8 use Carp;
  2         8  
  2         128  
9 2     2   1038 use File::ShareDir ();
  2         11650  
  2         51  
10 2     2   16 use File::Spec;
  2         3  
  2         53  
11 2     2   12 use Scalar::Util qw/blessed/;
  2         3  
  2         145  
12 2     2   11 use Capture::Tiny 0.17 qw/capture_merged/;
  2         51  
  2         103  
13 2     2   815 use Text::ParseWords qw/shellwords/;
  2         1516  
  2         182  
14              
15             =encoding UTF-8
16              
17             =head1 NAME
18              
19             Alien::Base - Base classes for Alien:: modules
20              
21             =head1 SYNOPSIS
22              
23             package Alien::MyLibrary;
24              
25             use strict;
26             use warnings;
27              
28             use parent 'Alien::Base';
29              
30             1;
31              
32             (for details on the C or C and L
33             that should be bundled with your L subclass, please see
34             L).
35              
36             Then a C can use C in its C:
37              
38             use Alien::MyLibrary;
39             use Module::Build 0.28; # need at least 0.28
40            
41             my $builder = Module::Build->new(
42             ...
43             extra_compiler_flags => Alien::MyLibrary->cflags,
44             extra_linker_flags => Alien::MyLibrary->libs,
45             ...
46             );
47            
48             $builder->create_build_script;
49              
50             Or if you prefer L, in its C:
51              
52             use Alien::MyLibrary
53             use ExtUtils::MakeMaker;
54             use Config;
55            
56             WriteMakefile(
57             ...
58             CCFLAGS => Alien::MyLibrary->cflags . " $Config{ccflags}",
59             LIBS => ALien::MyLibrary->libs,
60             ...
61             );
62              
63             Or if you are using L:
64              
65             use ExtUtils::MakeMaker;
66             use ExtUtils::Depends;
67             my $eud = ExtUtils::Depends->new(qw( MyLibrary::XS Alien::MyLibrary ));
68             WriteMakefile(
69             ...
70             $eud->get_makefile_vars
71             );
72              
73             In your C module, you may need to use L if
74             dynamic libraries are used:
75              
76             package MyLibrary::XS;
77            
78             use Alien::MyLibrary;
79            
80             ...
81              
82             Or you can use it from an FFI module:
83              
84             package MyLibrary::FFI;
85            
86             use Alien::MyLibrary;
87             use FFI::Platypus;
88            
89             my $ffi = FFI::Platypus->new;
90             $ffi->lib(Alien::MyLibrary->dynamic_libs);
91            
92             $ffi->attach( 'my_library_function' => [] => 'void' );
93              
94             You can even use it with L (C and C++ languages are supported):
95              
96             package MyLibrary::Inline;
97            
98             use Alien::MyLibrary;
99             # Inline 0.56 or better is required
100             use Inline 0.56 with => 'Alien::MyLibrary';
101             ...
102              
103             =head1 DESCRIPTION
104              
105             B: L is no longer bundled with L and has been spun off into a separate distribution.
106             L will be a prerequisite for L until October 1, 2017. If you are using L
107             you need to make sure it is declared as a C in your C. You may want to also consider using L and
108             L as a more modern alternative.
109              
110             L comprises base classes to help in the construction of C modules. Modules in the L namespace are used to locate and install (if necessary) external libraries needed by other Perl modules.
111              
112             This is the documentation for the L module itself. To learn more about the system as a whole please see L.
113              
114             =cut
115              
116             sub import {
117 0     0   0 my $class = shift;
118              
119 0 0       0 return if $class->runtime_prop;
120              
121 0 0       0 return if $class->install_type('system');
122              
123 0         0 require DynaLoader;
124              
125             # Sanity check in order to ensure that dist_dir can be found.
126             # This will throw an exception otherwise.
127 0         0 $class->dist_dir;
128              
129             # get a reference to %Alien::MyLibrary::AlienLoaded
130             # which contains names of already loaded libraries
131             # this logic may be replaced by investigating the DynaLoader arrays
132 0         0 my $loaded = do {
133 2     2   8 no strict 'refs';
  2         7  
  2         47  
134 2     2   7 no warnings 'once';
  2         3  
  2         3633  
135 0         0 \%{ $class . "::AlienLoaded" };
  0         0  
136             };
137              
138 0         0 my @libs = $class->split_flags( $class->libs );
139              
140 0         0 my @L = grep { s/^-L// } @libs;
  0         0  
141 0         0 my @l = grep { /^-l/ } @libs;
  0         0  
142              
143 0         0 unshift @DynaLoader::dl_library_path, @L;
144              
145 0         0 my @libpaths;
146 0         0 foreach my $l (@l) {
147 0 0       0 next if $loaded->{$l};
148              
149 0         0 my $path = DynaLoader::dl_findfile( $l );
150 0 0       0 unless ($path) {
151 0         0 carp "Could not resolve $l";
152 0         0 next;
153             }
154              
155 0         0 push @libpaths, $path;
156 0         0 $loaded->{$l} = $path;
157             }
158              
159 0         0 push @DynaLoader::dl_resolve_using, @libpaths;
160              
161 0         0 my @librefs = map { DynaLoader::dl_load_file( $_, 0x01 ) } grep !/\.(a|lib)$/, @libpaths;
  0         0  
162 0         0 push @DynaLoader::dl_librefs, @librefs;
163              
164             }
165              
166             =head1 METHODS
167              
168             In the example snippets here, C represents any
169             subclass of L.
170              
171             =head2 dist_dir
172              
173             my $dir = Alien::MyLibrary->dist_dir;
174              
175             Returns the directory that contains the install root for
176             the packaged software, if it was built from install (i.e., if
177             C is C).
178              
179             =cut
180              
181             sub dist_dir {
182 14     14 1 26 my $class = shift;
183              
184 14   33     55 my $dist = blessed $class || $class;
185 14         38 $dist =~ s/::/-/g;
186              
187 14 50       26 my $dist_dir =
188             $class->config('finished_installing')
189             ? File::ShareDir::dist_dir($dist)
190             : $class->config('working_directory');
191              
192 14 50 33     653 croak "Failed to find share dir for dist '$dist'"
193             unless defined $dist_dir && -d $dist_dir;
194              
195 14         206 return $dist_dir;
196             }
197              
198 1     1 0 10 sub new { return bless {}, $_[0] }
199              
200             sub _flags
201             {
202 12     12   19 my($class, $key) = @_;
203            
204 12         17 my $config = $class->runtime_prop;
205 12         18 my $flags = $config->{$key};
206              
207 12         8 my $prefix = $config->{prefix};
208 12 50       49 $prefix =~ s{\\}{/}g if $^O =~ /^(MSWin32|msys)$/;
209 12         14 my $distdir = $config->{distdir};
210 12 50       26 $distdir =~ s{\\}{/}g if $^O =~ /^(MSWin32|msys)$/;
211            
212 12 50       16 if($prefix ne $distdir)
213             {
214             $flags = join ' ', map {
215 12         20 s/^(-I|-L|-LIBPATH:)?\Q$prefix\E/$1$distdir/;
  29         943  
216 29         31 s/(\s)/\\$1/g;
217 29         50 $_;
218             } $class->split_flags($flags);
219             }
220            
221 12         102 $flags;
222             }
223              
224             =head2 cflags
225              
226             my $cflags = Alien::MyLibrary->cflags;
227              
228             use Text::ParseWords qw( shellwords );
229             my @cflags = shellwords( Alien::MyLibrary->cflags );
230              
231             Returns the C compiler flags necessary to compile an XS
232             module using the alien software. If you need this in list
233             form (for example if you are calling system with a list
234             argument) you can pass this value into C from
235             the Perl core L module.
236              
237             =cut
238              
239             sub cflags {
240 7     7 1 2533 my $class = shift;
241 7 100       22 return $class->runtime_prop ? $class->_flags('cflags') : $class->_pkgconfig_keyword('Cflags');
242             }
243              
244             sub cflags_static {
245 3     3 0 790 my $class = shift;
246 3 50       6 return $class->runtime_prop ? $class->_flags('cflags_static') : $class->_pkgconfig_keyword('Cflags', 'static');
247             }
248              
249             =head2 libs
250              
251             my $libs = Alien::MyLibrary->libs;
252              
253             use Text::ParseWords qw( shellwords );
254             my @cflags = shellwords( Alien::MyLibrary->libs );
255              
256             Returns the library linker flags necessary to link an XS
257             module against the alien software. If you need this in list
258             form (for example if you are calling system with a list
259             argument) you can pass this value into C from
260             the Perl core L module.
261              
262             =cut
263              
264             sub libs {
265 7     7 1 1160 my $class = shift;
266 7 100       16 return $class->runtime_prop ? $class->_flags('libs') : $class->_pkgconfig_keyword('Libs');
267             }
268              
269             sub libs_static {
270 3     3 0 903 my $class = shift;
271 3 50       8 return $class->runtime_prop ? $class->_flags('libs_static') : $class->_pkgconfig_keyword('Libs', 'static');
272             }
273              
274             =head2 version
275              
276             my $version = Alien::MyLibrary->version;
277              
278             Returns the version of the Alienized library or tool that was
279             determined at install time.
280              
281             =cut
282              
283             sub version {
284 4     4 1 477 my $self = shift;
285 4         11 my $version = $self->config('version');
286 4         9 chomp $version;
287 4         13 return $version;
288             }
289              
290             =head2 install_type
291              
292             my $install_type = Alien::MyLibrary->install_type;
293              
294             Returns the install type that was used when C was
295             installed. Types include:
296              
297             =over 4
298              
299             =item system
300              
301             The library was provided by the operating system
302              
303             =item share
304              
305             The library was not available when C was installed, so
306             it was built from source code, either downloaded from the Internet
307             or bundled with C.
308              
309             =back
310              
311             =cut
312              
313             sub install_type {
314 19     19 1 724 my $self = shift;
315 19         43 my $type = $self->config('install_type');
316 19 100       77 return @_ ? $type eq $_[0] : $type;
317             }
318              
319             sub _pkgconfig_keyword {
320 8     8   12 my $self = shift;
321 8         27 my $keyword = shift;
322 8         13 my $static = shift;
323              
324             # use pkg-config if installed system-wide
325 8 100       27 if ($self->install_type('system')) {
326 6         15 my $name = $self->config('name');
327 6         21 require Alien::Base::PkgConfig;
328 6 50       78 my $command = Alien::Base::PkgConfig->pkg_config_command . " @{[ $static ? '--static' : '' ]} --\L$keyword\E $name";
  6         110675  
329              
330 6         14 $! = 0;
331 6     6   171 chomp ( my $pcdata = capture_merged { system( $command ) } );
  6         782148  
332              
333             # if pkg-config fails for whatever reason, then we try to
334             # fallback on alien_provides_*
335 6 50 33     7111 $pcdata = '' if $! || $?;
336              
337 6         81 $pcdata =~ s/\s*$//;
338              
339 6 50       63 if($self->config('system_provides')) {
340 6 50       18 if(my $system_provides = $self->config('system_provides')->{$keyword}) {
341 0 0       0 $pcdata = length $pcdata ? "$pcdata $system_provides" : $system_provides;
342             }
343             }
344              
345 6         96 return $pcdata;
346             }
347              
348             # use parsed info from build .pc file
349 2         11 my $dist_dir = $self->dist_dir;
350 2         8 my @pc = $self->_pkgconfig(@_);
351             my @strings =
352             grep defined,
353 2         4 map { $_->keyword($keyword,
  2         9  
354             #{ pcfiledir => $dist_dir }
355             ) }
356             @pc;
357              
358 2 50 33     82 if(defined $self->config('original_prefix') && $self->config('original_prefix') ne $self->dist_dir)
359             {
360 2         3 my $dist_dir = $self->dist_dir;
361 2 50       8 $dist_dir =~ s{\\}{/}g if $^O eq 'MSWin32';
362 2         4 my $old = quotemeta $self->config('original_prefix');
363             @strings = map {
364 3         327 s{^(-I|-L|-LIBPATH:)?($old)}{$1.$dist_dir}e;
  2         6  
365 3         7 s/(\s)/\\$1/g;
366 3         6 $_;
367 2         3 } map { $self->split_flags($_) } @strings;
  2         16  
368             }
369              
370 2         9 return join( ' ', @strings );
371             }
372              
373             sub _pkgconfig {
374 2     2   3 my $self = shift;
375 2         2 my %all = %{ $self->config('pkgconfig') };
  2         6  
376              
377             # merge in found pc files
378 2         7 require File::Find;
379             my $wanted = sub {
380 14 50 66 14   545 return if ( -d or not /\.pc$/ );
381 0         0 require Alien::Base::PkgConfig;
382 0         0 my $pkg = Alien::Base::PkgConfig->new($_);
383 0         0 $all{$pkg->{package}} = $pkg;
384 2         7 };
385 2         5 File::Find::find( $wanted, $self->dist_dir );
386            
387 2 50       8 croak "No Alien::Base::PkgConfig objects are stored!"
388             unless keys %all;
389            
390             # Run through all pkgconfig objects and ensure that their modules are loaded:
391 2         5 for my $pkg_obj (values %all) {
392 4         13 my $perl_module_name = blessed $pkg_obj;
393 4         143 eval "require $perl_module_name";
394             }
395              
396 2 50       4 return @all{@_} if @_;
397              
398 2         3 my $manual = delete $all{_manual};
399              
400 2 50       7 if (keys %all) {
401 2         13 return values %all;
402             } else {
403 0         0 return $manual;
404             }
405             }
406              
407             =head2 config
408              
409             my $value = Alien::MyLibrary->config($key);
410              
411             Returns the configuration data as determined during the install
412             of L. For the appropriate config keys, see
413             L.
414              
415             This is not typically used by L and L,
416             but a compatible interface will be provided.
417              
418             =cut
419              
420             # helper method to call Alien::MyLib::ConfigData->config(@_)
421             sub config {
422 69     69 1 91 my $class = shift;
423 69   66     270 $class = blessed $class || $class;
424              
425 69 100       147 if(my $ab_config = $class->runtime_prop)
426             {
427 25         27 my $key = shift;
428 25         69 return $ab_config->{legacy}->{$key};
429             }
430              
431 44         76 my $config = $class . '::ConfigData';
432 44         2138 eval "require $config";
433 44 50       122 warn $@ if $@;
434              
435 44         135 return $config->config(@_);
436             }
437              
438             # helper method to split flags based on the OS
439             sub split_flags {
440 14     14 0 15 my ($class, $line) = @_;
441 14 50       23 if( $^O eq 'MSWin32' ) {
442 0         0 $class->split_flags_windows($line);
443             } else {
444             # $os eq 'Unix'
445 14         29 $class->split_flags_unix($line);
446             }
447             }
448              
449             sub split_flags_unix {
450 15     15 0 4036 my ($class, $line) = @_;
451 15         30 shellwords($line);
452             }
453              
454             sub split_flags_windows {
455             # NOTE a better approach would be to write a function that understands cmd.exe metacharacters.
456 3     3 0 1707 my ($class, $line) = @_;
457              
458             # Double the backslashes so that when they are unescaped by shellwords(),
459             # they become a single backslash. This should be fine on Windows since
460             # backslashes are not used to escape metacharacters in cmd.exe.
461 3         8 $line =~ s,\\,\\\\,g;
462 3         7 shellwords($line);
463             }
464              
465             =head2 dynamic_libs
466              
467             my @dlls = Alien::MyLibrary->dynamic_libs;
468             my($dll) = Alien::MyLibrary->dynamic_libs;
469              
470             Returns a list of the dynamic library or shared object files for the
471             alien software.
472              
473             =cut
474              
475             sub dynamic_libs {
476 2     2 1 359 my ($class) = @_;
477            
478 2         15 require FFI::CheckLib;
479            
480 2 100       5 if($class->install_type('system')) {
481              
482 1         3 my $name = $class->config('ffi_name');
483 1 50       3 unless(defined $name) {
484 1         2 $name = $class->config('name');
485             # strip leading lib from things like libarchive or libffi
486 1         2 $name =~ s/^lib//;
487             # strip trailing version numbers
488 1         2 $name =~ s/-[0-9\.]+$//;
489             }
490            
491 1         5 return FFI::CheckLib::find_lib(lib => $name);
492            
493             } else {
494            
495 1         3 my $dir = $class->dist_dir;
496 1         3 my $dynamic = File::Spec->catfile($class->dist_dir, 'dynamic');
497            
498 1 50       12 if(-d $dynamic)
499             {
500 1         10 return FFI::CheckLib::find_lib(
501             lib => '*',
502             libpath => $dynamic,
503             systempath => [],
504             );
505             }
506              
507 0         0 return FFI::CheckLib::find_lib(
508             lib => '*',
509             libpath => $dir,
510             systempath => [],
511             recursive => 1,
512             );
513             }
514             }
515              
516             =head2 bin_dir
517              
518             my(@dir) = Alien::MyLibrary->bin_dir
519              
520             Returns a list of directories with executables in them. For a C
521             install this will be an empty list. For a C install this will be
522             a directory under C named C if it exists. You may wish
523             to override the default behavior if you have executables or scripts that
524             get installed into non-standard locations.
525              
526             Example usage:
527              
528             use Env qw( @PATH );
529            
530             unshft @PATH, Alien::MyLibrary->bin_dir;
531              
532             =cut
533              
534             sub bin_dir {
535 3     3 1 1417 my ($class) = @_;
536 3 100       7 if($class->install_type('system'))
537             {
538 1         3 my $prop = $class->runtime_prop;
539 1 50       4 return unless defined $prop;
540 1 50       6 return unless defined $prop->{system_bin_dir};
541 0 0       0 return ref $prop->{system_bin_dir} ? @{ $prop->{system_bin_dir} } : ($prop->{system_bin_dir});
  0         0  
542             }
543             else
544             {
545 2         4 my $dir = File::Spec->catfile($class->dist_dir, 'bin');
546 2 50       40 return -d $dir ? ($dir) : ();
547             }
548             }
549              
550             =head2 alien_helper
551              
552             my $helpers = Alien::MyLibrary->alien_helper;
553              
554             Returns a hash reference of helpers provided by the Alien module.
555             The keys are helper names and the values are code references. The
556             code references will be executed at command time and the return value
557             will be interpolated into the command before execution. The default
558             implementation returns an empty hash reference, and you are expected
559             to override the method to create your own helpers.
560              
561             For use with commands specified in and L or in your C
562             when used with L.
563              
564             Helpers allow users of your Alien module to use platform or environment
565             determined logic to compute command names or arguments in your installer
566             logic. Helpers allow you to do this without making your Alien module a
567             requirement when a build from source code is not necessary.
568              
569             As a concrete example, consider L, which provides the
570             helper C:
571              
572             package Alien::gmake;
573            
574             ...
575            
576             sub alien_helper {
577             my($class) = @_;
578             return {
579             gmake => sub {
580             # return the executable name for GNU make,
581             # usually either make or gmake depending on
582             # the platform and environment
583             $class->exe;
584             }
585             },
586             }
587              
588             Now consider L. C requires GNU Make to build from
589             source code, but if the system C package is installed we don't
590             need it. From the L of C:
591              
592             use alienfile;
593            
594             plugin 'Probe::CommandLine' => (
595             command => 'nasm',
596             args => ['-v'],
597             match => qr/NASM version/,
598             );
599            
600             share {
601             ...
602             plugin 'Extract' => 'tar.gz';
603             plugin 'Build::MSYS' => ();
604            
605             build [
606             'sh configure --prefix=%{alien.install.prefix}',
607             '%{gmake}',
608             '%{gmake} install',
609             ];
610             };
611            
612             ...
613              
614             =cut
615              
616             sub alien_helper {
617 0     0 1 0 {};
618             }
619              
620             =head2 inline_auto_include
621              
622             my(@headers) = Alien::MyLibrary->inline_auto_include;
623              
624             List of header files to automatically include in inline C and C++
625             code when using L or L. This is provided
626             as a public interface primarily so that it can be overidden at run
627             time. This can also be specified in your C with
628             L using the C
629             property.
630              
631             =cut
632              
633             sub inline_auto_include {
634 0     0 1 0 my ($class) = @_;
635 0 0       0 return [] unless $class->config('inline_auto_include');
636 0         0 $class->config('inline_auto_include')
637             }
638              
639             sub Inline {
640 0     0 0 0 my ($class, $language) = @_;
641 0 0       0 return if $language !~ /^(C|CPP)$/;
642 0         0 my $config = {
643             CCFLAGSEX => $class->cflags,
644             LIBS => $class->libs,
645             };
646            
647 0 0       0 if (@{ $class->inline_auto_include } > 0) {
  0         0  
648 0         0 $config->{AUTO_INCLUDE} = join "\n", map { "#include \"$_\"" } @{ $class->inline_auto_include };
  0         0  
  0         0  
649             }
650            
651 0         0 $config;
652             }
653              
654             =head2 runtime_prop
655              
656             my $hashref = Alien::MyLibrary->runtime_prop;
657              
658             Returns a hash reference of the runtime properties computed by L during its
659             install process. If the L based L was not built using L,
660             then this will return undef.
661              
662             =cut
663              
664             {
665             my %alien_build_config_cache;
666              
667             sub runtime_prop
668             {
669 104     104 1 282 my($class) = @_;
670            
671             return $alien_build_config_cache{$class} if
672 104 100       376 exists $alien_build_config_cache{$class};
673            
674 6   66     25 $alien_build_config_cache{$class} ||= do {
675 6 100       14 my $dist = ref $class ? ref $class : $class;
676 6         17 $dist =~ s/::/-/g;
677 6         8 my $dist_dir = eval { File::ShareDir::dist_dir($dist) };
  6         20  
678 6 100       859 return if $@;
679 5         36 my $alien_json = File::Spec->catfile($dist_dir, '_alien', 'alien.json');
680 5 100       87 return unless -r $alien_json;
681 2         47 open my $fh, '<', $alien_json;
682 2         2 my $json = do { local $/; <$fh> };
  2         6  
  2         25  
683 2         9 close $fh;
684 2         8 require JSON::PP;
685 2         13 my $config = JSON::PP::decode_json($json);
686 2         4727 $config->{distdir} = $dist_dir;
687 2         15 $config;
688             };
689             }
690             }
691              
692             1;
693              
694             __END__