#!/usr/bin/env perl use strict; use Getopt::Std; # Globals my $VER = "1.15"; # 1.15 [KK 2008-10-12] Option "optflags" implemented. # 1.14 [KK 2008-08-22] c-compiler and c++-compiler attempt to find by # version, eg. '/opt/local/bin/g++-mp-4.2' is better # than '/usr/bin/g++' # 1.13 [KK 2008-07-15] Opimized subfiles() - way faster when nonrecursive now # 1.12 [KK 2008-04-15] Messaging improved upon -v flag # 1.11 [KK 2008-01-14] Added /opt/local/{lib,include} to the standard libs. # Also added /sw # 1.10 [KK 2007-08-29] Added libvariable01 and flag -l. Flags -l/L get # used upon libfunction/libvariable checks. # 1.09 [KK 2007-06-13] Added 'lib64' variants to libdirs, for 64bit Linux # 1.08 [KK 2007-05-22] -L{dir} shown only once for identical dirs during 'lib' # 1.07 [KK 2007-05-18] Flag -c for caching implemented # 1.06 [KK 2007-04-27] Added ifheader01 and libfunction01 # 1.05 [KK 2006-09-28] Flag -s (silent) implemented. Usage text updated. # 1.04 [KK 2006-09-05] C-compilers: gcc/g++ get selected first, instead of # cc/c++. Helps HP-UX ports. [Thanks, Bernd Krumboeck.] # 1.03 [KK 2006-07-19] 'subfiles' keeps track of visited dirs incase of # recursion. Testing is now by inode, used to be by name. # 1.02 [KK 2006-06-01] 'findbin' searches for .exe too now, for Cygwin support # 1.01 [KK 2005-09-29] Implemented context-sensitive help via -h. # Action 'header' implemented. # 1.00 [KK 2005-09-28] First version # Configuration my @def_headerdirs = ('/usr/include', '/usr/local/include', '/opt/local/include', '/sw/include', "$ENV{HOME}/include", ); my @def_libdirs = ('/usr/lib', '/usr/lib64', '/usr/local/lib', '/usr/local/lib64', '/opt/local/lib', '/opt/local/lib64', '/usr/ucblib', '/sw/lib', '/sw/lib64', "$ENV{HOME}/lib", ); my @c_compilers = ('gcc', 'cc'); my @cpp_compilers = ('g++', 'c++'); # Globals my %opts; my $base; my @warnings; my $printed; my @headerdirs; my @libdirs; my @libs; my $cachekey; my $cacheval; # Show usage and croak sub usage { die <<"ENDUSAGE" This is c-conf, the C compilation configuration helper V$VER Copyright (c) e-tunity. Contact for information. Usage: $base [flags] header FILE.H [FILE.H...] Searches for directories containing the named header(s), returns appropriate -I flags. $base [flags] headerdir DIR [DIR...]: Searches for directory containing headers, returns appropriate -I flags. $base [flags] ifheader FILE.H DEFINE Searches for the named header. If found, a compilation flag -DDEFINE is returned, indicating that the header is found. $base [flags] ifheader01 FILE.H DEFINE Similar to ifheader, but the returned define is either DEFINE=0 or DEFINE=1 $base [flags] lib NAME [NAME...]: Searches for libNAME.{a,so,...}, returns appropriate -L and -l flags. $base [flags] libfunction FUNC DEFINE Creates a small program that tries to use FUNC. If this succeeds, a -DDEFINE=1 flag is returned. $base [flags] libfunction01 FUNC DEFINE Similar to libfunction, but the returned define is DEFINE=0 or DEFINE=1 $base [flags] libvariable01 VAR DEFINE Creates a small program that accesses variable VAR. If this succeeds, -DDEFINE=1 is returned. $base [flags] so-name NAME: Returns filename of a shared-object for NAME, e.g. libNAME.so $base [flags] so-cflags: Returns compilation flags to build shared objects $base [flags] so-lflags: Returns linkage flags to produce a shared-object library $base [flags] c-compiler: Returns name of C compiler $base [flags] c++-compiler: Returns name of C++ compiler $base [flags] optflags: Returns fast-code optimization flags. Optional flags: -c CACHE: settings will be dynamically determined unless present in CACHE; new settings will be added to CACHE -h: to show short help for an action, e.g. try '$base -h so-name' -s: to suppress showing of warnings -v: to show verbose messages -I DIR[,DIR..]: to add DIR(s) to the searchpath for headers, default searchpath is @headerdirs -L DIR[,DIR..]: to add DIR(s) to the searchpath for libraries, default searchpath is @libdirs -l LIB[,LIB..]: to add LIB(s) to C compiler invocations, when checking for lib functions or variables Meaningful output is returned on stdout. Verbose messages, warnings and errors go to stderr. ENDUSAGE } # Issue a warning sub warning { push (@warnings, "@_"); } # Show a message sub msg { return unless ($opts{v}); print STDERR ("$base: ", @_); } # Show help info if -h was given sub checkhelp { return unless ($opts{h}); print STDERR (@_); exit (1); } # Basename / dirname of a file. sub basename ($) { my $name = shift; $name =~ s{.*/}{}; return ($name); } sub dirname ($) { my $name = shift; return (undef) unless ($name =~ /\//); $name =~ s{/[^/]$}{}; return ($name); } # Get the uname. sub uname() { my $ret = `uname`; chomp ($ret); return ($ret); } # Find a binary along the path. sub findbin($) { my $bin = shift; my $bestx = undef; my $bestver = -1; foreach my $d (split (/:/, $ENV{PATH})) { my @cand = (glob("$d/$bin"), glob("$d/$bin.exe"), glob("$d/$bin-*"), glob("$d/$bin-*.exe")); msg ("Candidates for '$bin' in '$d': [@cand]\n"); for my $x (@cand) { if (-x $x) { my $ver = $x; $ver =~ s{^.*/[^\d]*}{}; $ver = sprintf("%g", $ver); msg ("Version of $x: $ver\n"); if ($bestver < $ver or !$bestx) { $bestver = $ver; $bestx = $x; msg (" .. best so far\n"); } } } } msg ("Failed to locate executable '$bin'!\n") unless ($bestx); return ($bestx); } # Recursively determine the files under a given dir. my %_dir_visited; sub subfiles ($$$) { my ($dir, $mask, $recursive) = @_; %_dir_visited = (); my ($dev, $ino) = stat($dir) or return (undef); my $tag = sprintf ("%d-%d", $dev, $ino); return (undef) if ($_dir_visited{$tag}); $_dir_visited{$tag} = $dir; return (undef) unless (-d $dir); my @ret = (); foreach my $f (glob ("$dir/$mask")) { push (@ret, $f) if (-f $f); } if ($recursive) { foreach my $d (glob ("$dir/*")) { next unless (-d $d); my @subret = subfiles ("$d", $mask, 1); my $added = 0; foreach my $f (@subret) { if (-f $f) { push (@ret, $f); $added++; } } } } if ($#ret > -1) { return (@ret); } else { return (undef); } } # Output stuff, update $cacheval incase we'll add it to the cache later. sub output { if ($printed++) { print (' '); $cacheval .= ' '; } print (@_); for my $a (@_) { $cacheval .= $a; } } # Find a header, output a define if found. sub if_header { checkhelp <<"ENDHELP"; 'ifheader' tries to find a header file in the 'include' directories. When found, a define-flag for the C compiler is returned. E.g.: $base ifheader malloc.h HAVE_MALLOC_H (may return -DHAVE_MALLOC_H) Use in a Makefile as in: CFLAGS = \$(CFLAGS) \$(shell c-conf ifheader malloc.h HAVE_MALLOC_H) Then in a C source as: #ifdef HAVE_MALLOC_H #include #endif ENDHELP usage() if ($#_ != 1); my ($h, $def) = @_; foreach my $d (@headerdirs) { if (-f "$d/$h") { msg ("Header '$h' found as '$d/$h'\n"); output ("-D$def"); return; } } } # Find a header, output define=0 or define=1 sub if_header01 { checkhelp <<"ENDHELP"; 'ifheader01' tries to find a header in the 'include' directories. When found, a define for the C compiler is returned having value 1. When not found, the value is 0. Use in a Makefile as follows: CFLAGS = \$(shell c-conf ifheader01 malloc.h HAVE_MALLOC_H) Then in a C source as: #if HAVE_MALLOC_H == 1 #include #endif ENDHELP usage() if ($#_ != 1); my ($h, $def) = @_; foreach my $d (@headerdirs) { if (-f "$d/$h") { output ("-D$def=1"); return; } } output ("-D$def=0"); return; } # Find a header sub header { checkhelp <<"ENDHELP"; 'header' locates one or more C headers in the 'include' directories. E.g.: $base header e-lib.h stdio.h (may return -I/usr/include -I/usr/e/include) Use in a Makefile as in: CFLAGS = -C -Wall \$(shell c-conf header e-lib.h) Then in a C source as: #include ENDHELP usage() if ($#_ == -1); foreach my $h (@_) { my $found = 0; foreach my $d (@headerdirs) { if (-f "$d/$h") { $found++; msg ("Header '$h' found as '$d/$h\n"); output ("-I$d"); last; } } warning ("Failed to locate header '$h' in @headerdirs\n") unless ($found); } } # Find a header directory sub headerdir { checkhelp <<"ENDHELP"; 'headerdir' locates directories under which (in steps) C headers are E.g.: $base headerdir libxml2 (may return '-I/usr/include/libxml2') Use in a Makefile as in: CFLAGS = -C -Wall \$(shell c-conf headerdir libxml2) Then in a C source as: #include ENDHELP usage() if ($#_ == -1); foreach my $headerdir (@_) { my $found = 0; foreach my $d (@headerdirs) { my $target = "$d/$headerdir"; if (subfiles ($target, '*.h', 0)) { msg ("Header directory '$headerdir' found as '$target'\n"); output ("-I$target"); $found++; } } warning ("Header dir '$headerdir' not found\n") unless ($found); } } # Find a library sub lib { checkhelp <<"ENDHELP"; 'lib' generates the linkage flags for a given library name. The name is bare, without 'lib' and '.so' and the like. E.g.: $base lib xml2 (may return '-L/usr/lib -lxml2') Use in a Makefile as in: LDFLAGS = \$(shell c-conf lib xml2) ENDHELP usage() if ($#_ == -1); my %dirshown; foreach my $lib (@_) { my $found = 0; foreach my $d (@libdirs) { my $hit = (subfiles ($d, "lib$lib.*", 0))[0]; if ($hit) { msg ("Library '$lib' found as '$hit'\n"); $found++; $hit =~ s{/[^/]*$}{}; if (! $dirshown{$hit}) { output ("-L$hit"); $dirshown{$hit} = 1; } output ("-l$lib"); } } #warning ("Library '$lib' not found\n") # unless ($found); } } # Compilation flags to make a so-ready object. sub so_cflags { checkhelp <<"ENDHELP"; 'so-cflags' returns the compilation flags that are necessary when building objects for a shared library. E.g.: $base so-cflags (may return '-fPIC') Use in a Makefile as in: CFLAGS = -c -g -Wall \$(shell c-conf so-cflags) ENDHELP usage() if ($#_ > -1); my $flags; if (uname() eq 'Darwin') { $flags = '-fPIC'; } elsif (uname() eq 'Linux') { $flags = '-fpic'; } msg ("Shared object compilation flags: '$flags'\n"); output ($flags); } # Linkage flags to make an so. sub so_lflags { checkhelp << "ENDHELP"; 'so-lflags' returns the linkage flags that are necessary when combining objects into a shared library. E.g.: $base so-lflags (may return '-dynamiclib -Wl,-single_module') Use in a Makefile as in: MY_SO = \$(shell c-conf so-name my) \$(MY_SO): *.o \$(CC) -o \$(MY_SO) \$(shell c-conf so-lflags) *.o ENDHELP usage() if ($#_ > -1); my $lib = shift; my $flags; if (uname() eq 'Darwin') { $flags = "-dynamiclib -Wl,-single_module"; } else { $flags = "-shared"; } msg ("Shared library linkage flags: '$flags'\n"); output ($flags); } # Find the C compiler and return it, or die trying. sub find_c_compiler { foreach my $c (@c_compilers) { my $full = findbin($c); return ($full) if ($full); } die ("No C compiler found\n"); } # Get the C compiler sub c_compiler { checkhelp <<"ENDHELP"; 'c-compiler' tries to find a C compiler and returns its (bare) name. E.g.: $base c-compiler -> gcc ENDHELP usage() if ($#_ > -1); my $cc; eval { $cc = find_c_compiler(); }; if ($@) { warning ($@); } else { msg ("C compiler: '$cc'\n"); output ($cc); } } # Get the C++ compiler sub cpp_compiler { checkhelp <<"ENDHELP"; 'c++-compiler' tries to find a C++ compiler and returns its (bare) name. E.g.: $base c++-compiler -> g++ ENDHELP usage() if ($#_ > -1); foreach my $c (@cpp_compilers) { my $full = findbin($c); if ($full) { msg ("C++ compiler: '$full'\n"); output ($full); return; } } warning ("No C++ compiler found\n"); } # Get fast code optimization flags. sub optflags { checkhelp <<"ENDHELP"; 'optflags' tries to determine optimization flags. E.g.: $base optflags -> -O3 ENDHELP usage() if ($#_ > -1); for my $optflag ('-fast', '-O3', '-O2') { if (test_compile ("int main() {}\n", $optflag)) { output ($optflag); return; } } warning ("No optimization flag found."); } # Get the name for an SO. sub so_name { checkhelp <<"ENDHELP"; 'so-name' returns the filename of a shared library, based on the LIB argument. E.g.: $base so-name test -> libtest.so ENDHELP usage() if ($#_ != 0); my $name = shift; my $dir = dirname ($name); my $base = basename ($name); my $dest; if (uname() eq 'Darwin') { $dest = "lib$base.dylib"; } else { $dest = "lib$base.so"; } if ($dir ne '') { msg ("Shared library name for '$name': '$dir/$dest'\n"); output ("$dir/$dest"); } else { msg ("Shared library name for '$name': '$dest'\n"); output ("$dest"); } } # Check that a libfunction is present. sub libfunction { checkhelp <<"ENDHELP"; 'libfunction' checks whether a library function is present. There are two arguments: the function to check, and a define to output when the function is found. The output is a -D flag for the compiler commandline. E.g.: $base libfunction printf HAVE_PRINTF -> -DHAVE_PRINTF=1 $base libfunction foo_bar HAVE_FOOBAR -> (nothing) ENDHELP if (test_libfunction (@_)) { msg ("Library function '$_[0]' present\n"); output ("-D$_[1]=1"); } else { msg ("Library function '$_[0]' absent\n"); } } # Check that a lib variable is present, 01 version sub libvariable01 { checkhelp <<"ENDHELP"; 'libvariable01' checks whether a library variable is present. There are two arguments: a variable name, and a define to output with value 0 or 1. E.g.: $base libvariable01 errno HAVE_ERRNO -> -DHAVE_ERRNO=1 ENDHELP if (test_libvariable(@_)) { msg ("Library variable '$_[1]' present\n"); output ("-D$_[1]=1"); } else { msg ("Library variable '$_[1]' absent\n"); output ("-D$_[1]=0"); } } # Check that a libfunction is present, 01-version sub libfunction01 { checkhelp <<"ENDHELP"; 'libfunction01' checks whether a library function is present. There are two arguments: the function to check, and a define to output when the function is found. When the lib function is found, then the define is returned with a value 1, else with a value 0. E.g.: $base libfunction01 printf HAVE_PRINTF -> -DHAVE_PRINTF=1 $base libfunction foo_bar HAVE_FOOBAR -> -DHAVE_FOOBAR=0 ENDHELP if (test_libfunction (@_)) { msg ("Library function '$_[1]' present\n"); output ("-D$_[1]=1"); } else { msg ("Library function '$_[1]' absent\n"); output ("-D$_[1]=0"); } } sub test_compile { my $sourcecode = shift; my $cc = find_c_compiler(); # Create a temp .c file. my $src = "/tmp/$$.c"; my $dst = "/tmp/$$.out"; open (my $of, ">$src") or die ("Cannot write $src: $!\n"); print $of ($sourcecode); close ($of); my $cmd = "$cc "; for my $flag (@_) { $cmd .= "$flag "; } $cmd .= "$src -o $dst " . cc_inc_flags() . ' ' . cc_libdir_flags() . ' ' . cc_lib_flags(); # print ($cmd, "\n"); my $ret = system ("$cmd >/dev/null 2>&1"); unlink ($src, $dst); return ($ret == 0 ? 1 : 0); } # Return @headerdirs as C flags sub cc_inc_flags() { my $ret = ''; for my $h (@headerdirs) { $ret .= " -I$h"; } msg ("C compilation include flags: '$ret'\n"); return ($ret); } # Return @libdirs as C flags sub cc_libdir_flags() { my $ret = ''; for my $l (@libdirs) { $ret .= " -L$l" if (-d $l); } msg ("C library directory flags: '$ret'\n"); return ($ret); } # Return @libs as C flags sub cc_lib_flags() { my $ret = ''; for my $l (@libs) { $ret .= " -l$l"; } msg ("C library flags: '$ret'\n"); return ($ret); } # Test whether a lib function is present. sub test_libfunction { usage() if ($#_ != 1); my ($func, $def) = @_; return (test_compile ("main () {\n" . " void $func (void);\n" . " $func();\n" . "}\n")); } # Test whether a lib variable is present. sub test_libvariable { usage() if ($#_ != 1); my ($var, $def) = @_; return (test_compile ("main () {\n" . " extern int $var;\n" . " $var = 42;\n" . "}\n")); } # Main starts here $base = $0; $base =~ s{.*/}{}; usage () unless (getopts ('vhI:L:sc:l:', \%opts)); # See if we got a cache with the requests in it. # If we can match the request, return the result. if ($opts{c}) { $cachekey = ''; for my $a (@ARGV) { $cachekey .= ' ' if ($cachekey ne ''); $cachekey .= $a; } if (open (my $if, $opts{c})) { while (my $line = <$if>) { chomp ($line); my ($key, $val) = split (/\t/, $line); if ($key eq $cachekey) { output ($val); print ("\n"); exit (0); } } } } foreach my $d (split (/,/, $opts{L})) { push (@libdirs, $d); } foreach my $d (split (/,/, $opts{I})) { push (@headerdirs, $d); } foreach my $d (split (/,/, $opts{l})) { push (@libs, $d); } push (@libdirs, @def_libdirs); push (@headerdirs, @def_headerdirs); my $action = shift (@ARGV); if ($action eq 'header') { header (@ARGV); } elsif ($action eq 'headerdir') { headerdir (@ARGV); } elsif ($action eq 'lib') { lib (@ARGV); } elsif ($action eq 'so-cflags') { so_cflags (@ARGV); } elsif ($action eq 'so-lflags') { so_lflags (@ARGV); } elsif ($action eq 'c-compiler') { c_compiler(@ARGV); } elsif ($action eq 'c++-compiler') { cpp_compiler(@ARGV); } elsif ($action eq 'so-name') { so_name (@ARGV); } elsif ($action eq 'ifheader') { if_header (@ARGV); } elsif ($action eq 'ifheader01') { if_header01 (@ARGV); } elsif ($action eq 'libfunction') { libfunction (@ARGV); } elsif ($action eq 'libfunction01') { libfunction01 (@ARGV); } elsif ($action eq 'libvariable01') { libvariable01 (@ARGV); } elsif ($action eq 'optflags') { optflags (@ARGV); } else { usage (); } print ("\n") if ($printed); # Add to the cache if necessary. if ($opts{c}) { open (my $of, ">>$opts{c}") or die ("Cannot append to cache file $opts{c}: $!\n"); print $of ("$cachekey\t$cacheval\n"); } if ($#warnings > -1) { foreach my $w (@warnings) { print STDERR ("$base WARNING: $w") unless ($opts{s}); } exit (1); } exit (0);