# -*- coding: utf-8; -*- # Build.PL, (C) Dominique Quatravaux 2007 (See README for license details) # This script automatically builds a "Build" file in the current # directory (using a custom-made subclass to Module::Build), which in # turn builds the Crypt-OpenSSL-CA package. use strict; use warnings; use FindBin; use lib "$FindBin::Bin/inc"; use My::Module::Build; require 5.008; # Only tested as such. Advanced features that are # actually required include full UTF-8 support (ruling out 5.6) and # exception objects. my $class = My::Module::Build->subclass(code => join('', )); my $builder = $class->new ( module_name => 'Crypt::OpenSSL::CA', license => 'perl', dist_author => 'Dominique Quatravaux ', requires => { "XSLoader" => 0, }, build_requires => { "Convert::ASN1" => 0.20, # 0.20 brings needed utf8 bugfixes "Inline" => 0.40, "Inline::C" => 0, # Packages below are needed for the test suite. (And yes, # running the test suite *is* a requirement of the build # process) My::Module::Build->requires_for_build(), "Test::Builder" => 0, "Test::More" => 0, "Test::Group" => 0, "IPC::Run" => 0, "File::Find" => 0, "File::Path" => 0, "File::Spec" => 0, "File::Spec::Functions" => 0, "File::Slurp" => 0, "Devel::Leak" => 0, "Devel::Mallinfo" => 0, "Net::SSLeay" => 1.25, # For access to ERR_ in tests "MIME::Base64" => 0, "POSIX" => 0, }, add_to_cleanup => [ 'Crypt-OpenSSL-CA-*', "_Inline" ], add_to_no_index => { namespace => [ "Crypt::OpenSSL::CA::Inline" ] }, create_makefile_pl => 'passthrough', ); if (! $builder->prereq_failures()) { $builder->check_openssl_version_number ; } else { warn <check_openssl_version_number() again at # "./Build" time just to be sure. See L for discussion. } $builder->create_build_script(); 1; __END__ use strict; use warnings; use File::Spec::Functions qw(catdir catfile); use File::Spec::Unix; use IO::File; use FindBin qw($Bin); use My::Module::Build; =head1 CUSTOM BUILD OPTIONS =head2 --openssl-cflags=I Provides the CFLAGS to pass to the compiler so that it finds OpenSSL header files etc. Default is to query C, or failing that, to use no particular CFLAGS. =cut sub openssl_cflags : Config_Option(type="string") { (default => scalar(`pkg-config --cflags openssl 2>/dev/null`)); } =head2 --openssl-ldflags=I Provides the LDFLAGS to pass to the linker so that it finds OpenSSL libraries etc. Default is to query C, or failing that, to use only C<-lcrypto -lssl>. =cut sub openssl_ldflags : Config_Option(type="string") { (default => scalar(`pkg-config --libs openssl 2>/dev/null`)); } =head2 --full_debugging=1 Enables L while running C<./Build test>. Setting the FULL_DEBUGGING environment variable to 1 has the same effect, however the latter is not possible eg from the Perl debugger. Implies C (see L). =cut sub ACTION_test { my $self = shift; local $self->{args} = {%{$self->{args}}}; my %env = $self->customize_env(%ENV); delete $env{FULL_DEBUGGING}; if ($self->{args}->{full_debugging}) { $env{FULL_DEBUGGING} = 1; $self->use_blib(0); } $self->depends_on("buildXS") if $self->use_blib; local %ENV = %env; return $self->SUPER::ACTION_test; } =head1 CUSTOM CONSTRUCTOR AND BUILD METHODS =head2 resume () Overloaded so as to set a flag indicating that we are running from C<./Build> and not C, for the benefit of L which can thereafter act coy on L. =cut sub resume { my $class = shift; my $self = $class->SUPER::resume(@_); $self->{running_from_build_script} = 1; return $self; } =head2 ACTION_build () Overloaded so as to also call L. =head2 ACTION_buildXS () Builds the XS modules for distribution into L. Calls L. =cut sub ACTION_build { my $self = shift; $self->depends_on("buildXS"); $self->SUPER::ACTION_build(@_); } sub ACTION_buildXS { my ($self) = @_; $self->check_openssl_version_number(); do { unlink($_) or die "Cannot unlink($_): $!" } for glob("*.inl"); my @sofiles = glob(catfile(qw(blib arch auto Crypt OpenSSL CA * *.so))); my @sources = (catfile(qw(lib Crypt OpenSSL CA.pm)), catfile(qw(lib Crypt OpenSSL CA Inline C.pm))); return if (@sofiles && $self->up_to_date (\@sources, \@sofiles)); unlink @sofiles; my $version = $self->dist_version; # And now some ugly kludge to make everything hold together. # Inline::C wants to use MakeMaker; we don't. So let's call it in # a sub-Perl. local $ENV{PERL_INLINE_BUILD_NOISY} = 1; $self->run_subscript(<<"SCRIPT", $version, catdir(qw(blib arch))); BEGIN { \$Crypt::OpenSSL::CA::VERSION = '$version' ; } use Crypt::OpenSSL::CA::Inline::C; use Inline qw(_INSTALL_); use Crypt::OpenSSL::CA; SCRIPT do { unlink or die "Cannot unlink($_): $!" } for glob("*.inl"); } =begin internals =head1 OVERLOADED METHODS =head2 process_pm_files () Overloaded from parent class so as to reserve a special treatment to L (see its POD). =cut sub process_pm_files { my $self = shift; $self->SUPER::process_pm_files(@_); $self->require("Crypt::OpenSSL::CA::Inline::C"); my $out = catfile(qw(blib lib Crypt OpenSSL CA Inline C.pm)); unlink($out); my $outfd = new IO::File($out, ">") or die "Cannot open $out for writing: $!"; ($outfd->print(Crypt::OpenSSL::CA::Inline::C->installed_version) && $outfd->close()) or die "Cannot write to $out: $!\n"; } =head1 ADDITIONAL METHODS =head2 topdir () Returns the directory in which C resides. =cut sub topdir { # May not be good enough in some cases, but as long as the tests # do pass... require FindBin; return $FindBin::Bin; } =head2 customize_env (%env) Takes %env as an environment hash and returns a modified copy of it where the L are set. To be used typically as local %ENV = $self->customize_env(%ENV); right before doing anything that might result in the inline C code being recompiled. =cut sub customize_env { my ($self, %env) = @_; foreach my $item (qw(openssl_ldflags openssl_cflags)) { my $varname = "BUILD_" . uc($item); delete $env{$varname}; $env{$varname} = $self->option_value("$item") if $self->option_value("$item"); } return %env; } =head2 run_subscript ($script_text, @argv) Runs $script_text, a (possibly multi-line) script, in a sub-Perl with the environment appropriately set up so that it can invoke code in this package. @argv is passed on the command line. Dies upon errors. =cut sub run_subscript { my ($self, $script, @argv) = @_; chomp($script); $script =~ s/\n/ /g; my @cmdline = ($^X, "-I" => catdir($self->topdir, "lib"), -e => $script, @argv); warn(join(" ", @cmdline, "\n")); local %ENV = $self->customize_env(%ENV); system(@cmdline); die "Command exited with status " . ($? >> 8) if $?; } =head2 check_openssl_version_number () Checks that the OpenSSL development kit is working (ie, include files available + libraries that one can link a .so with) and additionally that the version thereof is 0.9.7 or later. If successful, encaches that information (persistently) so that subsequent calls to I return immediately (even from another invocation of C or C). If not, terminates with L. =cut sub check_openssl_version_number { my ($self) = @_; my $cachekey = "checked_openssl_version_number"; return if ($self->notes($cachekey)); $self->require("Crypt::OpenSSL::CA::Inline::C"); warn <<"MESSAGE"; Checking OpenSSL version number... MESSAGE my $test_c_program = <<"SOME_C_BONDAGE"; #include #include static long from_include_files() { return OPENSSL_VERSION_NUMBER; } static long from_linked_library() { return SSLeay(); } SOME_C_BONDAGE my $target_package = "Crypt::OpenSSL::CA::ExtractVersionNumber"; local %ENV = $self->customize_env(%ENV); eval { Crypt::OpenSSL::CA::Inline::C-> compile_into($target_package, $test_c_program); 1; } or do { # Bummer, C compilation went bang. Retry it with debugging on # so that the user gets a clue: my $error = $@; $target_package .= "Problem"; local $ENV{PERL_INLINE_BUILD_NOISY} = 1; eval { Crypt::OpenSSL::CA::Inline::C-> compile_into($target_package, $test_c_program); }; $error ||= $@; $self->fail_because_of_openssl_libs(<<"MESSAGE"); $error Compiling and linking a test OpenSSL program failed; please examine the errors above. You may have to install OpenSSL's development kit according to your distribution's instructions. MESSAGE }; my ($incversion, $libversion) = map { sprintf("0x%xd", $target_package->can($_)->()) } (qw(from_include_files from_linked_library)); unless ($incversion eq $libversion) { $self->fail_because_of_openssl_libs(<<"VERSION_MISMATCH"); *** VERSION MISMATCH in OpenSSL test program! *** The version number extracted from OpenSSL's header files ($incversion) differs from the one returned by a call to OpenSSL's SSLeay() ($libversion). This means that several versions of the OpenSSL development kit are present (in whole or in part) on your system. VERSION_MISMATCH } if (hex($libversion) < 0x00907000) { $self->fail_because_of_openssl_libs(<<"MESSAGE"); OpenSSL version 0.9.7 expected; I found only version $libversion. MESSAGE } warn <<"MESSAGE"; Found $libversion. Good. MESSAGE $self->notes($cachekey, 1); return; } =head2 require ($modulename) Like L; demands that $modulename, a module bundled with this CPAN distribution, be loaded in the current Perl process. =cut sub require { my ($self, $modulename) = @_; local @INC = (scalar(catdir($self->topdir, "lib")), @INC); eval "require $modulename; 1" or die $@; } =head2 fail_because_of_openssl_libs ($message) Like L, but wriggles me out of getting CPANTS blame mail. Called by L upon any mishap. Explain to the user what to do about it, namely set --openssl-cflags and/or --openssl-ldflags on the Build.PL command line. If run from the C<./Build> script instead (see L), acts coy and say "OS not supported", thereby tricking L into B reporting this failure. Okay, I'm not really proud about this quirk but it's the best I could come up under the current CPAN.pm design; here's why. When installing using CPAN.pm's automatic dependency-chasing feature (eg C including, but not limited to, the case of some CPANTS instance testing my module automatically), the dance happens thusly: =over =item 1. CPAN.pm runs my module's C and collects the standard output and error. If a C file is not created when C terminates, the game is over no matter what (and at this stage we can tell CPAN::Reporter to not send a report simply by exiting with code 0, see L). Otherwise CPAN.pm parses the standard output / error to find out the missing modules to install (!!). =item 2. CPAN.pm does whatever it takes to satisfy the dependencies, then turns around and runs the C file created at step 1. Now we are I in building the module and running the tests, and the only way to bail out without CPAN::Reporter yelling at us is to say "OS not supported" (op cit, L). =back The trouble is best summarized by saying that the upcall API from C to CPAN.pm is too narrow. I obviously wants a working set of header files and development libraries for OpenSSL; unfortunately we are unable to check for these until L is available, which typically occurs between steps 1. and 2. above, outside of our control. (And no, kludges such as backticking /usr/bin/openssl don't cut it, we really want to check for a working development kit because that's what we'll need at build time.) In an ideal world we would tell CPAN.pm "please install Inline::C right now" and then L immediately, all from within C. But we can't, and therefore the OpenSSL libs availability and version check typically has to wait until the C stage under hands-free installs; and in case the OpenSSL installation is no good there's no getting out from that pickle without upsetting CPANTS, save from lying with "OS not supported". The only alternative would be to not have a chained dependency problem, eg distribute L along with L in the C sub-directory, but that would be both bloaty and fragile. =cut sub fail_because_of_openssl_libs { my ($self, $message) = @_; my $exitcode = 0; $message =~ s/\s*$//gs; $message .= "\n" if $message; $message .= <<"SUGGEST_COMMAND_LINE_SWITCHES"; Maybe you could try re-running Build.PL with appropriate values for --openssl-cflags and --openssl-ldflags, for instance: perl Build.PL --openssl-cflags=-I/usr/local/lib/openssl/include \\ --openssl-libs=-L/usr/local/lib/openssl/lib Hint: don't sprinkle spaces where not necessary, as they are known to confuse the GNU linker! SUGGEST_COMMAND_LINE_SWITCHES if ($self->{running_from_build_script}) { $exitcode = 1; $message .= <<"CPAN_REPORTER_FODDER"; OS unsupported. Yes, this is a bald-faced lie, but I *really* don't want CPAN::Reporter to report this failure to me. (If you are running this interactively, well I'm sorry but didn't I tell you to re-run Build.PL after the Perl dependencies were fulfilled?) CPAN_REPORTER_FODDER } warn($message); exit($exitcode); } =end internals =cut 1;