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