| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| use strict; |
| use warnings; |
| |
| package Lucy::Build; |
| use base qw( |
| Clownfish::CFC::Perl::Build |
| Clownfish::CFC::Perl::Build::Charmonic |
| ); |
| |
| our $VERSION = '0.004001'; |
| $VERSION = eval $VERSION; |
| |
| use File::Spec::Functions qw( catdir catfile rel2abs ); |
| use File::Path qw( rmtree ); |
| use File::Copy qw( move ); |
| use Config; |
| use Carp; |
| use Cwd qw( getcwd ); |
| |
| my @BASE_PATH = __PACKAGE__->cf_base_path; |
| |
| my $COMMON_SOURCE_DIR = catdir( @BASE_PATH, 'common' ); |
| my $LEMON_DIR = catdir( @BASE_PATH, 'lemon' ); |
| my $LEMON_EXE_PATH = catfile( $LEMON_DIR, "lemon$Config{_exe}" ); |
| my $CORE_SOURCE_DIR = catdir( @BASE_PATH, 'core' ); |
| my $LIB_DIR = 'lib'; |
| my $IS_CPAN_DIST = !@BASE_PATH; |
| my $CHARMONIZER_C; |
| if ($IS_CPAN_DIST) { |
| $CHARMONIZER_C = 'charmonizer.c'; |
| } |
| else { |
| $CHARMONIZER_C = catfile( $COMMON_SOURCE_DIR, 'charmonizer.c' ); |
| } |
| |
| sub new { |
| my $self = shift->SUPER::new( recursive_test_files => 1, @_ ); |
| |
| # Fix for MSVC: Although the generated XS should be C89-compliant, it |
| # must be compiled in C++ mode like the rest of the code due to a |
| # mismatch between the sizes of the C++ bool type and the emulated bool |
| # type. (The XS code is compiled with Module::Build's extra compiler |
| # flags, not the Clownfish cflags.) |
| if ($Config{cc} =~ /^cl\b/) { |
| my $extra_cflags = $self->extra_compiler_flags; |
| push @$extra_cflags, '/TP'; |
| $self->extra_compiler_flags(@$extra_cflags); |
| } |
| |
| if ( $ENV{LUCY_VALGRIND} ) { |
| my $optimize = $self->config('optimize') || ''; |
| $optimize =~ s/\-O\d+/-O1/g; |
| $self->config( optimize => $optimize ); |
| } |
| |
| $self->charmonizer_params( charmonizer_c => $CHARMONIZER_C ); |
| |
| $self->clownfish_params( autogen_header => $self->autogen_header ); |
| |
| return $self; |
| } |
| |
| sub _run_make { |
| my ( $self, %params ) = @_; |
| my @command = @{ $params{args} }; |
| my $dir = $params{dir}; |
| my $current_directory = getcwd(); |
| chdir $dir if $dir; |
| unshift @command, 'CC=' . $self->config('cc'); |
| if ( $self->config('cc') =~ /^cl\b/ ) { |
| unshift @command, "-f", "Makefile.MSVC"; |
| } |
| elsif ( $^O =~ /mswin/i ) { |
| unshift @command, "-f", "Makefile.MinGW"; |
| } |
| unshift @command, "$Config{make}"; |
| system(@command) and confess("$Config{make} failed"); |
| chdir $current_directory if $dir; |
| } |
| |
| # Build the Lemon parser generator. |
| sub ACTION_lemon { |
| my $self = shift; |
| print "Building the Lemon parser generator...\n\n"; |
| $self->_run_make( |
| dir => $LEMON_DIR, |
| args => [], |
| ); |
| } |
| |
| sub ACTION_copy_clownfish_includes { |
| my $self = shift; |
| |
| $self->SUPER::ACTION_copy_clownfish_includes; |
| |
| $self->cf_copy_include_file( qw( Lucy Util ToolSet.h ) ); |
| } |
| |
| sub _valgrind_base_command { |
| return |
| "PERL_DESTRUCT_LEVEL=2 LUCY_VALGRIND=1 valgrind " |
| . "--leak-check=yes " |
| . "--show-reachable=yes " |
| . "--dsymutil=yes " |
| . "--suppressions=../devel/conf/lucyperl.supp "; |
| } |
| |
| # Run the entire test suite under Valgrind. |
| # |
| # For this to work, Lucy must be compiled with the LUCY_VALGRIND environment |
| # variable set to a true value, under a debugging Perl. |
| # |
| # A custom suppressions file will probably be needed -- use your judgment. |
| # To pass in one or more local suppressions files, provide a comma separated |
| # list like so: |
| # |
| # $ ./Build test_valgrind --suppressions=foo.supp,bar.supp |
| sub ACTION_test_valgrind { |
| my $self = shift; |
| # Debian's debugperl uses the Config.pm of the standard system perl |
| # so -DDEBUGGING won't be detected. |
| die "Must be run under a perl that was compiled with -DDEBUGGING" |
| unless $self->config('ccflags') =~ /-D?DEBUGGING\b/ |
| || $^X =~ /\bdebugperl\b/; |
| if ( !$ENV{LUCY_VALGRIND} ) { |
| warn "\$ENV{LUCY_VALGRIND} not true -- possible false positives"; |
| } |
| $self->depends_on('code'); |
| |
| # Unbuffer STDOUT, grab test file names. |
| $|++; |
| my $t_files = $self->find_test_files; # not public M::B API, may fail |
| my $valgrind_command = $self->_valgrind_base_command; |
| |
| if ( my $local_supp = $self->args('suppressions') ) { |
| for my $supp ( split( ',', $local_supp ) ) { |
| $valgrind_command .= "--suppressions=$supp "; |
| } |
| } |
| |
| # Iterate over test files. |
| my @failed; |
| for my $t_file (@$t_files) { |
| |
| # Run test file under Valgrind. |
| print "Testing $t_file..."; |
| die "Can't find '$t_file'" unless -f $t_file; |
| my $command = "$valgrind_command $^X -Mblib $t_file 2>&1"; |
| my $output = "\n" . ( scalar localtime(time) ) . "\n$command\n"; |
| $output .= `$command`; |
| |
| # Screen-scrape Valgrind output, looking for errors and leaks. |
| if ( $? |
| or $output =~ /ERROR SUMMARY:\s+[^0\s]/ |
| or $output =~ /definitely lost:\s+[^0\s]/ |
| or $output =~ /possibly lost:\s+[^0\s]/ |
| or $output =~ /still reachable:\s+[^0\s]/ ) |
| { |
| print " failed.\n"; |
| push @failed, $t_file; |
| print "$output\n"; |
| } |
| else { |
| print " succeeded.\n"; |
| } |
| } |
| |
| # If there are failed tests, print a summary list. |
| if (@failed) { |
| print "\nFailed " |
| . scalar @failed . "/" |
| . scalar @$t_files |
| . " test files:\n " |
| . join( "\n ", @failed ) . "\n"; |
| exit(1); |
| } |
| } |
| |
| # Run all .y files through lemon. |
| sub ACTION_parsers { |
| my $self = shift; |
| $self->depends_on('lemon'); |
| my $y_files = $self->rscan_dir( $CORE_SOURCE_DIR, qr/\.y$/ ); |
| for my $y_file (@$y_files) { |
| my $c_file = $y_file; |
| my $h_file = $y_file; |
| $c_file =~ s/\.y$/.c/ or die "no match"; |
| $h_file =~ s/\.y$/.h/ or die "no match"; |
| next if $self->up_to_date( $y_file, [ $c_file, $h_file ] ); |
| $self->add_to_cleanup( $c_file, $h_file ); |
| system( $LEMON_EXE_PATH, '-q', $y_file ) and die "lemon failed"; |
| } |
| } |
| |
| sub ACTION_compile_custom_xs { |
| my $self = shift; |
| |
| $self->depends_on(qw( parsers charmony )); |
| |
| # Add extra compiler flags from Charmonizer. |
| my $charm_cflags = $self->charmony('EXTRA_CFLAGS'); |
| if ($charm_cflags) { |
| my $cf_cflags = $self->clownfish_params('cflags'); |
| if ($cf_cflags) { |
| $cf_cflags .= " $charm_cflags"; |
| } |
| else { |
| $cf_cflags = $charm_cflags; |
| } |
| $self->clownfish_params( cflags => $cf_cflags ); |
| } |
| |
| $self->SUPER::ACTION_compile_custom_xs; |
| } |
| |
| sub autogen_header { |
| my $self = shift; |
| return <<"END_AUTOGEN"; |
| /*********************************************** |
| |
| !!!! DO NOT EDIT !!!! |
| |
| This file was auto-generated by Build.PL. |
| |
| ***********************************************/ |
| |
| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| END_AUTOGEN |
| } |
| |
| sub _check_module_build_for_dist { |
| eval "use Module::Build 0.38;"; |
| die "./Build dist reqiures Module::Build 0.38 or higher--this is only " |
| . Module::Build->VERSION . $/ if $@; |
| } |
| |
| sub ACTION_distdir { |
| _check_module_build_for_dist; |
| shift->SUPER::ACTION_distdir(@_); |
| } |
| |
| sub ACTION_dist { |
| my $self = shift; |
| _check_module_build_for_dist; |
| |
| # Create POD. |
| $self->depends_on('pod'); |
| rmtree("autogen"); |
| |
| # We build our Perl release tarball from $REPOS_ROOT/perl, rather than |
| # from the top-level. |
| # |
| # Because some items we need are outside this directory, we need to copy a |
| # bunch of stuff. After the tarball is packaged up, we delete the copied |
| # directories. |
| my %to_copy = ( |
| '../core' => 'core', |
| '../modules' => 'modules', |
| '../devel' => 'devel', |
| '../lemon' => 'lemon', |
| '../CHANGES' => 'CHANGES', |
| '../CONTRIBUTING' => 'CONTRIBUTING', |
| '../LICENSE' => 'LICENSE', |
| '../NOTICE' => 'NOTICE', |
| '../README' => 'README', |
| $CHARMONIZER_C => 'charmonizer.c', |
| ); |
| print "Copying files...\n"; |
| while (my ($from, $to) = each %to_copy) { |
| confess("'$to' already exists") if -e $to; |
| system("cp -R $from $to") and confess(); |
| } |
| |
| move( "MANIFEST", "MANIFEST.bak" ) or die "move() failed: $!"; |
| $self->depends_on('manifest'); |
| my $no_index = $self->_gen_pause_exclusion_list; |
| my $meta_add = $self->meta_add || {}; |
| $meta_add->{no_index} = $no_index; |
| $self->meta_add( $meta_add ); |
| $self->SUPER::ACTION_dist; |
| |
| # Clean up. |
| print "Removing copied files...\n"; |
| rmtree($_) for values %to_copy; |
| unlink("META.yml"); |
| unlink("META.json"); |
| move( "MANIFEST.bak", "MANIFEST" ) or die "move() failed: $!"; |
| } |
| |
| sub ACTION_distmeta { |
| my $self = shift; |
| $self->SUPER::ACTION_distmeta(@_); |
| # Make sure everything has a version. |
| require CPAN::Meta; |
| my $v = version->new($self->dist_version); |
| my $meta = CPAN::Meta->load_file('META.json'); |
| my $provides = $meta->provides; |
| while (my ($pkg, $data) = each %{ $provides }) { |
| die "$pkg, defined in $data->{file}, has no version\n" |
| unless $data->{version}; |
| die "$pkg, defined in $data->{file}, is " |
| . version->new($data->{version})->normal |
| . " but should be " . $v->normal . "\n" |
| unless $data->{version} == $v; |
| } |
| } |
| |
| # Generate a list of files for PAUSE, search.cpan.org, etc to ignore. |
| sub _gen_pause_exclusion_list { |
| my $self = shift; |
| |
| # Only exclude files that are actually on-board. |
| open( my $man_fh, '<', 'MANIFEST' ) or die "Can't open MANIFEST: $!"; |
| my @manifest_entries = <$man_fh>; |
| chomp @manifest_entries; |
| |
| my @excluded_files; |
| for my $entry (@manifest_entries) { |
| # Allow README and Changes. |
| next if $entry =~ m#^(README|Changes)#; |
| |
| # Allow public modules. |
| if ( $entry =~ m#^(perl/)?lib\b.+\.(pm|pod)$# ) { |
| open( my $fh, '<', $entry ) or die "Can't open '$entry': $!"; |
| my $content = do { local $/; <$fh> }; |
| next if $content =~ /=head1\s*NAME/; |
| } |
| |
| # Disallow everything else. |
| push @excluded_files, $entry; |
| } |
| |
| # Exclude redacted modules. |
| if ( eval { require "buildlib/Lucy/Redacted.pm" } ) { |
| my @redacted = map { |
| my @parts = split( /\W+/, $_ ); |
| catfile( $LIB_DIR, @parts ) . '.pm' |
| } Lucy::Redacted->redacted, Lucy::Redacted->hidden; |
| push @excluded_files, @redacted; |
| } |
| |
| my %uniquifier; |
| @excluded_files = sort grep { !$uniquifier{$_}++ } @excluded_files; |
| return { file => \@excluded_files }; |
| } |
| |
| sub ACTION_semiclean { |
| my $self = shift; |
| print "Cleaning up most build files.\n"; |
| my @candidates |
| = grep { $_ !~ /(charmonizer|^_charm|charmony|charmonize|snowstem)/ } |
| $self->cleanup; |
| for my $path ( map { glob($_) } @candidates ) { |
| next unless -e $path; |
| rmtree($path); |
| confess("Failed to remove '$path'") if -e $path; |
| } |
| } |
| |
| sub ACTION_clean { |
| my $self = shift; |
| $self->_run_make( dir => $LEMON_DIR, args => ['clean'] ); |
| $self->SUPER::ACTION_clean; |
| } |
| |
| 1; |
| |
| __END__ |