blob: 6314ed5f5d49f86074b2f3049b4261dbe53c20dc [file] [log] [blame]
# 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;
use lib '../../compiler/perl/blib/arch';
use lib '../../compiler/perl/blib/lib';
package Clownfish::Build;
# We want to subclass Clownfish::CFC::Perl::Build, but CFC might not be built
# yet. So we look in 'clownfish/compiler/perl/lib' directly and cleanup @INC
# afterwards.
use lib '../../compiler/perl/lib';
use base qw(
Clownfish::CFC::Perl::Build
Clownfish::CFC::Perl::Build::Charmonic
);
no lib '../../compiler/perl/lib';
our $VERSION = '0.004001';
$VERSION = eval $VERSION;
use File::Spec::Functions qw( catdir catfile updir rel2abs );
use File::Path qw( rmtree );
use File::Copy qw( move );
use File::Find qw( find );
use Config;
use Carp;
use Cwd qw( getcwd );
my @BASE_PATH = __PACKAGE__->cf_base_path;
my $COMMON_SOURCE_DIR = catdir( @BASE_PATH, 'common' );
my $CORE_SOURCE_DIR = catdir( @BASE_PATH, 'core' );
my $CFC_DIR = catdir( @BASE_PATH, updir(), 'compiler', 'perl' );
my $XS_SOURCE_DIR = 'xs';
my $CFC_BUILD = catfile( $CFC_DIR, 'Build' );
my $LIB_DIR = 'lib';
my $CHARMONIZER_C;
my $IS_CPAN_DIST = !@BASE_PATH;
if ($IS_CPAN_DIST) {
$CHARMONIZER_C = 'charmonizer.c';
}
else {
$CHARMONIZER_C = catfile( $COMMON_SOURCE_DIR, 'charmonizer.c' );
}
sub new {
my ( $class, %args ) = @_;
$args{include_dirs} = $XS_SOURCE_DIR;
$args{clownfish_params} = {
autogen_header => _autogen_header(),
include => [], # Don't use default includes.
source => [ $CORE_SOURCE_DIR, $XS_SOURCE_DIR ],
};
if (!$IS_CPAN_DIST) {
delete $args{build_requires}{'Clownfish::CFC'};
delete $args{configure_requires}{'Clownfish::CFC::Perl::Build'};
}
my $self = $class->SUPER::new( recursive_test_files => 1, %args );
# 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 );
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;
}
sub ACTION_cfc {
my $self = shift;
return if $IS_CPAN_DIST;
my $old_dir = getcwd();
chdir($CFC_DIR);
if ( !-f 'Build' ) {
print "\nBuilding Clownfish compiler... \n";
system("$^X Build.PL");
system("$^X Build code");
print "\nFinished building Clownfish compiler.\n\n";
}
chdir($old_dir);
}
sub ACTION_copy_clownfish_includes {
my $self = shift;
$self->SUPER::ACTION_copy_clownfish_includes;
$self->cf_copy_include_file( 'XSBind.h' );
}
sub ACTION_clownfish {
my $self = shift;
$self->depends_on('cfc');
$self->SUPER::ACTION_clownfish;
}
sub ACTION_compile_custom_xs {
my $self = shift;
$self->depends_on('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 _valgrind_base_command {
return
"PERL_DESTRUCT_LEVEL=2 LUCY_VALGRIND=1 valgrind "
. "--leak-check=yes "
. "--show-reachable=yes "
. "--dsymutil=yes "
. "--suppressions=../../devel/conf/cfruntime-perl.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 and suppressions files.
$|++;
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);
}
}
sub _autogen_header {
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
}
# Run the cleanup targets for independent prerequisite builds.
sub _clean_prereq_builds {
my $self = shift;
if ( -e $CFC_BUILD ) {
my $old_dir = getcwd();
chdir $CFC_DIR;
system("$^X Build realclean")
and die "Clownfish clean failed";
chdir $old_dir;
}
}
sub ACTION_clean {
my $self = shift;
_clean_prereq_builds($self);
$self->SUPER::ACTION_clean;
}
sub ACTION_dist {
my $self = shift;
# We build our Perl release tarball from a subdirectory rather than from
# the top-level $REPOS_ROOT. Because some assets we need are outside this
# directory, we need to copy them in.
my %to_copy = (
'../../CONTRIBUTING' => 'CONTRIBUTING',
'../../LICENSE' => 'LICENSE',
'../../NOTICE' => 'NOTICE',
'../../README' => 'README',
$CORE_SOURCE_DIR => 'core',
$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("cp failed");
}
move( "MANIFEST", "MANIFEST.bak" ) or die "move() failed: $!";
my $saved = _hide_pod( $self, { 'lib/Clownfish.pm' => 1 } );
$self->depends_on("manifest");
$self->SUPER::ACTION_dist;
_restore_pod( $self, $saved );
# Now that the tarball is packaged up, delete the copied assets.
rmtree($_) for values %to_copy;
unlink("META.yml");
unlink("META.json");
move( "MANIFEST.bak", "MANIFEST" ) or die "move() failed: $!";
}
# Strip POD from files in the `lib` directory. This is a temporary measure to
# allow us to release Clownfish as a separate dist but with a cloaked API.
sub _hide_pod {
my ( $self, $excluded ) = @_;
my %saved;
find(
{
no_chdir => 1,
wanted => sub {
my $path = $File::Find::name;
return if $excluded->{$path};
return unless $path =~ /\.(pm|pod)$/;
open( my $fh, '<:encoding(UTF-8)', $path )
or confess("Can't open '$path' for reading: $!");
my $content = do { local $/; <$fh> };
close $fh;
if ( $path =~ /\.pod$/ ) {
$saved{$path} = $content;
print "Hiding POD for $path\n";
unlink($path) or confess("Can't unlink '$path': $!");
}
else {
my $copy = $content;
$copy =~ s/^=\w+.*?^=cut\s*$//gsm;
return if $copy eq $content;
print "Hiding POD for $path\n";
$saved{$path} = $content;
open( $fh, '>:encoding(UTF-8)', $path )
or confess("Can't open '$path' for writing: $!");
print $fh $copy;
}
},
},
'lib',
);
return \%saved;
}
# Undo POD hiding.
sub _restore_pod {
my ( $self, $saved ) = @_;
while ( my ( $path, $content ) = each %$saved ) {
open( my $fh, '>:encoding(UTF-8)', $path )
or confess("Can't open '$path' for writing: $!");
print $fh $saved->{$path};
}
}
1;