use strict; use warnings; use 5.008003; use Module::Build; use File::Find qw( find ); use Storable qw( store retrieve ); use File::Spec::Functions qw( catfile ); use Fcntl; =for comment Build.PL file generates a slew of files on the fly before writing the Build script. See KinoSearch::Docs::DevGuide. =cut my $ks_xs_filepath = 'KinoSearch.xs'; # keep lists of which .c .h and .xs files need to be rewritten and cleaned up my %needs_rewrite; # retrieve a list of modification times from when Build.PL was last run my $lastmod = -f 'lastmod' ? retrieve('lastmod') : {}; # If Build.PL was modified, force recompile of KinoSearch.xs my $build_pl_lastmod = ( stat('Build.PL') )[9]; if ( !exists $lastmod->{'Build.PL'} or $lastmod->{'Build.PL'} != $build_pl_lastmod ) { $needs_rewrite{$ks_xs_filepath} = 1; } $lastmod->{'Build.PL'} = $build_pl_lastmod; # hold filepath => content pairs for generated files my %code = ( $ks_xs_filepath => '' ); my %source_pm = (); # grab all .pm filepaths, making sure that KinoSearch.pm is first my @pm_filepaths; find( { wanted => sub { if ( $File::Find::name =~ /KinoSearch\.pm$/ ) { unshift @pm_filepaths, $File::Find::name; } elsif ( $File::Find::name =~ /\.pm$/ ) { push @pm_filepaths, $File::Find::name; } }, no_chdir => 1, }, 'lib', ); for my $pm_filepath (@pm_filepaths) { open( my $module_fh, '<', $pm_filepath ) or die "couldn't open file '$pm_filepath': $!"; my $module_text = do { local $/; <$module_fh> }; my $outfilepath; # grab code that's delimited by an xs, c, or h __TAG__ my $inside = ''; my $line_count = 0; while ( $module_text =~ /(.*?(?:\n|\r\n|\r))/g ) { $line_count++; my $line = $1; if ( $line =~ /^__(\w+)__/ ) { # the tag to begin a block $inside = $1; if ( $inside eq 'XS' ) { # all XS code goes into one file: lib/KinoSearch.xs $outfilepath = $ks_xs_filepath; } elsif ( $inside eq 'H' or $inside eq 'C' ) { # each .c and .h code section becomes its own file $outfilepath = $pm_filepath; $outfilepath =~ s/lib//; $outfilepath =~ s/\W//g; $outfilepath =~ s/pm$//; if ( $inside eq 'H' ) { $outfilepath .= ".h"; # prepend an #include to KinoSearch.xs $code{$ks_xs_filepath} = qq|#include "$outfilepath"\n$code{$ks_xs_filepath}|; } else { $outfilepath .= ".c"; } $outfilepath = catfile( 'src', $outfilepath ); my $line_start = $line_count + 1; $code{$outfilepath} = qq|#line $line_start "$pm_filepath"\n|; } if ( $inside =~ /^(?:XS|H|C)$/ ) { # if the file has been modified, force a recompile my $mod_time = ( stat($module_fh) )[9]; if ( !exists $lastmod->{$pm_filepath}{$inside} or $lastmod->{$pm_filepath}{$inside} != $mod_time ) { $needs_rewrite{$outfilepath} = 1; } $lastmod->{$pm_filepath}{$inside} = $mod_time; $source_pm{$outfilepath} = $pm_filepath; } } elsif ( $inside =~ /^(?:XS|H|C)$/ ) { $code{$outfilepath} .= $line; } } } # write all the files that have been modified. for my $outfilepath ( keys %needs_rewrite ) { my $autogen_header = <<"END_AUTOGEN"; /*********************************************** !!!! DO NOT EDIT THIS FILE !!!! This file was auto-generated by Build.PL from $source_pm{$outfilepath} See KinoSearch::Docs::DevGuide for details. ***********************************************/ END_AUTOGEN print "Writing $outfilepath\n"; unlink $outfilepath; sysopen( my $fh, $outfilepath, O_CREAT | O_EXCL | O_WRONLY ) or die "Couldn't open file '$outfilepath' for writing: $!"; print $fh "$autogen_header$code{$outfilepath}" or die "Print to '$outfilepath' failed: $!"; close $fh or die "Couldn't close file '$outfilepath': $!"; } =begin Rationale All of KinoSearch's C-struct types share the same typemap profile, but can't be mapped to a single type. Instead of tediously hand-editing the typemap file, we autogenerate the file. Adding a new type is now as simple as adding an item to the @struct_classes array (provided it follows the same pattern as all the others). =end Rationale =cut # write the typemap file. if ( $needs_rewrite{$ks_xs_filepath} ) { my @struct_classes = qw( KinoSearch::Analysis::Stemmer::Stemmifier KinoSearch::Analysis::Token KinoSearch::Analysis::TokenBatch KinoSearch::Index::SegTermEnum KinoSearch::Index::TermBuffer KinoSearch::Index::TermDocs KinoSearch::Index::TermInfo KinoSearch::Index::TermInfosWriter KinoSearch::Search::HitCollector KinoSearch::Search::MatchBatch KinoSearch::Search::Scorer KinoSearch::Search::Similarity KinoSearch::Store::InStream KinoSearch::Store::OutStream KinoSearch::Util::BitVector KinoSearch::Util::BoolSet KinoSearch::Util::PriorityQueue KinoSearch::Util::SortExternal ); my $typemap_start = qq|\nTYPEMAP\n|; my $typemap_input = qq|\n\nINPUT\n|; my $typemap_output = qq|\n\nOUTPUT\n|; for my $struct_class (@struct_classes) { my ($ctype) = $struct_class =~ /([^:]+$)/; my $uc_ctype = uc($ctype); $ctype .= ' *'; $typemap_start .= "$ctype\t$uc_ctype\n"; my $input_frag = <<'END_INPUT'; #UC_CTYPE# if (sv_derived_from($arg, \"#STRUCT_CLASS#\")) { $var = INT2PTR($type,( SvIV((SV*)SvRV($arg)) ) ); } else Perl_croak(aTHX_ \"$var is not of type #STRUCT_CLASS#\") END_INPUT $input_frag =~ s/#UC_CTYPE#/$uc_ctype/gsm; $input_frag =~ s/#STRUCT_CLASS#/$struct_class/gsm; $typemap_input .= $input_frag; my $output_frag .= <<'END_OUTPUT'; #UC_CTYPE# sv_setref_pv($arg, \"#STRUCT_CLASS#\", (void*)$var); END_OUTPUT $output_frag =~ s/#UC_CTYPE#/$uc_ctype/gsm; $output_frag =~ s/#STRUCT_CLASS#/$struct_class/gsm; $typemap_output .= $output_frag; } # blast it out print "Writing typemap\n"; unlink 'typemap'; sysopen( my $typemap_fh, 'typemap', O_CREAT | O_WRONLY | O_EXCL ) or die "Couldn't open 'typemap' for writing: $!"; print $typemap_fh "# Auto-generated file. See KinoSearch::Docs::DevGuide.\n\n" or die "Print to 'typemap' failed: $!"; print $typemap_fh "$typemap_start $typemap_input $typemap_output" or die "Print to 'typemap' failed: $!"; } # record mod times in anticipation of Build.PL's next run store( $lastmod, 'lastmod' ); my $builder = Module::Build->new( module_name => 'KinoSearch', license => 'perl', dist_author => 'Marvin Humphrey ', dist_version_from => 'lib/KinoSearch.pm', requires => { 'Compress::Zlib' => 0, 'Lingua::Stem::Snowball' => 0.94, 'Lingua::StopWords' => 0.02, }, build_requires => { 'ExtUtils::CBuilder' => 0, 'ExtUtils::ParseXS' => 0, }, create_makefile_pl => 'passthrough', # extra_compiler_flags => [ # '-Wall', '-Wextra', # '-pedantic', '-ansi', # '-DPERL_GCC_PEDANTIC', '-std=c89', # ], xs_files => { $ks_xs_filepath => 'lib/KinoSearch.xs' }, c_source => 'src', add_to_cleanup => [ keys %code, 'KinoSearch-*', 'typemap', 'MANIFEST.bak', 'lastmod', 'perltidy.ERR', '*.o', ], ); $builder->create_build_script();