use strict; use warnings; use ExtUtils::MakeMaker; use ExtUtils::MakeMaker::Config; use File::Find; use Config; # you can set those manually if curl-config is not working for you my %curl = ( incdir => '', # /some/path (where curl/curl.h is) cflags => '', # -I/some/path libs => '', # -lcurl version => '' # 7.21.0 ); my $www_compat = 1; # 0, "" - no WWW::Curl compat # "exp" - explicit compatibility modules, will conflict with WWW::Curl # true - compat through Net::Curl::Compat (default) if ( exists $ENV{WWW_COMPAT} ) { $www_compat = $ENV{WWW_COMPAT}; } # - this version added curl_multi_socket_action() and many symbols my $minimum_libcurl_ver = "7.15.5"; my $constant_names; # XXX: some compilers may not support those flags my $devel_cflags = "-Wall -Wno-unknown-pragmas"; #$devel_cflags .= "-Werror -DCALLBACK_TYPECHECK " if -d ".git"; if ( $curl{libs} and $curl{version} ) { print "Using manually introduced curl options:\n"; while ( my ($k, $v) = each %curl ) { printf " %8s => %s\n", $k, $v; } } else { eval { require ExtUtils::PkgConfig; my $pkgconfig = `which curl`; unless ( $? ) { chomp $pkgconfig; $pkgconfig =~ s/\bbin(.+?)curl$/lib$1pkgconfig/i; $ENV{PKG_CONFIG_PATH} = $pkgconfig; } else { print STDERR "which failed:\n$@\n"; } %curl = ExtUtils::PkgConfig->find( 'libcurl' ); $curl{version} = $curl{modversion}; }; if ( $@ ) { print STDERR "pkgconfig failed:\n$@\n"; eval { %curl = get_curl_config(); $curl{version} =~ s/libcurl\s//; }; if ( $@ ) { print STDERR "curl-config failed:\n$@\n\n", "libcurl development files do not seem to be available\n", "You must install libcurl $minimum_libcurl_ver or newer to\n", "build this module\n\n"; print STDERR "NA: Unable to build distribution on this platform.\n"; exit 0; } } print "Found libcurl version $curl{version}\n"; (my $ver_to_cmp = $curl{version}) =~ s/[^\d]*$//; if ( eval "v$ver_to_cmp lt v$minimum_libcurl_ver" or $@ ) { print STDERR "Your currently installed libcurl version - $curl{version} - is too old.\n". "This module does not support libcurl older than $minimum_libcurl_ver\n\n"; print STDERR "NA: Unable to build distribution on this platform.\n"; exit 0; } } my $constant_names_sym = get_constants_symbols( $curl{version} ); eval { $curl{incdir} = get_curl_incdir(); $constant_names = get_constants_headers( $curl{cflags}, $curl{incdir} . "/curl/curl.h", -f $curl{incdir} . "/curl/multi.h" ? $curl{incdir} . "/curl/multi.h" : () ); }; if ( $@ ) { warn "Cannot extract constants from header files: $@"; warn "Using symbols-in-versions instead\n"; $constant_names = $constant_names_sym; } { my $cn = scalar @$constant_names; my $cns = scalar @$constant_names_sym; my %cn; @cn{ @$constant_names } = ( 1 ) x scalar @$constant_names; foreach my $cnt ( @$constant_names_sym ) { print "$cnt missing\n" unless $cn{ $cnt }; } my %cns; @cns{ @$constant_names_sym } = ( 1 ) x scalar @$constant_names_sym; foreach my $cnt ( @$constant_names ) { print "$cnt unexpected\n" unless $cns{ $cnt }; } die "Found only $cn constants, there should be at least $cns\n" if $cn < $cns; print "-> found $cn constants (should be $cns)\n"; } my @constant_types = divide_constants(); write_defenums( "const-defenums-h.inc" ); write_constants( "", $constant_types[ 0 ] ); write_constants( "Easy", $constant_types[ 1 ] ); write_constants( "Form", $constant_types[ 2 ] ); write_constants( "Multi", $constant_types[ 3 ] ); write_constants( "Share", $constant_types[ 4 ] ); split_xs( "Easy" ); split_xs( "Form" ); split_xs( "Multi" ); split_xs( "Share" ); write_examples_pod( 'lib/Net/Curl/examples.pod' ); if ( $www_compat ) { if ( $www_compat eq "exp" ) { deep_copy( 'inc/Compat/WWW', 'lib' ); open my $f, '>', 'lib/Net/Curl/Compat.pm'; print $f "'compat is explicit';\n"; } else { write_compat_maybe( 'lib/Net/Curl/Compat.pm', 'inc/Compat' ); } } else { open my $f, '>', 'lib/Net/Curl/Compat.pm'; print $f qq[die "WWW::Curl compatibility disabled\\n";\n]; } my $bits = (length(pack(p => 0)) < 8) ? ' -D_FILE_OFFSET_BITS=64' : ''; # older perl seems to choke on it, maybe utf8::upgrade would work ? my ($l_, $a_, $c_) = ($] >= 5.010) ? ("\x{142}", "\x{e1}", "\x{107}") : qw(l a c); WriteMakefile( NAME => 'Net::Curl', VERSION_FROM => 'lib/Net/Curl.pm', ABSTRACT_FROM => 'lib/Net/Curl.pm', AUTHOR => "Przemys${l_}aw Iskra ", CCFLAGS => $devel_cflags . ' ' . $curl{cflags} . $bits . " $Config::Config{ccflags}", LIBS => $curl{libs}, SIGN => 1, LICENSE => 'mit', META_MERGE => { recommends => { "ExtUtils::PkgConfig" => 0, "XSLoader" => 0, }, resources => { bugtracker => 'https://github.com/sparky/perl-Net-Curl/issues', homepage => 'https://github.com/sparky/perl-Net-Curl', repository => 'git://github.com/sparky/perl-Net-Curl.git', }, x_contributors => [ "B${a_}lint Szilakszi", "Felipe Gasper", "Fuji, Goro", "Przemys${l_}aw Iskra", "Stanislaw Pusep", "Olaf Alders", "Maksym Davydov", "Andy Jack", "Ferenc Erki", "Nick Kostyria", "Daniel Ruoso", "H.Merijn Brand", "Yanick Champoux", "Slaven Rezi${c_}", "Yuni Kim", ], }, MIN_PERL_VERSION => 5.008001, CONFIGURE_REQUIRES => { "ExtUtils::MakeMaker::Config" => 0, }, PREREQ_PM => { "Carp" => 0, "DynaLoader" => 0, "Exporter" => 0, "overload" => 0, "strict" => 0, "warnings" => 0, }, depend => { 'Makefile' => '$(VERSION_FROM)', '$(FIRST_MAKEFILE)' => join ( " ", qw(Curl_Easy.xsh Curl_Form.xsh Curl_Multi.xsh Curl_Share.xsh Curl_Easy_setopt.c Curl_Easy_callbacks.c inc/symbols-in-versions), glob "examples/*.pl" ), }, clean => { FILES => join " ", qw(const-*.inc curl-*.inc lib/WWW lib/Net/Curl/examples.pod lib/Net/Curl/Compat.pm), }, DIR => [], # no other Makefile.PL ); exit 0; sub get_curl_config { my $curl_config = $ENV{CURL_CONFIG} || 'curl-config'; print "Using $curl_config script.\n"; my %cc; foreach my $opt ( qw(vernum version prefix cflags libs) ) { my $ret = `${curl_config} --$opt`; if ( $? ) { die "Execution ${curl_config} --$opt failed.\n" . "is your libcurl installed correctly ?\n"; } chomp $ret; $cc{ $opt } = $ret; # print "${curl_config} --$opt: $ret\n"; } return %cc; } sub get_curl_incdir { my @incpath = ( ( defined $curl{incdir} ? $curl{incdir} : () ), ( $curl{cflags} =~ /-I(\S+)/g ), ( defined $curl{prefix} ? "$curl{prefix}/include" : () ), ( split /\s+/, $Config{usrinc} ), ( split /\s+/, $Config{locincpth} ), qw( /usr/include /usr/local/include /usr/local/curl/include /usr/local/include/curl ) ); foreach my $inc ( @incpath ) { if ( -f $inc . "/curl/curl.h") { return $inc; } } die "Cannot find curl/curl.h\n"; } sub get_constants_symbols { my $curlver = shift; $curlver =~ s/libcurl\s+//; $curlver =~ s/[^\d]*$//; my $cver = eval "v$curlver"; my %out; open my $fin, "<", "inc/symbols-in-versions" or die "Cannot open symbols file: $!\n"; while ( <$fin> ) { next if /^[#\s]/; my ( $sym, $in, $dep, $out ) = split /\s+/, $_; if ( $out ) { my $vout = eval "v$out"; next if $cver ge $vout; } if ( $in ne "-" ) { my $vin = eval "v$in"; next unless $cver ge $vin; } $out{ $sym } = 1; } my @out = sort keys %out; return \@out; } sub get_constants_headers { my %syms; my $cflags = shift; foreach my $curl_h ( @_ ) { print "Reading $curl_h ($Config{cpprun} $cflags $curl_h)\n"; open( H_IN, "-|", "$Config{cpprun} $cflags $curl_h" ) or die "Cannot run $Config{cpprun} $curl_h: $@\n"; while ( ) { if ( /enum\s+(\S+\s+)?{/ .. /}/ ) { s/^\s+//; next unless /^CURL/; chomp; s/[,\s].*//; s/=.*$//; next unless /^\w+$/; $syms{ $_ } = 1; } } close H_IN; open (H, "<", $curl_h) or die "Cannot open $curl_h: ".$!; while() { # Skip defines without values like: # #define CURL_STRICTER if (m{^#\s*define\s+(CURL\w*)\s*$}) { chomp; warn "Skipping '$_': does not define a symbol"; next; } m{^#\s*define\s+(CURL\w*)} and $syms{$1}++; } close H; } my @out; foreach my $e (sort keys %syms) { if ( $e =~ /(OBSOLETE|^CURL_EXTERN|_LAST\z|_LASTENTRY\z|^CURL_FORMAT_OFF_T$|^CURL_ISOCPP$|^CURLOPT$)/ ) { next; } push @out, $e; } return \@out; } sub divide_constants { my @out = (); foreach ( @$constant_names ) { my $list = 1; # Easy $list = 0 if /^CURL_?VERSION/; # main $list = 2 if /^CURL_?FORM/; # Form $list = 3 if /^CURL(M_|MSG_|MOPT_|_POLL_|_CSELECT_|_SOCKET_TIMEOUT)/; # Multi $list = 4 if /^(CURLSHOPT_|CURL_LOCK_)/; # Share push @{ $out[ $list ] }, $_; } return @out; } sub split_xs { my $name = shift; my $in = "Curl_$name.xsh"; open my $fin, '<', $in or die "Can't open $in: $!\n"; my $outc = "curl-$name-c.inc"; open my $foutc, '>', $outc or die "Can't create $outc: $!\n"; print "Writing $outc\n"; my $outxs = "curl-$name-xs.inc"; open my $foutxs, '>', $outxs or die "Can't create $outxs: $!\n"; print "Writing $outxs\n"; while ( <$fin> ) { if ( /^MODULE\s*=.*PACKAGE/ ) { print $foutxs $_; print $foutxs @_ = <$fin>; last; } else { print $foutxs "\n"; print $foutc $_; } } } sub write_constants { my $name = shift; my $constants = shift; my $lname = $name ? lc $name : 'curl'; my $out = "const-$lname-xs.inc"; print "Writing $out\n"; open my $foutxs, '>', $out or die "Can't create $out: $!\n"; $name .= '::' if $name; my $symbol_table = "Net::Curl::$name"; print $foutxs <<"EOBOOT"; BOOT: { dTHX; HV *symbol_table = get_hv( "$symbol_table", GV_ADD ); static const struct iv_s values_for_iv[] = { EOBOOT foreach my $c ( sort @$constants ) { printf $foutxs qq[\t\t\t{ "%s", %d, %s },\n], $c, length $c, $c; } print $foutxs <<'EOBOOT'; { NULL, 0, 0 } }; const struct iv_s *value_for_iv = values_for_iv; while ( value_for_iv->name ) { perl_curl_constant_add(aTHX_ symbol_table, value_for_iv->name, value_for_iv->namelen, newSViv( value_for_iv->value ) ); ++value_for_iv; } ++PL_sub_generation; } EOBOOT } sub write_defenums { my $out = shift; print "Writing $out\n"; open my $o, ">", $out; foreach ( @$constant_names ) { print $o "#ifndef $_\n"; print $o "# define $_ $_\n"; print $o "#endif\n"; } close $o; } sub write_examples_pod { my $out = shift; print "Writing $out\n"; open my $o, ">", $out; print $o "=head1 NAME\n\n"; print $o "Net::Curl::examples - sample modules and test code for Net::Curl\n\n"; foreach my $script ( sort glob "examples/*.pl" ) { my $nopod = 0; my $code = 1; print "<- $script\n"; open my $fin, '<', $script or die "Cannot open $script: $!\n"; while ( <$fin> ) { if ( /^=cut/ ) { $code = 1; next; } elsif ( /^=/ ) { $code = 0; } elsif ( /^#nopod/ ) { $nopod = 1; next; } elsif ( /^#endnopod/ ) { $nopod = 0; next; } elsif ( $nopod ) { next; } $_ = " " . $_ if $code; s/^\t/ /; s/\t/ /g; s/ +$//; print $o $_; if ( /^=head1\s/ ) { print $o "\n=head4 I>\n"; } } print $o "\n=cut\n"; } } sub write_compat_maybe { my ( $name_out, $name_in ) = @_; return unless -r $name_in; open my $fout, '>', $name_out or die "Cannot write to $name_out: $!\n"; print "Writing $name_out\n"; my @pm; find( sub{ push @pm, $File::Find::name if /\.pm$/ }, $name_in ); local $/ = undef; my $data = ""; my @sections; foreach ( sort @pm ) { open my $fin, '<', $_ or die; s#\Q$name_in\E/##; my $pos = length $data; push @sections, "\t'$_' => $pos,"; $data .= <$fin> . "\n__END__\n\n"; } open my $fin, '<', $name_in . ".pm" or die; $_ = <$fin>; local $" = "\n"; s/#MODULES#/@sections/; print $fout $_ . $data; } sub deep_copy { my ( $src, $dst ) = @_; system "cp", "-a", $src, $dst; if ( $? ) { require File::Copy::Recursive; ( my $name = $src ) =~ s#.*/##; File::Copy::Recursive::dircopy( $src, "$dst/$name" ); } } sub MY::postamble { return <<'EOM'; .PHONY: testall disttestall version_update symbols_update test_update inc_update testall: AUTOMATED_TESTING=1 AUTHOR_TESTING=1 EXTENDED_TESTING=1 $(MAKE) test disttestall: AUTOMATED_TESTING=1 AUTHOR_TESTING=1 EXTENDED_TESTING=1 $(MAKE) disttest version_update: sed -i "/VERSION\s*=/s/=\s*'.*'/= '$(VERSION)'/" lib/Net/Curl/*.pm symbols_update: curl https://github.com/bagder/curl/raw/master/docs/libcurl/symbols-in-versions -o inc/symbols-in-versions test_update: curl https://github.com/sparky/perl-Test-HTTP-Server/raw/master/lib/Test/HTTP/Server.pm -o inc/Test/HTTP/Server.pm inc_update: symbols_update test_update EOM } __END__ package ExtUtils::MM_Unix; sub all_target { my $self = shift; return <<'MAKE_EXT'; all :: pure_all manifypods $(NOECHO) echo "Module loads OK ?" $(PERLRUNINST) -le 'use Net::Curl; print Net::Curl::LIBCURL_VERSION' $(NOECHO) $(NOOP) MAKE_EXT } # vim: ts=4:sw=4