File Coverage

blib/lib/Module/Build/WithXSpp.pm
Criterion Covered Total %
statement 12 221 5.4
branch 0 66 0.0
condition 0 58 0.0
subroutine 4 21 19.0
pod 0 11 0.0
total 16 377 4.2


line stmt bran cond sub pod time code
1             package Module::Build::WithXSpp;
2 1     1   1034 use strict;
  1         3  
  1         40  
3 1     1   7 use warnings;
  1         2  
  1         46  
4              
5 1     1   937 use Module::Build;
  1         138034  
  1         34  
6 1     1   880 use ExtUtils::CppGuess ();
  1         23362  
  1         3595  
7              
8             our @ISA = qw(Module::Build);
9             our $VERSION = '0.14';
10              
11             # TODO
12             # - configurable set of xsp and xspt files (and XS typemaps?)
13             # => works via directories for now.
14             # - configurable includes/C-preamble for the XS?
15             # => Works in the .xsp files, but the order of XS++ inclusion
16             # is undefined.
17             # - configurable C++ source folder(s) (works, needs docs)
18             # => to be documented another time. This is really not a feature that
19             # should be commonly used.
20              
21             sub new {
22 0     0 0   my $proto = shift;
23 0   0       my $class = ref($proto) || $proto;
24 0           my %args = @_;
25              
26             # This gives us the correct settings for the C++ compile (hopefully)
27 0           my $guess = ExtUtils::CppGuess->new();
28 0 0         if (defined $args{extra_compiler_flags}) {
29 0 0         if (ref($args{extra_compiler_flags})) {
30 0           $guess->add_extra_compiler_flags($_) for @{$args{extra_compiler_flags}};
  0            
31             }
32             else {
33 0           $guess->add_extra_compiler_flags($args{extra_compiler_flags})
34             }
35 0           delete $args{extra_compiler_flags};
36             }
37              
38 0 0         if (defined $args{extra_linker_flags}) {
39 0 0         if (ref($args{extra_linker_flags})) {
40 0           $guess->add_extra_linker_flags($_) for @{$args{extra_linker_flags}};
  0            
41             }
42             else {
43 0           $guess->add_extra_linker_flags($args{extra_linker_flags})
44             }
45 0           delete $args{extra_linker_flags};
46             }
47              
48             # add the typemap modules to the build dependencies
49 0   0       my $build_requires = $args{build_requires}||{};
50 0   0       my $extra_typemap_modules = $args{extra_typemap_modules}||{};
51             # FIXME: This prevents any potential subclasses from fudging with the extra typemaps?
52 0           foreach my $module (keys %$extra_typemap_modules) {
53 0 0 0       if (not defined $build_requires->{$module}
      0        
54             or defined($extra_typemap_modules->{$module})
55             && $build_requires->{$module} < $extra_typemap_modules->{$module})
56             {
57 0           $build_requires->{$module} = $extra_typemap_modules->{$module};
58             }
59             }
60 0           $args{build_requires} = $build_requires;
61              
62             # Construct object using C++ options guess
63 0           my $self = $class->SUPER::new(
64             %args,
65             $guess->module_build_options # FIXME find a way to let the user override this
66             );
67              
68 0 0         push @{$self->extra_compiler_flags},
  0            
69             map "-I$_",
70 0           (@{$self->cpp_source_dirs||[]}, $self->build_dir);
71              
72 0           $self->_init(\%args);
73              
74 0           return $self;
75             }
76              
77             sub _init {
78 0     0     my $self = shift;
79 0           my $args = shift;
80             }
81              
82             sub auto_require {
83 0     0 0   my ($self) = @_;
84 0           my $p = $self->{properties};
85              
86 0 0 0       if ($self->dist_name ne 'Module-Build-WithXSpp'
87             and $self->auto_configure_requires)
88             {
89 0 0         if (not exists $p->{configure_requires}{'Module::Build::WithXSpp'}) {
90 0           (my $ver = $VERSION) =~ s/^(\d+\.\d\d).*$/$1/; # last major release only
91 0           $self->_add_prereq('configure_requires', 'Module::Build::WithXSpp', $ver);
92             }
93 0 0         if (not exists $p->{configure_requires}{'ExtUtils::CppGuess'}) {
94 0           (my $ver = $ExtUtils::CppGuess::VERSION) =~ s/^(\d+\.\d\d).*$/$1/; # last major release only
95 0           $self->_add_prereq('configure_requires', 'ExtUtils::CppGuess', $ver);
96             }
97 0 0 0       if (not exists $p->{build_requires}{'ExtUtils::CppGuess'}
      0        
98             and eval("require ExtUtils::XSpp;")
99             and defined $ExtUtils::XSpp::VERSION)
100             {
101 0           (my $ver = $ExtUtils::XSpp::VERSION) =~ s/^(\d+\.\d\d).*$/$1/; # last major release only
102 0           $self->_add_prereq('build_requires', 'ExtUtils::XSpp', $ver);
103             }
104             }
105              
106 0           $self->SUPER::auto_require();
107              
108 0           return;
109             }
110              
111             sub ACTION_create_buildarea {
112 0     0 0   my $self = shift;
113 0           mkdir($self->build_dir);
114 0           $self->add_to_cleanup($self->build_dir);
115             }
116              
117             sub ACTION_code {
118 0     0 0   my $self = shift;
119 0           $self->depends_on('create_buildarea');
120 0           $self->depends_on('generate_typemap');
121 0           $self->depends_on('generate_main_xs');
122              
123 0           my $files = {};
124 0           foreach my $file (@{$self->cpp_source_files}) {
  0            
125 0           $files->{$file} = undef;
126             }
127              
128 0           foreach my $ext (qw(c cc cxx cpp C)) {
129 0 0         foreach my $dir (@{$self->cpp_source_dirs||[]}) {
  0            
130 0           my $this = $self->_find_file_by_type($ext, $dir);
131 0           $files = $self->_merge_hashes($files, $this);
132             }
133             }
134              
135 0           my @objects;
136 0           foreach my $file (keys %$files) {
137 0           my $obj = $self->compile_c($file);
138 0           push @objects, $obj;
139 0           $self->add_to_cleanup($obj);
140             }
141              
142 0   0       $self->{properties}{objects} ||= [];
143 0           push @{$self->{properties}{objects}}, @objects;
  0            
144              
145 0           return $self->SUPER::ACTION_code(@_);
146             }
147              
148             # I guess I should use a module here.
149             sub _naive_shell_escape {
150 0     0     my $s = shift;
151 0           $s =~ s/\\/\\\\/g;
152 0           $s =~ s/"/\\"/g;
153 0           $s
154             }
155              
156             sub ACTION_generate_main_xs {
157 0     0 0   my $self = shift;
158              
159 0           my $xs_files = $self->find_xs_files;
160 0           my $main_xs_file = File::Spec->catfile($self->build_dir, 'main.xs');
161              
162 0 0         if (keys(%$xs_files) > 1) {
163             # user knows what she's doing, do not generate XS
164 0           $self->log_info("Found custom XS files. Not auto-generating main XS file...\n");
165 0           return 1;
166             }
167              
168 0           my $xsp_files = $self->find_xsp_files;
169 0           my $xspt_files = $self->find_xsp_typemaps;
170              
171 0           my $newest = $self->_calc_newest(
172             keys(%$xsp_files),
173             keys(%$xspt_files),
174             'Build.PL',
175             # Commented out: Do not include generated typemap in -M check
176             # because -M granularity causes unnecessary regens.
177             # See "_mbwxspp_force_xs_regen"
178             #File::Spec->catdir($self->build_dir, 'typemap'),
179             );
180              
181 0           my $main_time = 1e99;
182 0 0 0       $main_time = -M $main_xs_file
183             if defined $main_xs_file and -e $main_xs_file;
184              
185 0 0 0       if (keys(%$xs_files) == 1
186             && (values(%$xs_files))[0] =~ /\Q$main_xs_file\E$/)
187             {
188             # is main xs file still current?
189 0 0 0       if (!$self->{_mbwxspp_force_xs_regen} && $main_time < $newest) {
190 0           return 1;
191             }
192             }
193              
194 0           delete $self->{_mbwxspp_force_xs_regen}; # done its job
195 0           $self->log_info("Generating main XS file...\n");
196              
197 0           my $early_includes = join "\n",
198             map {
199 0 0         s/^\s*#\s*include\s*//i;
200 0 0         /^"/ or $_ = "<$_>";
201 0           "#include $_"
202             }
203 0           @{ $self->early_includes || [] };
204              
205 0           my $module_name = $self->module_name;
206 0           my $xs_code = <<"HERE";
207             /*
208             * WARNING: This file was auto-generated. Changes will be lost!
209             */
210              
211             $early_includes
212              
213             #ifdef __cplusplus
214             extern "C" {
215             #endif
216             #include "EXTERN.h"
217             #include "perl.h"
218             #include "XSUB.h"
219             #include "ppport.h"
220             #undef do_open
221             #undef do_close
222             #ifdef __cplusplus
223             }
224             #endif
225              
226             MODULE = $module_name PACKAGE = $module_name
227              
228             HERE
229              
230 0           my $typemap_args = '';
231 0           $typemap_args .= '-t "' . _naive_shell_escape(Cwd::abs_path($_)) . '" ' foreach keys %$xspt_files;
232              
233 0           foreach my $xsp_file (keys %$xsp_files) {
234 0           my $full_path_file = _naive_shell_escape( Cwd::abs_path($xsp_file) );
235 0           my $cmd = qq{INCLUDE_COMMAND: \$^X -MExtUtils::XSpp::Cmd -e xspp -- $typemap_args "$full_path_file"\n\n};
236 0           $xs_code .= $cmd;
237             }
238              
239 0           my $outfile = File::Spec->catdir($self->build_dir, 'main.xs');
240 0 0         open my $fh, '>', $outfile
241             or die "Could not open '$outfile' for writing: $!";
242 0           print $fh $xs_code;
243 0           close $fh;
244              
245 0           return 1;
246             }
247              
248             sub _load_extra_typemap_modules {
249 0     0     my $self = shift;
250              
251 0           require ExtUtils::Typemaps;
252 0   0       my $extra_modules = $self->extra_typemap_modules||{};
253              
254 0           foreach my $module (keys %$extra_modules) {
255 0 0         my $str = $extra_modules->{$module}
256             ? "$module $extra_modules->{$module}"
257             : $module;
258 0 0         if (not eval "use $str;1;") {
259 0           $self->log_warn(<
260             ERROR: Required typemap module '$module' version $extra_modules->{$module} not found.
261             Error message:
262             $@
263             HERE
264             }
265             }
266             }
267              
268             sub ACTION_generate_typemap {
269 0     0 0   my $self = shift;
270 0           $self->depends_on('create_buildarea');
271              
272 0           require File::Spec;
273              
274 0           my $files = $self->find_map_files;
275              
276 0           $self->_load_extra_typemap_modules();
277 0   0       my $extra_modules = $self->extra_typemap_modules||{};
278              
279 0           my $newest = $self->_calc_newest(
280             keys(%$files),
281             'Build.PL',
282             );
283              
284 0           my $out_map_file = File::Spec->catfile($self->build_dir, 'typemap');
285 0 0 0       if (-f $out_map_file and -M $out_map_file < $newest) {
286 0           return 1;
287             }
288              
289 0           $self->log_info("Processing XS typemap files...\n");
290              
291             # merge all typemaps into 'buildtmp/typemap'
292             # creates empty typemap file if there are no files to merge
293 0           my $merged = ExtUtils::Typemaps->new;
294 0           $merged->merge(typemap => $_->new) for keys %$extra_modules;
295              
296 0           foreach my $file (keys %$files) {
297 0           $merged->merge(typemap => ExtUtils::Typemaps->new(file => $file));
298             }
299 0           $merged->write(file => $out_map_file);
300              
301 0           $self->{_mbwxspp_force_xs_regen} = 1;
302             }
303              
304             sub find_map_files {
305 0     0 0   my $self = shift;
306 0           my $files = $self->_find_file_by_type('map', 'lib');
307 0 0         my @extra_files = map glob($_),
308             map File::Spec->catfile($_, '*.map'),
309 0           (@{$self->extra_xs_dirs||[]});
310              
311 0           $files->{$_} = $_ foreach map $self->localize_file_path($_),
312             @extra_files;
313              
314 0 0         $files->{'typemap'} = 'typemap' if -f 'typemap';
315              
316 0           return $files;
317             }
318              
319              
320             sub find_xsp_files {
321 0     0 0   my $self = shift;
322              
323 0 0         my @extra_files = map glob($_),
324             map File::Spec->catfile($_, '*.xsp'),
325 0           (@{$self->extra_xs_dirs||[]});
326              
327 0           my $files = $self->_find_file_by_type('xsp', 'lib');
328 0           $files->{$_} = $_ foreach map $self->localize_file_path($_),
329             @extra_files;
330              
331 0           require File::Basename;
332             # XS++ typemaps aren't XSP files in this regard
333 0           foreach my $file (keys %$files) {
334 0 0         delete $files->{$file}
335             if File::Basename::basename($file) eq 'typemap.xsp';
336             }
337              
338 0           return $files;
339             }
340              
341             sub find_xsp_typemaps {
342 0     0 0   my $self = shift;
343              
344 0           my $xsp_files = $self->_find_file_by_type('xsp', 'lib');
345 0           my $xspt_files = $self->_find_file_by_type('xspt', 'lib');
346              
347 0           foreach (keys %$xsp_files) { # merge over 'typemap.xsp's
348 0 0         next unless File::Basename::basename($_) eq 'typemap.xsp';
349 0           $xspt_files->{$_} = $_
350             }
351              
352 0           my @extra_files = grep -e $_,
353             map glob($_),
354             grep defined $_ && /\S/,
355 0 0         map { ( File::Spec->catfile($_, 'typemap.xsp'),
356             File::Spec->catfile($_, '*.xspt') ) }
357 0   0       @{$self->extra_xs_dirs||[]};
358 0           $xspt_files->{$_} = $_ foreach map $self->localize_file_path($_),
359             @extra_files;
360 0           return $xspt_files;
361             }
362              
363              
364             # This overrides the equivalent in the base class to add the buildtmp and
365             # the main directory
366             sub find_xs_files {
367 0     0 0   my $self = shift;
368 0           my $xs_files = $self->SUPER::find_xs_files;
369              
370 0 0         my @extra_files = map glob($_),
371             map File::Spec->catfile($_, '*.xs'),
372 0           @{$self->extra_xs_dirs||[]};
373              
374 0           $xs_files->{$_} = $_ foreach map $self->localize_file_path($_),
375             @extra_files;
376              
377 0           my $auto_gen_file = File::Spec->catfile($self->build_dir, 'main.xs');
378 0 0         if (-e $auto_gen_file) {
379 0           $xs_files->{$auto_gen_file} = $self->localize_file_path($auto_gen_file);
380             }
381 0           return $xs_files;
382             }
383              
384              
385             # overridden from original. We really require
386             # EU::ParseXS, so the "if (eval{require EU::PXS})" is gone.
387             sub compile_xs {
388 0     0 0   my ($self, $file, %args) = @_;
389 0           $self->log_verbose("$file -> $args{outfile}\n");
390              
391 0           require ExtUtils::ParseXS;
392              
393 0           my $main_dir = Cwd::abs_path( Cwd::cwd() );
394 0           my $build_dir = Cwd::abs_path( $self->build_dir );
395 0           ExtUtils::ParseXS::process_file(
396             filename => $file,
397             prototypes => 0,
398             output => $args{outfile},
399             # not default:
400             'C++' => 1,
401             hiertype => 1,
402             typemap => File::Spec->catfile($build_dir, 'typemap'),
403             );
404             }
405              
406             # modified from orinal M::B (FIXME: shouldn't do this with private methods)
407             # Changes from the original:
408             # - If we're looking at the "main.xs" file in the build
409             # directory, override the TARGET paths with the real
410             # module name.
411             # - In that case, also override the file basename for further
412             # build products (maybe this should only be done on installation
413             # into blib/.../?)
414             sub _infer_xs_spec {
415 0     0     my $self = shift;
416 0           my $file = shift;
417              
418 0           my $cf = $self->{config};
419              
420 0           my %spec;
421              
422 0           my( $v, $d, $f ) = File::Spec->splitpath( $file );
423 0           my @d = File::Spec->splitdir( $d );
424 0           (my $file_base = $f) =~ s/\.[^.]+$//i;
425              
426 0           my $build_folder = $self->build_dir;
427 0 0 0       if ($d =~ /\Q$build_folder\E/ && $file_base eq 'main') {
428 0           my $name = $self->module_name;
429 0           @d = split /::/, $name;
430 0           $file_base = $d[-1];
431 0 0         pop @d if @d;
432             }
433             else {
434             # the module name
435 0   0       shift( @d ) while @d && ($d[0] eq 'lib' || $d[0] eq '');
      0        
436 0   0       pop( @d ) while @d && $d[-1] eq '';
437             }
438              
439 0           $spec{base_name} = $file_base;
440              
441 0           $spec{src_dir} = File::Spec->catpath( $v, $d, '' );
442              
443 0           $spec{module_name} = join( '::', (@d, $file_base) );
444              
445 0           $spec{archdir} = File::Spec->catdir($self->blib, 'arch', 'auto',
446             @d, $file_base);
447              
448 0           $spec{bs_file} = File::Spec->catfile($spec{archdir}, "${file_base}.bs");
449              
450 0           $spec{lib_file} = File::Spec->catfile($spec{archdir},
451             "${file_base}.".$cf->get('dlext'));
452              
453 0           $spec{c_file} = File::Spec->catfile( $spec{src_dir},
454             "${file_base}.c" );
455              
456 0           $spec{obj_file} = File::Spec->catfile( $spec{src_dir},
457             "${file_base}".$cf->get('obj_ext') );
458              
459 0           return \%spec;
460             }
461              
462             __PACKAGE__->add_property( 'cpp_source_files' => [] );
463             __PACKAGE__->add_property( 'cpp_source_dirs' => ['src'] );
464             __PACKAGE__->add_property( 'build_dir' => 'buildtmp' );
465             __PACKAGE__->add_property( 'extra_xs_dirs' => [".", grep { -d $_ and /^xsp?$/i } glob("*")] );
466             __PACKAGE__->add_property( 'extra_typemap_modules' => {} );
467             __PACKAGE__->add_property( 'early_includes' => [] );
468              
469              
470             sub _merge_hashes {
471 0     0     my $self = shift;
472 0           my %h;
473 0           foreach my $m (@_) {
474 0           $h{$_} = $m->{$_} foreach keys %$m;
475             }
476 0           return \%h;
477             }
478              
479             sub _calc_newest {
480 0     0     my $self = shift;
481 0           my $newest = 1.e99;
482 0           foreach my $file (@_) {
483 0 0         next if not defined $file;
484 0           my $age = -M $file;
485 0 0 0       $newest = $age if defined $age and $age < $newest;
486             }
487 0           return $newest;
488             }
489              
490             1;
491              
492             __END__