File Coverage

blib/lib/C/Scan/Constants.pm
Criterion Covered Total %
statement 148 159 93.0
branch 19 36 52.7
condition 6 18 33.3
subroutine 22 22 100.0
pod 2 2 100.0
total 197 237 83.1


line stmt bran cond sub pod time code
1             package C::Scan::Constants;
2              
3 4     4   156892 use 5.008003;
  4         17  
  4         166  
4 4     4   24 use strict;
  4         9  
  4         145  
5 4     4   21 use warnings;
  4         10  
  4         139  
6 4     4   28 use Carp;
  4         7  
  4         409  
7              
8 4     4   4411 use ExtUtils::Constant;
  4         234208  
  4         396  
9 4     4   5198 use ModPerl::CScan;
  4         22  
  4         349  
10 4     4   10300 use File::Temp qw( tempdir );
  4         228251  
  4         419  
11 4     4   9724 use File::Copy;
  4         12613  
  4         389  
12 4     4   28 use File::Spec;
  4         11  
  4         68  
13 4     4   22 use File::Path;
  4         6  
  4         211  
14 4     4   11621 use Data::Dumper;
  4         65155  
  4         351  
15 4     4   10610 use IO::File;
  4         9604  
  4         975  
16 4     4   34 use Config;
  4         8  
  4         1975  
17              
18             require Exporter;
19              
20             our @ISA = qw(Exporter);
21              
22             # Our functions are pretty uniquely named, and intended for
23             # calling from Makefile.PL, so we simply export them be default.
24             our @EXPORT = qw( extract_constants_from
25             write_constants_module );
26              
27             our %EXPORT_TAGS = ( 'all' => [ @EXPORT ] );
28             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
29              
30             our $VERSION = "1.020";
31             $VERSION = eval $VERSION;
32              
33             # This module was originally written to support a custom pure-Perl
34             # build system named Blueprint. If you know of or use Blueprint,
35             # this section will mean something to you. If not, ignore it.
36             my $g_use_blueprint_sections;
37             BEGIN {
38             # Initialize global variable(s)
39 4     4   11 $g_use_blueprint_sections = 0;
40              
41 4         317 eval 'require Blueprint';
42              
43 4 50       28613 unless ($@) {
44 0         0 $g_use_blueprint_sections = 1;
45             }
46              
47             # Now blueprint comment block protection is quietly enabled.
48             # This will almost never be turned on.
49             }
50              
51             # _get_constant_data_blobs_from()
52             #
53             # Internal function.
54             #
55             # Returns a two blobs of data from the supplied file:
56             # ($defines, <-- #define macros with no args
57             # $typedefs) <-- #typedef enum constants
58             sub _get_constant_data_blobs_from {
59 5     5   23 my $file_to_relocate = shift;
60              
61 5 50       149 if ( ! -f $file_to_relocate ) {
62 0         0 croak "$file_to_relocate does not appear to be accessible";
63             }
64              
65             # Create a temp directory here.
66 5 50       476 my $temp_scan_dir = tempdir( 'c_scan_const_XXXXX',
67             DIR => File::Spec->tmpdir(),
68             CLEANUP => 1 )
69             or die "Internal error: failed to create temp dir";
70              
71             # copy the file into it
72 5         170500 my $scan_file_basename = ( File::Spec->splitpath($file_to_relocate) )[2];
73 5         87 my $relocated_file = File::Spec->catpath( '',
74             $temp_scan_dir,
75             $scan_file_basename );
76 5 50       65 copy($file_to_relocate, $relocated_file)
77             or croak "Could not copy $file_to_relocate to $relocated_file";
78              
79             # scan the file
80 5         3557 my $c_header_file = ModPerl::CScan->new( filename => $relocated_file );
81            
82 5 50       21 if ( !defined( $c_header_file ) ) {
83 0         0 croak "Could not create ModPerl::CScan obj for $relocated_file";
84             }
85              
86             # Ugly hack to fix ActivePerl config bomb, i.e. expectation that "cppstdin"
87             # is the cpp we'll be using. This assumes MinGW is installed, which we
88             # attempted to enforce in the Makefile.PL. It probably assumes more than
89             # should be safely assumed about the return data structure from Data::Flow,
90             # but it seems to work.
91 5 50       46 if ( $^O =~ /MSWin/i ) {
92 0         0 my $cur_cppstdin = $c_header_file->get('Cpp')->{cppstdin};
93 0         0 my $cur_cc = $Config{cc};
94 0 0 0     0 unless ( $cur_cppstdin =~ /$cur_cc/
95             and $cur_cppstdin =~ /\-E/ ) {
96 0         0 $c_header_file->get('Cpp')->{cppstdin} = "$cur_cc -E";
97             }
98             }
99            
100             # Swallow STDERR temporarily
101 5         130 open my $OLDERR, ">&", STDERR;
102 5         27 close(STDERR);
103              
104             # Redirect temporarily to the bit bucket, but keep it open
105             # to avoid conflicting in a -w environment such as under test.
106             # TBD: Make this friendlier for non-*n[u|i]x systems.
107 5         195 open *STDERR, ">", "/dev/null";
108              
109             # We only care about unadorned macros, i.e. "defines"
110 5         139 my $defs = $c_header_file->get("defines_no_args");
111             ### These next lines represent possible future functionality ####
112             # my $defs2 = $c_header_file->get("defines_maybe");
113             # my $defs3 = $c_header_file->get("defines_full");
114             # my $defs4 = $c_header_file->get("defines_args");
115             # my $defs5 = $c_header_file->get("defines_no_args_full");
116             # my $defs6 = $c_header_file->get("Defines");
117             ##################################################################
118 5         54 my $typedefs = $c_header_file->get("typedef_texts");
119              
120              
121             ### For debugging only ######################################################
122             ### NOTE: need to send STDERR somewhere other than /dev/null for these to
123             ### work as intended.
124             ###
125             # warn sprintf("[$file_to_relocate] defines_no_args = %s", Dumper($defs));
126             # warn sprintf("[$file_to_relocate] defines_maybe = %s", Dumper($defs2));
127             # warn sprintf("[$file_to_relocate] defines_full = %s", Dumper($defs3));
128             # warn sprintf("[$file_to_relocate] defines_args = %s", Dumper($defs4));
129             # warn sprintf("[$file_to_relocate] defines_no_args_full = %s", Dumper($defs5));
130             # warn sprintf("[$file_to_relocate] Defines = %s", Dumper($defs6));
131             # warn sprintf("[$file_to_relocate] enums = %s", Dumper($typedefs));
132             #############################################################################
133              
134             # Restore STDERR and close the temp filehandle for neatness.
135 5         233 close STDERR;
136 5         121 open STDERR, ">&", $OLDERR;
137 5         38 close $OLDERR;
138              
139             # Return the file object returned from ModPerl::CScan->new()
140             # Note: these may be empty (hashref, arrayref)
141 5         519 return ($defs, $typedefs);
142             }
143              
144              
145              
146              
147             # extract_constants_from()
148             #
149             # Exported function.
150             #
151             # This function takes a list of C header (.h) files and returns a list
152             # of constants information suitable for supplying as the NAME parameter
153             # to ExtUtils::Constant.
154             sub extract_constants_from {
155 2     2 1 182 my @c_header_paths = @_; # full paths to each .h file to scan
156              
157 2         5 my @all_constants;
158              
159             C_HEADER_FILE:
160 2         7 foreach my $c_header_file ( @c_header_paths ) {
161 5         32 my ($defs,
162             $typedefs) = _get_constant_data_blobs_from( $c_header_file );
163              
164 5 0 33     115 if ( ( !defined $defs ||
      0        
      33        
165             (defined $defs && scalar( keys %$defs ) == 0) ) and
166             ( !defined $typedefs ||
167             (defined $typedefs && scalar @$typedefs == 0) ) ) {
168 0         0 warn "WARNING: Found no constants in $c_header_file.";
169 0         0 next C_HEADER_FILE;
170             }
171              
172             # Do the messy enum extraction
173 5         72 my @enums = _extract_enum_constants_from( $typedefs );
174              
175             # We convert the base filename into something we can use
176             # to avoid the error of throwing away the "filename constant"
177             # e.g. #ifndef FOO_H_
178             # #define FOO_H_
179 5         348 my $all_caps_basename = uc ( ( File::Spec->splitpath($c_header_file) )[2] );
180 5         47 $all_caps_basename =~ s/[.]/_/g;
181              
182             # Consolidate all names found into a single list.
183             # Note that we discard string constants.
184 577         527 my @constant_names = ( @enums,
185             grep {
186 5         209 my $defn = $_;
187              
188             # Toss header file identifiers, but only
189             # when they are *really* header file identifiers.
190 577 100 66     4320 ( $defn !~ /_H[_]?$/
      66        
191             or ($defn =~ /_H[_]?$/
192             and $all_caps_basename !~ /[_]?$defn[_]?/) )
193              
194             # Toss things ending in underscore (may not
195             # be a good idea, but we'll wait to be convinced...)
196             and $defn !~ /_$/
197              
198             # Toss string constants.
199             and $defs->{$defn} !~ /^["]/
200 5         10 } keys %{$defs} );
201              
202             # Add these to the output
203 5         261 push @all_constants, @constant_names;
204             }
205              
206 2         159 return @all_constants;
207             }
208              
209              
210              
211              
212             # _extract_enum_constants_from()
213             #
214             # Internal function.
215             #
216             # Does some heinous massaging on a "typedef blob" returned from the
217             # ModPerl::CScan::get() macro, ultimately spitting out a hashref for each
218             # enumerated constant of the following form:
219             #
220             # { name => $enumerated_constant_name,
221             # macro => 1 }
222             #
223             # See C::Scan for more details on the "typedef blob".
224             sub _extract_enum_constants_from {
225              
226 5     5   11 my $typedefs = shift;
227              
228             # enums will live in the @$typedefs array as follows:
229             # ' enum
230             # {
231             # FOO_TYPE_A, FOO_TYPE_B, FOO_TYPE_C,
232             # FOO_TYPE_D, FOO_TYPE_E, FOO_TYPE_F,
233             # FOO_TYPE_INVALID
234             # } foo_type_e'
235             # We want to remove all the extraneous stuff and output the
236             # following for each enum constant:
237             # { name => $constant, macro => 1 }
238             # This can then be fed into the NAMES parameter of WriteConstant
239             # and have it do the right thing.
240 30         87 my @enums = map { { name => "$_", macro => 1 } } # 7) assemble hashrefs
  30         43  
241             # for
242             # WriteConstants()
243 30         51 map { s/[=][^\s]+//; $_ } # 6) discard explicit
  4         32  
244             # val settings
245 4         49 map { split ',' } # 5) split into consts
246 141         216 map { s/^\s*enum.+[{]\s*//s; # 2) strip chars up
247             # to 1st constant
248 4         32 s/\s*[}].+_e$//s; # 3) strip chars after
249             # last constant
250 4         42 s/\s//sg; # 4) strip all other
251             # whitespace
252 4         15 $_ }
253 5         10 grep { /enum/ } @{$typedefs}; # 1) find "enum" typedefs
  5         14  
254              
255 5         24 return @enums;
256             }
257              
258              
259              
260              
261             # _const_mod_header_text()
262             #
263             # Internal function.
264             #
265             # Return the block of code to be written to the top of the Symbols.pm
266             # module.
267             sub _const_mod_header_text {
268 1     1   2 my $sub_pkg_name = shift;
269              
270 1         13 return <<"END_OF_MODULE_HEADER";
271             package $sub_pkg_name;
272              
273             use 5.008003;
274             use strict;
275             use warnings;
276              
277             use base 'Exporter';
278              
279             our \@EXPORT = qw( \@ALL );
280              
281             our \@ALL = qw(
282             END_OF_MODULE_HEADER
283             }
284              
285              
286              
287              
288             # _const_mod_symbol_names()
289             #
290             # Internal function.
291             #
292             # Return symbol names found in a list such as that which is returned
293             # from extract_constants_from(). This function is typically used
294             # to get text for writing to the middle portion of the Symbols.pm
295             # module.
296             sub _const_mod_symbol_names {
297 2     2   7 my $names_ref = shift;
298              
299 2         6 my $symbol_names_str = "";
300 2         5 for my $symbol (@$names_ref) {
301 46 100       74 if (ref $symbol) {
302 30         55 $symbol_names_str .= join q{}, ' 'x4,
303             $symbol->{name},
304             "\n";
305             }
306             else {
307 16         32 $symbol_names_str .= join q{}, ' 'x4,
308             $symbol,
309             "\n";
310             }
311             }
312              
313 2         26 return $symbol_names_str;
314             }
315              
316              
317              
318              
319             # _const_mod_trailer_text()
320             #
321             # Internal function.
322             #
323             # Return the block of code to be written to the bottom of the Symbols.pm
324             # module.
325             sub _const_mod_trailer_text {
326 1     1   3 return <<"END_OF_MODULE_TRAILER";
327             );
328              
329             1;
330             END_OF_MODULE_TRAILER
331             }
332              
333              
334              
335             # write_constants_module()
336             #
337             # Exported function.
338             #
339             # This function writes a Constants/C/Symbols.pm submodule into the
340             # invoking Makefile.PL module's namespace.
341             sub write_constants_module {
342 1     1 1 170 my $pkg_name = shift;
343 1         5 my @c_constants = @_; # array of symbol name blobs
344              
345             # This is the canonical name of the submodule exporting the C symbols
346 1         8 my $const_mod_base_name = 'Symbols.pm';
347 1         5 my $fwd_decl_base_name = 'ForwardDecls.pm';
348 1         6 my @const_mod_subdir_elems = qw(Constants C);
349              
350             # turn the current package name into a directory path, creating
351             # subordinate paths if needed
352 1         7 my $const_mod_dir_name
353             = join "/", ( 'lib',
354             split( "::", $pkg_name ),
355             @const_mod_subdir_elems,
356             );
357              
358 1         12 my $const_mod_base_full_name
359             = join '/', ( $const_mod_dir_name,
360             $const_mod_base_name,
361             );
362 1         5 my $fwd_decl_base_full_name
363             = join '/', ( $const_mod_dir_name,
364             $fwd_decl_base_name,
365             );
366              
367              
368             # Create directory in which to place the module
369 1 50       37 unless (-d "$const_mod_dir_name") {
370 1 50       461 mkpath( $const_mod_dir_name, 0, 0755) or die "mkpath failed: $!";
371             }
372              
373             # Create the module file to house the list of constants, as
374             # well as the forward declarations file.
375 1 50       70 open my $const_mod_fh, ">", "$const_mod_base_full_name"
376             or die "Could not open $const_mod_base_name for writing: $!";
377 1 50       56 open my $fwd_decl_fh, ">", "$fwd_decl_base_full_name"
378             or die "Could not open $fwd_decl_base_full_name for writing: $!";
379              
380             # Common arg list for the next threee functions
381 1         11 (my $const_mod_name_prefix = $const_mod_base_name) =~ s/[.]pm$//;
382 1         8 my $sub_pkg_name = join "::", ($pkg_name,
383             @const_mod_subdir_elems,
384             $const_mod_name_prefix);
385              
386             # Write file contents.
387 1         2 print {$const_mod_fh} _const_mod_header_text( $sub_pkg_name );
  1         8  
388 1         5 print {$const_mod_fh} _const_mod_symbol_names( \@c_constants );
  1         7  
389 1         2 print {$const_mod_fh} _const_mod_trailer_text( );
  1         4  
390              
391             # Close file.
392 1         53 close $const_mod_fh;
393              
394             # Write forward declarations
395 1         4 my @sym_names = split /\s+/, _const_mod_symbol_names( \@c_constants );
396 1         4 for my $sym (grep { ! /^\s*$/ } @sym_names) {
  24         65  
397 23         28 print {$fwd_decl_fh} "sub $sym();\n";
  23         46  
398             }
399 1         2 print {$fwd_decl_fh} "\n1;\n";
  1         3  
400              
401             # Close file.
402 1         35 close $fwd_decl_fh;
403              
404             # Now write the XS stuff. This is overly simplistic. For example,
405             # string constants will not be handled correctly this way.
406 1         75 ExtUtils::Constant::WriteConstants(
407             NAME => $pkg_name,
408             NAMES => \@c_constants,
409             DEFAULT_TYPE => 'IV',
410             C_FILE => 'const-c.inc',
411             XS_FILE => 'const-xs.inc',
412             );
413              
414             # We've now written the file, but we need to modify handling of IVs
415             # to avoid seg faults on C constant access.
416 1 50       49288 open CONST_XS_IN, "const-xs.inc"
417             or die "Failed to open autogen'd const-xs.inc file for mods: $!";
418 1         68 my @in_code_lines = ;
419 1         13 close CONST_XS_IN;
420              
421             # Make the modification. Basically we assure that returned IVs have
422             # refcounts of 1 vs. leaving it up to Perl to decide.
423 1         3 my @out_code_lines;
424 1         4 for my $line (@in_code_lines) {
425 90 100       172 if ($line =~ /PUSHi[(]iv[)]/) {
426 1         3 $line = " PUSHs(sv_2mortal(newSViv(iv)));\n";
427             }
428 90         139 push @out_code_lines, $line;
429             }
430              
431             # Write out the modified file. Only one line should differ from
432             # the original.
433 1 50       85 open CONST_XS_OUT, ">const-xs.inc"
434             or die "Failed to open const-xs.inc for writing, post mods: $!";
435 1         3 for my $line (@out_code_lines) {
436 90         128 print CONST_XS_OUT $line;
437             }
438 1         36 close CONST_XS_OUT;
439              
440             # Help the user out. They will need to modify their code.
441 1         2 print {*STDERR} _suggested_code_snippets($pkg_name);
  1         15  
442              
443 1         32 return;
444             }
445              
446              
447              
448             # _suggested_code_snippets()
449             #
450             # Internal function.
451             #
452             # Returns a block of text that provides helpful direction to
453             # someone who has just run C::Scan::Constants code, via "perl Makefile.PL"
454             # so that the next time they do that they'll actually get all the
455             # goodies wired into their code.
456             sub _suggested_code_snippets {
457 2     2   12 my $pkg_name = shift;
458              
459             # Set up for extra decoration if needed to help out a build system
460 2         8 my ($header,$trailer);
461              
462             # As mentioned above, we include support for a custom pure-Perl
463             # build system named Blueprint. If you know of or use Blueprint,
464             # the "if" clause here will mean something to you. If not, ignore it.
465 2 50       10 if ($g_use_blueprint_sections) {
466 0         0 $header = "##### (BLUEPRINT: BEGIN EXPECTED OUTPUT) #####\n";
467 0         0 $trailer = "##### (BLUEPRINT: END EXPECTED OUTPUT) #####\n";
468             }
469             else {
470             # The most common situation
471 2         4 $header = q{};
472 2         5 $trailer = q{};
473             }
474              
475 2         93 return <<"END_BEGIN_SNIPPET";
476             $header
477              
478             You will need to add some code to your YourPkgName.pm and YourPkgName.xs
479             files in order to make use of the code that has just been autogenerated
480             via C::Scan::Constants.
481              
482             If you've already added the code, just ignore this message.
483              
484             Otherwise, do some cut-and-paste of the following snippets,
485             substituting "YourPkgName" with your actual module name
486             everywhere you see it in the snippets.
487              
488             Then, simply "make" and test! It's that easy.
489              
490             #------------- start of .pm snippet ----------------------
491              
492             # Do we have C symbols in a YourPkgName::Constants::C::Symbols module?
493             my \$_symbols_present;
494              
495             # Check for (and note) the existence of the C constants module.
496             BEGIN {
497             eval "require YourPkgName::Constants::C::Symbols";
498             \$_symbols_present = 1 unless \$\@;
499              
500             eval "require YourPkgName::Constants::C::ForwardDecls";
501             }
502              
503             # (Later, in your exports definition section...)
504              
505             # Bring in the whole lot of C constants that are available. Your mileage
506             # of course, may vary, e.g. alternatively do this via \@EXPORT_OK.
507             our \@EXPORT = (
508              
509             # any other symbols you are exporting, plus:
510              
511             \$_symbols_present ? \@YourPkgName::Constants::C::Symbols::ALL
512             : (),
513             );
514              
515             # Make sure to have a $VERSION defined.
516              
517             # Then, prior to subroutine definitions, insert the following. Note
518             # that if you left autoloading turned on when you created your module
519             # skeleton with h2xs (i.e. you did *not* specify -A when you ran it),
520             # you already have this code in place.
521              
522             use Carp;
523             use AutoLoader;
524              
525             sub AUTOLOAD {
526             # This AUTOLOAD is used to 'autoload' constants from the constant()
527             # XS function.
528              
529             my \$constname;
530             our \$AUTOLOAD;
531             (\$constname = \$AUTOLOAD) =~ s/.*:://;
532             croak "&YourPkgName::constant not defined" if \$constname eq 'constant'
533             ;
534             my (\$error, \$val) = constant(\$constname);
535             if (\$error) { croak \$error; }
536             {
537             no strict 'refs';
538             *\$AUTOLOAD = sub { \$val };
539             }
540             goto &\$AUTOLOAD;
541             }
542             require XSLoader;
543             XSLoader::load('YourPkgName', \$VERSION);
544              
545             #------------- start of .pm snippet ----------------------
546              
547             #------------- start of .xs snippet ----------------------
548              
549             # In YourPkgName.xs, make sure to add the following lines.
550              
551             /* Before "MODULE =" line: */
552              
553             /* Specific .h files to scan */
554             #include "header_file_a.h"
555             #include "header_file_b.h"
556             /* ... */
557             #include "header_file_c.h"
558              
559             /*
560             * Note that if you left autoloading turned on when you created your module
561             * skeleton with h2xs (i.e. you did *not* specify -A when you ran it),
562             * you probably already have the code below in place and ready to use.
563             */
564              
565             /* Reference to autogenerated C-side binding file */
566             #include "const-c.inc"
567              
568             /* After "MODULE =" line: */
569              
570             # Reference to autogenerated xs-side binding file.
571             INCLUDE: const-xs.inc
572              
573             #------------- end of .xs snippet ------------------------
574              
575             $trailer
576             END_BEGIN_SNIPPET
577              
578             }
579              
580             1;
581             __END__