1package ExtUtils::CBuilder::Base; 2 3use strict; 4use File::Spec; 5use File::Basename; 6use Cwd (); 7use Config; 8use Text::ParseWords; 9use IPC::Cmd qw(can_run); 10use File::Temp qw(tempfile); 11 12use vars qw($VERSION); 13$VERSION = '0.280217'; 14 15# More details about C/C++ compilers: 16# http://developers.sun.com/sunstudio/documentation/product/compiler.jsp 17# http://gcc.gnu.org/ 18# http://publib.boulder.ibm.com/infocenter/comphelp/v101v121/index.jsp 19# http://msdn.microsoft.com/en-us/vstudio/default.aspx 20 21my %cc2cxx = ( 22 # first line order is important to support wrappers like in pkgsrc 23 cc => [ 'c++', 'CC', 'aCC', 'cxx', ], # Sun Studio, HP ANSI C/C++ Compilers 24 gcc => [ 'g++' ], # GNU Compiler Collection 25 xlc => [ 'xlC' ], # IBM C/C++ Set, xlc without thread-safety 26 xlc_r => [ 'xlC_r' ], # IBM C/C++ Set, xlc with thread-safety 27 cl => [ 'cl' ], # Microsoft Visual Studio 28); 29 30sub new { 31 my $class = shift; 32 my $self = bless {@_}, $class; 33 34 $self->{properties}{perl} = $class->find_perl_interpreter 35 or warn "Warning: Can't locate your perl binary"; 36 37 while (my ($k,$v) = each %Config) { 38 $self->{config}{$k} = $v unless exists $self->{config}{$k}; 39 } 40 $self->{config}{cc} = $ENV{CC} if defined $ENV{CC}; 41 $self->{config}{ccflags} = join(" ", $self->{config}{ccflags}, $ENV{CFLAGS}) 42 if defined $ENV{CFLAGS}; 43 $self->{config}{cxx} = $ENV{CXX} if defined $ENV{CXX}; 44 $self->{config}{cxxflags} = $ENV{CXXFLAGS} if defined $ENV{CXXFLAGS}; 45 $self->{config}{ld} = $ENV{LD} if defined $ENV{LD}; 46 $self->{config}{ldflags} = join(" ", $self->{config}{ldflags}, $ENV{LDFLAGS}) 47 if defined $ENV{LDFLAGS}; 48 49 unless ( exists $self->{config}{cxx} ) { 50 my ($ccpath, $ccbase, $ccsfx ) = fileparse($self->{config}{cc}, qr/\.[^.]*/); 51 foreach my $cxx (@{$cc2cxx{$ccbase}}) { 52 if( can_run( File::Spec->catfile( $ccpath, $cxx, $ccsfx ) ) ) { 53 $self->{config}{cxx} = File::Spec->catfile( $ccpath, $cxx, $ccsfx ); 54 last; 55 } 56 if( can_run( File::Spec->catfile( $cxx, $ccsfx ) ) ) { 57 $self->{config}{cxx} = File::Spec->catfile( $cxx, $ccsfx ); 58 last; 59 } 60 if( can_run( $cxx ) ) { 61 $self->{config}{cxx} = $cxx; 62 last; 63 } 64 } 65 unless ( exists $self->{config}{cxx} ) { 66 $self->{config}{cxx} = $self->{config}{cc}; 67 my $cflags = $self->{config}{ccflags}; 68 $self->{config}{cxxflags} = '-x c++'; 69 $self->{config}{cxxflags} .= " $cflags" if defined $cflags; 70 } 71 } 72 73 return $self; 74} 75 76sub find_perl_interpreter { 77 my $perl; 78 File::Spec->file_name_is_absolute($perl = $^X) 79 or -f ($perl = $Config::Config{perlpath}) 80 or ($perl = $^X); # XXX how about using IPC::Cmd::can_run here? 81 return $perl; 82} 83 84sub add_to_cleanup { 85 my $self = shift; 86 foreach (@_) { 87 $self->{files_to_clean}{$_} = 1; 88 } 89} 90 91sub cleanup { 92 my $self = shift; 93 foreach my $file (keys %{$self->{files_to_clean}}) { 94 unlink $file; 95 } 96} 97 98sub get_config { 99 return %{ $_[0]->{config} }; 100} 101 102sub object_file { 103 my ($self, $filename) = @_; 104 105 # File name, minus the suffix 106 (my $file_base = $filename) =~ s/\.[^.]+$//; 107 return "$file_base$self->{config}{obj_ext}"; 108} 109 110sub arg_include_dirs { 111 my $self = shift; 112 return map {"-I$_"} @_; 113} 114 115sub arg_nolink { '-c' } 116 117sub arg_object_file { 118 my ($self, $file) = @_; 119 return ('-o', $file); 120} 121 122sub arg_share_object_file { 123 my ($self, $file) = @_; 124 return ($self->split_like_shell($self->{config}{lddlflags}), '-o', $file); 125} 126 127sub arg_exec_file { 128 my ($self, $file) = @_; 129 return ('-o', $file); 130} 131 132sub arg_defines { 133 my ($self, %args) = @_; 134 return map "-D$_=$args{$_}", keys %args; 135} 136 137sub compile { 138 my ($self, %args) = @_; 139 die "Missing 'source' argument to compile()" unless defined $args{source}; 140 141 my $cf = $self->{config}; # For convenience 142 143 my $object_file = $args{object_file} 144 ? $args{object_file} 145 : $self->object_file($args{source}); 146 147 my $include_dirs_ref = 148 (exists($args{include_dirs}) && ref($args{include_dirs}) ne "ARRAY") 149 ? [ $args{include_dirs} ] 150 : $args{include_dirs}; 151 my @include_dirs = $self->arg_include_dirs( 152 @{ $include_dirs_ref || [] }, 153 $self->perl_inc(), 154 ); 155 156 my @defines = $self->arg_defines( %{$args{defines} || {}} ); 157 158 my @extra_compiler_flags = 159 $self->split_like_shell($args{extra_compiler_flags}); 160 my @cccdlflags = $self->split_like_shell($cf->{cccdlflags}); 161 my @ccflags = $self->split_like_shell($args{'C++'} ? $cf->{cxxflags} : $cf->{ccflags}); 162 my @optimize = $self->split_like_shell($cf->{optimize}); 163 my @flags = ( 164 @include_dirs, 165 @defines, 166 @cccdlflags, 167 @extra_compiler_flags, 168 $self->arg_nolink, 169 @ccflags, 170 @optimize, 171 $self->arg_object_file($object_file), 172 ); 173 my @cc = $self->split_like_shell($args{'C++'} ? $cf->{cxx} : $cf->{cc}); 174 175 $self->do_system(@cc, @flags, $args{source}) 176 or die "error building $object_file from '$args{source}'"; 177 178 return $object_file; 179} 180 181sub have_compiler { 182 my ($self, $is_cplusplus) = @_; 183 my $have_compiler_flag = $is_cplusplus ? "have_cxx" : "have_cc"; 184 my $suffix = $is_cplusplus ? ".cc" : ".c"; 185 return $self->{$have_compiler_flag} if defined $self->{$have_compiler_flag}; 186 187 my $result; 188 my $attempts = 3; 189 # tmpdir has issues for some people so fall back to current dir 190 191 # don't clobber existing files (rare, but possible) 192 my ( $FH, $tmpfile ) = tempfile( "compilet-XXXXX", SUFFIX => $suffix ); 193 binmode $FH; 194 195 if ( $is_cplusplus ) { 196 print $FH "class Bogus { public: int boot_compilet() { return 1; } };\n"; 197 } 198 else { 199 print $FH "int boot_compilet() { return 1; }\n"; 200 } 201 close $FH; 202 203 my ($obj_file, @lib_files); 204 eval { 205 local $^W = 0; 206 local $self->{quiet} = 1; 207 $obj_file = $self->compile('C++' => $is_cplusplus, source => $tmpfile); 208 @lib_files = $self->link(objects => $obj_file, module_name => 'compilet'); 209 }; 210 $result = $@ ? 0 : 1; 211 212 foreach (grep defined, $tmpfile, $obj_file, @lib_files) { 213 1 while unlink; 214 } 215 216 return $self->{$have_compiler_flag} = $result; 217} 218 219sub have_cplusplus { 220 push @_, 1; 221 goto &have_compiler; 222} 223 224sub lib_file { 225 my ($self, $dl_file, %args) = @_; 226 $dl_file =~ s/\.[^.]+$//; 227 $dl_file =~ tr/"//d; 228 229 if (defined $args{module_name} and length $args{module_name}) { 230 # Need to create with the same name as DynaLoader will load with. 231 require DynaLoader; 232 if (defined &DynaLoader::mod2fname) { 233 my $lib = DynaLoader::mod2fname([split /::/, $args{module_name}]); 234 my ($dev, $lib_dir, undef) = File::Spec->splitpath($dl_file); 235 $dl_file = File::Spec->catpath($dev, $lib_dir, $lib); 236 } 237 } 238 239 $dl_file .= ".$self->{config}{dlext}"; 240 241 return $dl_file; 242} 243 244 245sub exe_file { 246 my ($self, $dl_file) = @_; 247 $dl_file =~ s/\.[^.]+$//; 248 $dl_file =~ tr/"//d; 249 return "$dl_file$self->{config}{_exe}"; 250} 251 252sub need_prelink { 0 } 253 254sub extra_link_args_after_prelink { return } 255 256sub prelink { 257 my ($self, %args) = @_; 258 259 my ($dl_file_out, $mksymlists_args) = _prepare_mksymlists_args(\%args); 260 261 require ExtUtils::Mksymlists; 262 # dl. abbrev for dynamic library 263 ExtUtils::Mksymlists::Mksymlists( %{ $mksymlists_args } ); 264 265 # Mksymlists will create one of these files 266 return grep -e, map "$dl_file_out.$_", qw(ext def opt); 267} 268 269sub _prepare_mksymlists_args { 270 my $args = shift; 271 ($args->{dl_file} = $args->{dl_name}) =~ s/.*::// unless $args->{dl_file}; 272 273 my %mksymlists_args = ( 274 DL_VARS => $args->{dl_vars} || [], 275 DL_FUNCS => $args->{dl_funcs} || {}, 276 FUNCLIST => $args->{dl_func_list} || [], 277 IMPORTS => $args->{dl_imports} || {}, 278 NAME => $args->{dl_name}, # Name of the Perl module 279 DLBASE => $args->{dl_base}, # Basename of DLL file 280 FILE => $args->{dl_file}, # Dir + Basename of symlist file 281 VERSION => (defined $args->{dl_version} ? $args->{dl_version} : '0.0'), 282 ); 283 return ($args->{dl_file}, \%mksymlists_args); 284} 285 286sub link { 287 my ($self, %args) = @_; 288 return $self->_do_link('lib_file', lddl => 1, %args); 289} 290 291sub link_executable { 292 my ($self, %args) = @_; 293 return $self->_do_link('exe_file', lddl => 0, %args); 294} 295 296sub _do_link { 297 my ($self, $type, %args) = @_; 298 299 my $cf = $self->{config}; # For convenience 300 301 my $objects = delete $args{objects}; 302 $objects = [$objects] unless ref $objects; 303 my $out = $args{$type} || $self->$type($objects->[0], %args); 304 305 my @temp_files; 306 @temp_files = 307 $self->prelink(%args, dl_name => $args{module_name}) 308 if $args{lddl} && $self->need_prelink; 309 310 my @linker_flags = ( 311 $self->split_like_shell($args{extra_linker_flags}), 312 $self->extra_link_args_after_prelink( 313 %args, dl_name => $args{module_name}, prelink_res => \@temp_files 314 ) 315 ); 316 317 my @output = $args{lddl} 318 ? $self->arg_share_object_file($out) 319 : $self->arg_exec_file($out); 320 my @shrp = $self->split_like_shell($cf->{shrpenv}); 321 my @ld = $self->split_like_shell($cf->{ld}); 322 323 $self->do_system(@shrp, @ld, @output, @$objects, @linker_flags) 324 or die "error building $out from @$objects"; 325 326 return wantarray ? ($out, @temp_files) : $out; 327} 328 329 330sub do_system { 331 my ($self, @cmd) = @_; 332 print "@cmd\n" if !$self->{quiet}; 333 return !system(@cmd); 334} 335 336sub split_like_shell { 337 my ($self, $string) = @_; 338 339 return () unless defined($string); 340 return @$string if UNIVERSAL::isa($string, 'ARRAY'); 341 $string =~ s/^\s+|\s+$//g; 342 return () unless length($string); 343 344 # Text::ParseWords replaces all 'escaped' characters with themselves, which completely 345 # breaks paths under windows. As such, we forcibly replace backwards slashes with forward 346 # slashes on windows. 347 $string =~ s@\\@/@g if $^O eq 'MSWin32'; 348 349 return Text::ParseWords::shellwords($string); 350} 351 352# if building perl, perl's main source directory 353sub perl_src { 354 # N.B. makemaker actually searches regardless of PERL_CORE, but 355 # only squawks at not finding it if PERL_CORE is set 356 357 return unless $ENV{PERL_CORE}; 358 359 my $Updir = File::Spec->updir; 360 my $dir = File::Spec->curdir; 361 362 # Try up to 5 levels upwards 363 for (0..10) { 364 if ( 365 -f File::Spec->catfile($dir,"config_h.SH") 366 && 367 -f File::Spec->catfile($dir,"perl.h") 368 && 369 -f File::Spec->catfile($dir,"lib","Exporter.pm") 370 ) { 371 return Cwd::realpath( $dir ); 372 } 373 374 $dir = File::Spec->catdir($dir, $Updir); 375 } 376 377 warn "PERL_CORE is set but I can't find your perl source!\n"; 378 return ''; # return empty string if $ENV{PERL_CORE} but can't find dir ??? 379} 380 381# directory of perl's include files 382sub perl_inc { 383 my $self = shift; 384 385 $self->perl_src() || File::Spec->catdir($self->{config}{archlibexp},"CORE"); 386} 387 388sub DESTROY { 389 my $self = shift; 390 local($., $@, $!, $^E, $?); 391 $self->cleanup(); 392} 393 3941; 395 396# vim: ts=2 sw=2 et: 397